From 4dfdfcccd3e6fe3ee80cedfe249862aff197648b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 4 Aug 2024 16:09:52 +0200 Subject: [PATCH] Migrate to external library for interacting with API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- package.json | 6 +- src/actions/about.ts | 4 +- src/actions/account-notes.ts | 12 +- src/actions/accounts.ts | 279 ++++++------------ src/actions/admin.ts | 91 +++--- src/actions/aliases.ts | 59 ++-- src/actions/apps.ts | 27 +- src/actions/auth.ts | 33 ++- src/actions/backups.ts | 6 +- src/actions/bookmarks.ts | 25 +- src/actions/circle.ts | 40 ++- src/actions/compose.ts | 46 +-- src/actions/conversations.ts | 21 +- src/actions/custom-emojis.ts | 6 +- src/actions/directory.ts | 6 +- src/actions/domain-blocks.ts | 34 +-- src/actions/email-list.ts | 8 +- src/actions/emoji-reacts.ts | 8 +- src/actions/events.ts | 58 ++-- src/actions/export-data.ts | 31 +- src/actions/external-auth.ts | 10 +- src/actions/familiar-followers.ts | 8 +- src/actions/favourites.ts | 29 +- src/actions/filters.ts | 249 +++------------- src/actions/groups.ts | 26 +- src/actions/history.ts | 6 +- src/actions/import-data.ts | 17 +- src/actions/instance.ts | 65 +--- src/actions/interactions.ts | 99 +++---- src/actions/lists.ts | 104 +++---- src/actions/markers.ts | 11 +- src/actions/me.ts | 21 +- src/actions/media.ts | 47 +-- src/actions/mfa.ts | 18 +- src/actions/notifications.ts | 34 +-- src/actions/oauth.ts | 18 +- src/actions/pin-statuses.ts | 8 +- src/actions/polls.ts | 12 +- src/actions/push-notifications/registerer.ts | 2 +- src/actions/push-subscriptions.ts | 17 +- src/actions/reports.ts | 21 +- src/actions/scheduled-statuses.ts | 23 +- src/actions/search.ts | 78 ++--- src/actions/security.ts | 41 ++- src/actions/soapbox.ts | 11 +- src/actions/status-quotes.ts | 6 +- src/actions/statuses.ts | 65 ++-- src/actions/suggestions.ts | 84 ++---- src/actions/tags.ts | 30 +- src/actions/timelines.ts | 8 +- src/actions/trending-statuses.ts | 5 +- src/actions/trends.ts | 31 -- src/api/hooks/accounts/useAccount.ts | 7 +- src/api/hooks/accounts/useAccountList.ts | 18 +- src/api/hooks/accounts/useAccountLookup.ts | 7 +- src/api/hooks/accounts/useFollow.ts | 18 +- src/api/hooks/accounts/useRelationship.ts | 6 +- src/api/hooks/accounts/useRelationships.ts | 7 +- src/api/hooks/admin/useAnnouncements.ts | 18 +- src/api/hooks/admin/useDomains.ts | 18 +- src/api/hooks/admin/useModerationLog.ts | 6 +- src/api/hooks/admin/useRelays.ts | 10 +- src/api/hooks/admin/useRules.ts | 18 +- src/api/hooks/admin/useSuggest.ts | 14 +- src/api/hooks/admin/useVerify.ts | 12 +- .../hooks/announcements/useAnnouncements.ts | 16 +- .../groups/useCancelMembershipRequest.ts | 6 +- src/api/hooks/groups/useCreateGroup.ts | 17 +- src/api/hooks/groups/useDeleteGroupStatus.ts | 6 +- src/api/hooks/groups/useGroup.ts | 6 +- src/api/hooks/groups/useGroupMedia.ts | 6 +- src/api/hooks/groups/useGroupMembers.ts | 7 +- .../groups/useGroupMembershipRequests.ts | 10 +- src/api/hooks/groups/useGroupRelationship.ts | 6 +- src/api/hooks/groups/useGroupRelationships.ts | 6 +- src/api/hooks/groups/useGroups.ts | 6 +- src/api/hooks/groups/useUpdateGroup.ts | 21 +- src/api/hooks/statuses/useBookmarkFolders.ts | 6 +- .../hooks/statuses/useCreateBookmarkFolder.ts | 6 +- .../hooks/statuses/useUpdateBookmarkFolder.ts | 6 +- src/api/index.ts | 104 +------ src/components/autosuggest-account-input.tsx | 8 +- src/components/blurhash.tsx | 2 +- src/components/polls/poll-footer.tsx | 2 +- src/components/polls/poll.tsx | 2 +- src/components/sidebar-navigation.tsx | 2 +- src/components/thumb-navigation.tsx | 2 +- src/entity-store/hooks/types.ts | 4 +- src/entity-store/hooks/useEntities.ts | 8 +- src/entity-store/hooks/useEntityActions.ts | 16 +- src/features/admin/announcements.tsx | 1 - src/features/bookmark-folders/index.tsx | 1 - .../chats/components/chat-composer.tsx | 44 ++- .../chats/components/chat-textarea.tsx | 68 ++--- src/features/chats/components/chat.tsx | 47 ++- .../compose/components/language-dropdown.tsx | 1 - .../compose/components/privacy-dropdown.tsx | 1 - src/features/direct-timeline/index.tsx | 55 ---- .../export-data/components/csv-exporter.tsx | 2 +- src/features/filters/edit-filter.tsx | 20 +- src/features/follow-recommendations/index.tsx | 14 +- src/features/mutes/index.tsx | 1 - .../notifications/components/filter-bar.tsx | 2 +- .../steps/suggested-accounts-step.tsx | 13 +- src/features/soapbox-config/index.tsx | 4 +- .../manage-group-modal/steps/details-step.tsx | 1 - .../ui/components/panels/sign-up-panel.tsx | 1 - .../ui/components/subscription-button.tsx | 47 +-- src/features/ui/index.tsx | 6 +- src/features/ui/util/async-components.ts | 1 - src/hooks/index.ts | 2 +- src/hooks/useApi.ts | 11 - src/hooks/useClient.ts | 7 + src/pages/search-page.tsx | 1 - src/queries/accounts.ts | 11 +- src/queries/chats.ts | 39 ++- src/queries/embed.ts | 12 +- src/queries/relationships.ts | 6 +- src/queries/search.ts | 46 +-- src/queries/suggestions.ts | 94 ++---- src/queries/trends.ts | 6 +- src/reducers/auth.ts | 22 +- src/reducers/domain-lists.ts | 3 +- src/reducers/followed-tags.ts | 5 +- src/reducers/instance.ts | 39 +-- src/reducers/meta.ts | 6 +- src/reducers/relationships.ts | 4 - src/reducers/status-lists.ts | 7 +- src/reducers/suggestions.ts | 26 +- src/reducers/trends.ts | 10 +- src/reducers/user-lists.ts | 7 +- src/schemas/emoji-reaction.ts | 1 - src/schemas/instance.ts | 2 - src/selectors/index.ts | 1 - src/utils/features.ts | 37 +-- src/utils/queries.ts | 3 +- yarn.lock | 48 ++- 137 files changed, 1136 insertions(+), 2094 deletions(-) delete mode 100644 src/features/direct-timeline/index.tsx delete mode 100644 src/hooks/useApi.ts create mode 100644 src/hooks/useClient.ts diff --git a/package.json b/package.json index 3d7076d0c..3bc9d36bc 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,6 @@ "bugs": { "url": "https://github.com/mkljczk/pl-fe/issues" }, - "funding": { - "type": "lightning", - "url": "lightning:alex@alexgleason.me" - }, "scripts": { "start": "npx vite serve", "dev": "${npm_execpath} run start", @@ -100,6 +96,7 @@ "@types/uuid": "^9.0.0", "@vitejs/plugin-react": "^4.0.4", "@webbtc/webln-types": "^3.0.0", + "async-dispatch": "^1.0.10", "autoprefixer": "^10.4.15", "babel-plugin-formatjs": "^10.5.6", "babel-plugin-preval": "^5.1.0", @@ -137,6 +134,7 @@ "multiselect-react-dropdown": "^2.0.25", "object-to-formdata": "^4.5.1", "path-browserify": "^1.0.1", + "pl-api": "file:../pl-api", "postcss": "^8.4.29", "process": "^0.11.10", "punycode": "^2.1.1", diff --git a/src/actions/about.ts b/src/actions/about.ts index ee73c9852..8a129594e 100644 --- a/src/actions/about.ts +++ b/src/actions/about.ts @@ -1,4 +1,4 @@ -import api from '../api'; +import { staticFetch } from '../api'; import type { AnyAction } from 'redux'; import type { RootState } from 'soapbox/store'; @@ -11,7 +11,7 @@ const fetchAboutPage = (slug = 'index', locale?: string) => (dispatch: React.Dis dispatch({ type: FETCH_ABOUT_PAGE_REQUEST, slug, locale }); const filename = `${slug}${locale ? `.${locale}` : ''}.html`; - return api(getState)(`/instance/about/${filename}`) + return staticFetch(`/instance/about/${filename}`) .then(({ data: html }) => { dispatch({ type: FETCH_ABOUT_PAGE_SUCCESS, slug, locale, html }); return html; diff --git a/src/actions/account-notes.ts b/src/actions/account-notes.ts index c867f09d9..b8846b20b 100644 --- a/src/actions/account-notes.ts +++ b/src/actions/account-notes.ts @@ -1,4 +1,4 @@ -import api from '../api'; +import { getClient } from '../api'; import type { AnyAction } from 'redux'; import type { RootState } from 'soapbox/store'; @@ -11,12 +11,10 @@ const submitAccountNote = (id: string, value: string) => (dispatch: React.Dispatch, getState: () => RootState) => { dispatch(submitAccountNoteRequest()); - return api(getState)(`/api/v1/accounts/${id}/note`, { - method: 'POST', body: - JSON.stringify({ comment: value }), - }).then(response => { - dispatch(submitAccountNoteSuccess(response.json)); - }).catch(error => dispatch(submitAccountNoteFail(error))); + return getClient(getState).accounts.updateAccountNote(id, value) + .then(response => { + dispatch(submitAccountNoteSuccess(response)); + }).catch(error => dispatch(submitAccountNoteFail(error))); }; const submitAccountNoteRequest = () => ({ diff --git a/src/actions/accounts.ts b/src/actions/accounts.ts index da241ac9b..0c09f9ab7 100644 --- a/src/actions/accounts.ts +++ b/src/actions/accounts.ts @@ -4,7 +4,7 @@ import { selectAccount } from 'soapbox/selectors'; import { isLoggedIn } from 'soapbox/utils/auth'; import { getFeatures, parseVersion, PLEROMA } from 'soapbox/utils/features'; -import api, { getNextLink, type PlfeResponse } from '../api'; +import { getClient, type PlfeResponse } from '../api'; import { importFetchedAccount, @@ -13,6 +13,7 @@ import { } from './importer'; import type { Map as ImmutableMap } from 'immutable'; +import type { Account, PaginatedResponse } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity, Status } from 'soapbox/types/entities'; import type { History } from 'soapbox/types/history'; @@ -41,14 +42,6 @@ const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST'; const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS'; const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL'; -const ACCOUNT_SUBSCRIBE_REQUEST = 'ACCOUNT_SUBSCRIBE_REQUEST'; -const ACCOUNT_SUBSCRIBE_SUCCESS = 'ACCOUNT_SUBSCRIBE_SUCCESS'; -const ACCOUNT_SUBSCRIBE_FAIL = 'ACCOUNT_SUBSCRIBE_FAIL'; - -const ACCOUNT_UNSUBSCRIBE_REQUEST = 'ACCOUNT_UNSUBSCRIBE_REQUEST'; -const ACCOUNT_UNSUBSCRIBE_SUCCESS = 'ACCOUNT_UNSUBSCRIBE_SUCCESS'; -const ACCOUNT_UNSUBSCRIBE_FAIL = 'ACCOUNT_UNSUBSCRIBE_FAIL'; - const ACCOUNT_PIN_REQUEST = 'ACCOUNT_PIN_REQUEST'; const ACCOUNT_PIN_SUCCESS = 'ACCOUNT_PIN_SUCCESS'; const ACCOUNT_PIN_FAIL = 'ACCOUNT_PIN_FAIL'; @@ -129,10 +122,7 @@ const noOp = () => new Promise(f => f(undefined)); const createAccount = (params: Record) => async (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: ACCOUNT_CREATE_REQUEST, params }); - return api(getState, 'app')('/api/v1/accounts', { - method: 'POST', - body: JSON.stringify(params), - }).then(({ json: token }) => + return getClient(getState()).accounts.createAccount(params).then((token) => dispatch({ type: ACCOUNT_CREATE_SUCCESS, params, token }), ).catch(error => { dispatch({ type: ACCOUNT_CREATE_FAIL, error, params }); @@ -152,10 +142,10 @@ const fetchAccount = (id: string) => dispatch(fetchAccountRequest(id)); - return api(getState)(`/api/v1/accounts/${id}`) + return getClient(getState()).accounts.getAccount(id) .then(response => { - dispatch(importFetchedAccount(response.json)); - dispatch(fetchAccountSuccess(response.json)); + dispatch(importFetchedAccount(response)); + dispatch(fetchAccountSuccess(response)); }) .catch(error => { dispatch(fetchAccountFail(id, error)); @@ -168,10 +158,10 @@ const fetchAccountByUsername = (username: string, history?: History) => const features = getFeatures(instance); if (features.accountByUsername && (me || !features.accountLookup)) { - return api(getState)(`/api/v1/accounts/${username}`).then(response => { - dispatch(fetchRelationships([response.json.id])); - dispatch(importFetchedAccount(response.json)); - dispatch(fetchAccountSuccess(response.json)); + return getClient(getState()).accounts.getAccount(username).then(response => { + dispatch(fetchRelationships([response.id])); + dispatch(importFetchedAccount(response)); + dispatch(fetchAccountSuccess(response)); }).catch(error => { dispatch(fetchAccountFail(null, error)); dispatch(importErrorWhileFetchingAccountByUsername(username)); @@ -186,11 +176,7 @@ const fetchAccountByUsername = (username: string, history?: History) => maybeRedirectLogin(error, history); }); } else { - return dispatch(accountSearch({ - q: username, - limit: 5, - resolve: true, - })).then(accounts => { + return getClient(getState()).accounts.searchAccounts(username, { resolve: true, limit: 1 }).then(accounts => { const found = accounts.find((a: APIEntity) => a.acct === username); if (found) { @@ -229,11 +215,11 @@ const blockAccount = (id: string) => dispatch(blockAccountRequest(id)); - return api(getState)(`/api/v1/accounts/${id}/block`, { method: 'POST' }) + return getClient(getState()).accounts.blockAccount(id) .then(response => { - dispatch(importEntities([response.json], Entities.RELATIONSHIPS)); + dispatch(importEntities([response], Entities.RELATIONSHIPS)); // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers - return dispatch(blockAccountSuccess(response.json, getState().statuses)); + return dispatch(blockAccountSuccess(response, getState().statuses)); }).catch(error => dispatch(blockAccountFail(error))); }; @@ -243,10 +229,10 @@ const unblockAccount = (id: string) => dispatch(unblockAccountRequest(id)); - return api(getState)(`/api/v1/accounts/${id}/unblock`, { method: 'POST' }) + return getClient(getState()).accounts.unblockAccount(id) .then(response => { - dispatch(importEntities([response.json], Entities.RELATIONSHIPS)); - return dispatch(unblockAccountSuccess(response.json)); + dispatch(importEntities([response], Entities.RELATIONSHIPS)); + return dispatch(unblockAccountSuccess(response)); }) .catch(error => dispatch(unblockAccountFail(error))); }; @@ -286,6 +272,8 @@ const muteAccount = (id: string, notifications?: boolean, duration = 0) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; + const client = getClient(getState); + dispatch(muteAccountRequest(id)); const params: Record = { @@ -293,8 +281,7 @@ const muteAccount = (id: string, notifications?: boolean, duration = 0) => }; if (duration) { - const state = getState(); - const instance = state.instance; + const instance = client.instanceInformation; const v = parseVersion(instance.version); if (v.software === PLEROMA) { @@ -304,14 +291,11 @@ const muteAccount = (id: string, notifications?: boolean, duration = 0) => } } - return api(getState)(`/api/v1/accounts/${id}/mute`, { - method: 'POST', - body: JSON.stringify(params), - }) + return client.accounts.muteAccount(id, params) .then(response => { - dispatch(importEntities([response.json], Entities.RELATIONSHIPS)); + dispatch(importEntities([response], Entities.RELATIONSHIPS)); // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers - return dispatch(muteAccountSuccess(response.json, getState().statuses)); + return dispatch(muteAccountSuccess(response, getState().statuses)); }) .catch(error => dispatch(muteAccountFail(error))); }; @@ -322,10 +306,10 @@ const unmuteAccount = (id: string) => dispatch(unmuteAccountRequest(id)); - return api(getState)(`/api/v1/accounts/${id}/unmute`, { method: 'POST' }) + return getClient(getState()).accounts.unmuteAccount(id) .then(response => { - dispatch(importEntities([response.json], Entities.RELATIONSHIPS)); - return dispatch(unmuteAccountSuccess(response.json)); + dispatch(importEntities([response], Entities.RELATIONSHIPS)); + return dispatch(unmuteAccountSuccess(response)); }) .catch(error => dispatch(unmuteAccountFail(error))); }; @@ -361,70 +345,14 @@ const unmuteAccountFail = (error: unknown) => ({ error, }); -const subscribeAccount = (id: string, notifications?: boolean) => - (dispatch: AppDispatch, getState: () => RootState) => { - if (!isLoggedIn(getState)) return null; - - dispatch(subscribeAccountRequest(id)); - - return api(getState)(`/api/v1/pleroma/accounts/${id}/subscribe`, { - method: 'POST', - body: JSON.stringify({ notifications }), - }).then(response => dispatch(subscribeAccountSuccess(response.json))) - .catch(error => dispatch(subscribeAccountFail(error))); - }; - -const unsubscribeAccount = (id: string) => - (dispatch: AppDispatch, getState: () => RootState) => { - if (!isLoggedIn(getState)) return null; - - dispatch(unsubscribeAccountRequest(id)); - - return api(getState)(`/api/v1/pleroma/accounts/${id}/unsubscribe`, { method: 'POST' }) - .then(response => dispatch(unsubscribeAccountSuccess(response.json))) - .catch(error => dispatch(unsubscribeAccountFail(error))); - }; - -const subscribeAccountRequest = (id: string) => ({ - type: ACCOUNT_SUBSCRIBE_REQUEST, - id, -}); - -const subscribeAccountSuccess = (relationship: APIEntity) => ({ - type: ACCOUNT_SUBSCRIBE_SUCCESS, - relationship, -}); - -const subscribeAccountFail = (error: unknown) => ({ - type: ACCOUNT_SUBSCRIBE_FAIL, - error, -}); - -const unsubscribeAccountRequest = (id: string) => ({ - type: ACCOUNT_UNSUBSCRIBE_REQUEST, - id, -}); - -const unsubscribeAccountSuccess = (relationship: APIEntity) => ({ - type: ACCOUNT_UNSUBSCRIBE_SUCCESS, - relationship, -}); - -const unsubscribeAccountFail = (error: unknown) => ({ - type: ACCOUNT_UNSUBSCRIBE_FAIL, - error, -}); - const removeFromFollowers = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; dispatch(removeFromFollowersRequest(id)); - return api(getState)(`/api/v1/accounts/${id}/remove_from_followers`, { - method: 'POST', - }) - .then(response => dispatch(removeFromFollowersSuccess(response.json))) + return getClient(getState()).accounts.removeAccountFromFollowers(id) + .then(response => dispatch(removeFromFollowersSuccess(response))) .catch(error => dispatch(removeFromFollowersFail(id, error))); }; @@ -448,13 +376,11 @@ const fetchFollowers = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchFollowersRequest(id)); - return api(getState)(`/api/v1/accounts/${id}/followers`) + return getClient(getState()).accounts.getAccountFollowers(id) .then(response => { - const next = getNextLink(response); - - dispatch(importFetchedAccounts(response.json)); - dispatch(fetchFollowersSuccess(id, response.json, next || null)); - dispatch(fetchRelationships(response.json.map((item: APIEntity) => item.id))); + dispatch(importFetchedAccounts(response.items)); + dispatch(fetchFollowersSuccess(id, response.items, response.next)); + dispatch(fetchRelationships(response.items.map((item) => item.id))); }) .catch(error => { dispatch(fetchFollowersFail(id, error)); @@ -466,7 +392,7 @@ const fetchFollowersRequest = (id: string) => ({ id, }); -const fetchFollowersSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({ +const fetchFollowersSuccess = (id: string, accounts: APIEntity[], next: (() => Promise>) | null) => ({ type: FOLLOWERS_FETCH_SUCCESS, id, accounts, @@ -483,22 +409,17 @@ const expandFollowers = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; - const url = getState().user_lists.followers.get(id)?.next as string; + const next = getState().user_lists.followers.get(id)!.next; - if (url === null) { - return null; - } + if (next === null) return; dispatch(expandFollowersRequest(id)); - return api(getState)(url) - .then(response => { - const next = getNextLink(response); - - dispatch(importFetchedAccounts(response.json)); - dispatch(expandFollowersSuccess(id, response.json, next || null)); - dispatch(fetchRelationships(response.json.map((item: APIEntity) => item.id))); - }) + next().then(response => { + dispatch(importFetchedAccounts(response.items)); + dispatch(expandFollowersSuccess(id, response.items, response.next)); + dispatch(fetchRelationships(response.items.map((item: APIEntity) => item.id))); + }) .catch(error => { dispatch(expandFollowersFail(id, error)); }); @@ -509,7 +430,7 @@ const expandFollowersRequest = (id: string) => ({ id, }); -const expandFollowersSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({ +const expandFollowersSuccess = (id: string, accounts: APIEntity[], next: (() => Promise>) | null) => ({ type: FOLLOWERS_EXPAND_SUCCESS, id, accounts, @@ -526,13 +447,11 @@ const fetchFollowing = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchFollowingRequest(id)); - return api(getState)(`/api/v1/accounts/${id}/following`) + return getClient(getState()).accounts.getAccountFollowing(id) .then(response => { - const next = getNextLink(response); - - dispatch(importFetchedAccounts(response.json)); - dispatch(fetchFollowingSuccess(id, response.json, next || null)); - dispatch(fetchRelationships(response.json.map((item: APIEntity) => item.id))); + dispatch(importFetchedAccounts(response.items)); + dispatch(fetchFollowingSuccess(id, response.items, response.next)); + dispatch(fetchRelationships(response.items.map((item) => item.id))); }) .catch(error => { dispatch(fetchFollowingFail(id, error)); @@ -544,7 +463,7 @@ const fetchFollowingRequest = (id: string) => ({ id, }); -const fetchFollowingSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({ +const fetchFollowingSuccess = (id: string, accounts: APIEntity[], next: (() => Promise>) | null) => ({ type: FOLLOWING_FETCH_SUCCESS, id, accounts, @@ -561,25 +480,19 @@ const expandFollowing = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; - const url = getState().user_lists.following.get(id)!.next; + const next = getState().user_lists.following.get(id)!.next; - if (url === null) { - return null; - } + if (next === null) return; dispatch(expandFollowingRequest(id)); - return api(getState)(url) - .then(response => { - const next = getNextLink(response); - - dispatch(importFetchedAccounts(response.json)); - dispatch(expandFollowingSuccess(id, response.json, next || null)); - dispatch(fetchRelationships(response.json.map((item: APIEntity) => item.id))); - }) - .catch(error => { - dispatch(expandFollowingFail(id, error)); - }); + next().then(response => { + dispatch(importFetchedAccounts(response.items)); + dispatch(expandFollowingSuccess(id, response.items, response.next)); + dispatch(fetchRelationships(response.items.map((item: APIEntity) => item.id))); + }).catch(error => { + dispatch(expandFollowingFail(id, error)); + }); }; const expandFollowingRequest = (id: string) => ({ @@ -587,7 +500,7 @@ const expandFollowingRequest = (id: string) => ({ id, }); -const expandFollowingSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({ +const expandFollowingSuccess = (id: string, accounts: APIEntity[], next: (() => Promise>) | null) => ({ type: FOLLOWING_EXPAND_SUCCESS, id, accounts, @@ -613,10 +526,10 @@ const fetchRelationships = (accountIds: string[]) => dispatch(fetchRelationshipsRequest(newAccountIds)); - return api(getState)(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`) + return getClient(getState()).accounts.getRelationships(newAccountIds) .then(response => { - dispatch(importEntities(response.json, Entities.RELATIONSHIPS)); - dispatch(fetchRelationshipsSuccess(response.json)); + dispatch(importEntities(response, Entities.RELATIONSHIPS)); + dispatch(fetchRelationshipsSuccess(response)); }) .catch(error => dispatch(fetchRelationshipsFail(error))); }; @@ -645,11 +558,10 @@ const fetchFollowRequests = () => dispatch(fetchFollowRequestsRequest()); - return api(getState)('/api/v1/follow_requests') + return getClient(getState()).accounts.getFollowRequests() .then(response => { - const next = getNextLink(response); - dispatch(importFetchedAccounts(response.json)); - dispatch(fetchFollowRequestsSuccess(response.json, next || null)); + dispatch(importFetchedAccounts(response.items)); + dispatch(fetchFollowRequestsSuccess(response.items, response.next)); }) .catch(error => dispatch(fetchFollowRequestsFail(error))); }; @@ -658,7 +570,7 @@ const fetchFollowRequestsRequest = () => ({ type: FOLLOW_REQUESTS_FETCH_REQUEST, }); -const fetchFollowRequestsSuccess = (accounts: APIEntity[], next: string | null) => ({ +const fetchFollowRequestsSuccess = (accounts: APIEntity[], next: (() => Promise>) | null) => ({ type: FOLLOW_REQUESTS_FETCH_SUCCESS, accounts, next, @@ -673,28 +585,23 @@ const expandFollowRequests = () => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; - const url = getState().user_lists.follow_requests.next; + const next = getState().user_lists.follow_requests.next; - if (url === null) { - return null; - } + if (next === null) return null; dispatch(expandFollowRequestsRequest()); - return api(getState)(url) - .then(response => { - const next = getNextLink(response); - dispatch(importFetchedAccounts(response.json)); - dispatch(expandFollowRequestsSuccess(response.json, next || null)); - }) - .catch(error => dispatch(expandFollowRequestsFail(error))); + return next().then(response => { + dispatch(importFetchedAccounts(response.items)); + dispatch(expandFollowRequestsSuccess(response.items, response.next)); + }).catch(error => dispatch(expandFollowRequestsFail(error))); }; const expandFollowRequestsRequest = () => ({ type: FOLLOW_REQUESTS_EXPAND_REQUEST, }); -const expandFollowRequestsSuccess = (accounts: APIEntity[], next: string | null) => ({ +const expandFollowRequestsSuccess = (accounts: APIEntity[], next: (() => Promise>) | null) => ({ type: FOLLOW_REQUESTS_EXPAND_SUCCESS, accounts, next, @@ -711,7 +618,7 @@ const authorizeFollowRequest = (id: string) => dispatch(authorizeFollowRequestRequest(id)); - return api(getState)(`/api/v1/follow_requests/${id}/authorize`, { method: 'POST' }) + return getClient(getState()).accounts.acceptFollowRequest(id) .then(() => dispatch(authorizeFollowRequestSuccess(id))) .catch(error => dispatch(authorizeFollowRequestFail(id, error))); }; @@ -738,7 +645,7 @@ const rejectFollowRequest = (id: string) => dispatch(rejectFollowRequestRequest(id)); - api(getState)(`/api/v1/follow_requests/${id}/reject`, { method: 'POST' }) + return getClient(getState()).accounts.rejectFollowRequest(id) .then(() => dispatch(rejectFollowRequestSuccess(id))) .catch(error => dispatch(rejectFollowRequestFail(id, error))); }; @@ -765,8 +672,8 @@ const pinAccount = (id: string) => dispatch(pinAccountRequest(id)); - return api(getState)(`/api/v1/accounts/${id}/pin`, { method: 'POST' }).then(response => { - dispatch(pinAccountSuccess(response.json)); + return getClient(getState()).accounts.pinAccount(id).then(response => { + dispatch(pinAccountSuccess(response)); }).catch(error => { dispatch(pinAccountFail(error)); }); @@ -778,8 +685,8 @@ const unpinAccount = (id: string) => dispatch(unpinAccountRequest(id)); - return api(getState)(`/api/v1/accounts/${id}/unpin`, { method: 'POST' }).then(response => { - dispatch(unpinAccountSuccess(response.json)); + return getClient(getState()).accounts.unpinAccount(id).then(response => { + dispatch(unpinAccountSuccess(response)); }).catch(error => { dispatch(unpinAccountFail(error)); }); @@ -788,9 +695,9 @@ const unpinAccount = (id: string) => const updateNotificationSettings = (params: Record) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: NOTIFICATION_SETTINGS_REQUEST, params }); - return api(getState)('/api/pleroma/notification_settings', { + return getClient(getState).request('/api/pleroma/notification_settings', { method: 'PUT', - body: JSON.stringify(params), + body: params, }).then(({ json: data }) => { dispatch({ type: NOTIFICATION_SETTINGS_SUCCESS, params, data }); }).catch(error => { @@ -833,7 +740,7 @@ const fetchPinnedAccounts = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchPinnedAccountsRequest(id)); - api(getState)(`/api/v1/pleroma/accounts/${id}/endorsements`).then(response => { + return getClient(getState).request(`/api/v1/pleroma/accounts/${id}/endorsements`).then(response => { dispatch(importFetchedAccounts(response.json)); dispatch(fetchPinnedAccountsSuccess(id, response.json, null)); }).catch(error => { @@ -859,10 +766,10 @@ const fetchPinnedAccountsFail = (id: string, error: unknown) => ({ error, }); -const accountSearch = (params: Record, signal?: AbortSignal) => +const accountSearch = (q: string, signal?: AbortSignal) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ type: ACCOUNT_SEARCH_REQUEST, params }); - return api(getState)('/api/v1/accounts/search', { params, signal }).then(({ json: accounts }) => { + dispatch({ type: ACCOUNT_SEARCH_REQUEST, params: { q } }); + return getClient(getState()).accounts.searchAccounts(q, { resolve: false, limit: 4, following: true }).then((accounts) => { dispatch(importFetchedAccounts(accounts)); dispatch({ type: ACCOUNT_SEARCH_SUCCESS, accounts }); return accounts; @@ -875,10 +782,8 @@ const accountSearch = (params: Record, signal?: AbortSignal) => const accountLookup = (acct: string, signal?: AbortSignal) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: ACCOUNT_LOOKUP_REQUEST, acct }); - return api(getState)('/api/v1/accounts/lookup', { - body: JSON.stringify({ acct }), - signal, - }).then(({ json: account }) => { + // TODO signal + return getClient(getState()).accounts.lookupAccount(acct).then((account) => { if (account && account.id) dispatch(importFetchedAccount(account)); dispatch({ type: ACCOUNT_LOOKUP_SUCCESS, account }); return account; @@ -896,7 +801,7 @@ const fetchBirthdayReminders = (month: number, day: number) => dispatch({ type: BIRTHDAY_REMINDERS_FETCH_REQUEST, day, month, id: me }); - return api(getState)('/api/v1/pleroma/birthdays', { params: { day, month } }).then(response => { + return getClient(getState).request('/api/v1/pleroma/birthdays', { params: { day, month } }).then(response => { dispatch(importFetchedAccounts(response.json)); dispatch({ type: BIRTHDAY_REMINDERS_FETCH_SUCCESS, @@ -929,12 +834,6 @@ export { ACCOUNT_UNMUTE_REQUEST, ACCOUNT_UNMUTE_SUCCESS, ACCOUNT_UNMUTE_FAIL, - ACCOUNT_SUBSCRIBE_REQUEST, - ACCOUNT_SUBSCRIBE_SUCCESS, - ACCOUNT_SUBSCRIBE_FAIL, - ACCOUNT_UNSUBSCRIBE_REQUEST, - ACCOUNT_UNSUBSCRIBE_SUCCESS, - ACCOUNT_UNSUBSCRIBE_FAIL, ACCOUNT_PIN_REQUEST, ACCOUNT_PIN_SUCCESS, ACCOUNT_PIN_FAIL, @@ -1008,14 +907,6 @@ export { unmuteAccountRequest, unmuteAccountSuccess, unmuteAccountFail, - subscribeAccount, - unsubscribeAccount, - subscribeAccountRequest, - subscribeAccountSuccess, - subscribeAccountFail, - unsubscribeAccountRequest, - unsubscribeAccountSuccess, - unsubscribeAccountFail, removeFromFollowers, removeFromFollowersRequest, removeFromFollowersSuccess, diff --git a/src/actions/admin.ts b/src/actions/admin.ts index 8017daa88..6f4901f48 100644 --- a/src/actions/admin.ts +++ b/src/actions/admin.ts @@ -5,7 +5,7 @@ import { accountIdsToAccts } from 'soapbox/selectors'; import { filterBadges, getTagDiff } from 'soapbox/utils/badges'; import { getFeatures } from 'soapbox/utils/features'; -import api, { getNextLink } from '../api'; +import { getClient, getNextLink } from '../api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -79,7 +79,7 @@ const ADMIN_USER_INDEX_QUERY_SET = 'ADMIN_USER_INDEX_QUERY_SET'; const fetchConfig = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: ADMIN_CONFIG_FETCH_REQUEST }); - return api(getState)('/api/v1/pleroma/admin/config') + return getClient(getState).request('/api/v1/pleroma/admin/config') .then(({ json: data }) => { dispatch({ type: ADMIN_CONFIG_FETCH_SUCCESS, configs: data.configs, needsReboot: data.need_reboot }); }).catch(error => { @@ -90,7 +90,7 @@ const fetchConfig = () => const updateConfig = (configs: Record[]) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: ADMIN_CONFIG_UPDATE_REQUEST, configs }); - return api(getState)('/api/v1/pleroma/admin/config', { method: 'POST', body: JSON.stringify({ configs }) }) + return getClient(getState).request('/api/v1/pleroma/admin/config', { method: 'POST', body: { configs } }) .then(({ json: data }) => { dispatch({ type: ADMIN_CONFIG_UPDATE_SUCCESS, configs: data.configs, needsReboot: data.need_reboot }); }).catch(error => { @@ -113,7 +113,7 @@ const updateSoapboxConfig = (data: Record) => const fetchMastodonReports = (params: Record) => (dispatch: AppDispatch, getState: () => RootState) => - api(getState)('/api/v1/admin/reports', { params }) + getClient(getState).request('/api/v1/admin/reports', { params }) .then(({ json: reports }) => { reports.forEach((report: APIEntity) => { dispatch(importFetchedAccount(report.account?.account)); @@ -127,7 +127,7 @@ const fetchMastodonReports = (params: Record) => const fetchPleromaReports = (params: Record) => (dispatch: AppDispatch, getState: () => RootState) => - api(getState)('/api/v1/pleroma/admin/reports', { params }) + getClient(getState).request('/api/v1/pleroma/admin/reports', { params }) .then(({ json: { reports } }) => { reports.forEach((report: APIEntity) => { dispatch(importFetchedAccount(report.account)); @@ -160,9 +160,11 @@ const fetchReports = (params: Record = {}) => }; const patchMastodonReports = (reports: { id: string; state: string }[]) => - (dispatch: AppDispatch, getState: () => RootState) => - Promise.all(reports.map(({ id, state }) => - api(getState)(`/api/v1/admin/reports/${id}/${state === 'resolved' ? 'reopen' : 'resolve'}`, { + (dispatch: AppDispatch, getState: () => RootState) => { + const client = getClient(getState); + + return Promise.all(reports.map(({ id, state }) => + client.request(`/api/v1/admin/reports/${id}/${state === 'resolved' ? 'reopen' : 'resolve'}`, { method: 'POST', }) .then(() => { @@ -171,12 +173,13 @@ const patchMastodonReports = (reports: { id: string; state: string }[]) => dispatch({ type: ADMIN_REPORTS_PATCH_FAIL, error, reports }); }), )); + }; const patchPleromaReports = (reports: { id: string; state: string }[]) => (dispatch: AppDispatch, getState: () => RootState) => - api(getState)('/api/v1/pleroma/admin/reports', { + getClient(getState).request('/api/v1/pleroma/admin/reports', { method: 'PATCH', - body: JSON.stringify(reports), + body: reports, }).then(() => { dispatch({ type: ADMIN_REPORTS_PATCH_SUCCESS, reports }); }).catch(error => { @@ -214,7 +217,7 @@ const fetchMastodonUsers = (filters: string[], page: number, query: string | nul if (filters.includes('active')) params.active = true; if (filters.includes('need_approval')) params.pending = true; - return api(getState)(next || '/api/v1/admin/accounts', { params }) + return getClient(getState).request(next || '/api/v1/admin/accounts', { params }) .then((response) => { const accounts = response.json; const next = getNextLink(response); @@ -237,7 +240,7 @@ const fetchPleromaUsers = (filters: string[], page: number, query?: string | nul const params: Record = { filters: filters.join(), page, page_size: pageSize }; if (query) params.query = query; - return api(getState)('/api/v1/pleroma/admin/users', { params }) + return getClient(getState).request('/api/v1/pleroma/admin/users', { params }) .then(({ json: { users, count, page_size: pageSize } }) => { dispatch(fetchRelationships(users.map((user: APIEntity) => user.id))); dispatch({ type: ADMIN_USERS_FETCH_SUCCESS, users, count, pageSize, filters, page }); @@ -264,14 +267,13 @@ const fetchUsers = (filters: string[] = [], page = 1, query?: string | null, pag }; const deactivateMastodonUsers = (accountIds: string[], reportId?: string) => - (dispatch: AppDispatch, getState: () => RootState) => - Promise.all(accountIds.map(accountId => { - api(getState)(`/api/v1/admin/accounts/${accountId}/action`, { + (dispatch: AppDispatch, getState: () => RootState) => { + const client = getClient(getState); + + return Promise.all(accountIds.map(accountId => { + client.request(`/api/v1/admin/accounts/${accountId}/action`, { method: 'POST', - body: JSON.stringify({ - type: 'disable', - report_id: reportId, - }), + body: { type: 'disable', report_id: reportId }, }) .then(() => { dispatch({ type: ADMIN_USERS_DEACTIVATE_SUCCESS, accountIds: [accountId] }); @@ -279,13 +281,14 @@ const deactivateMastodonUsers = (accountIds: string[], reportId?: string) => dispatch({ type: ADMIN_USERS_DEACTIVATE_FAIL, error, accountIds: [accountId] }); }); })); + }; const deactivatePleromaUsers = (accountIds: string[]) => (dispatch: AppDispatch, getState: () => RootState) => { const nicknames = accountIdsToAccts(getState(), accountIds); - return api(getState)('/api/v1/pleroma/admin/users/deactivate', { + return getClient(getState).request('/api/v1/pleroma/admin/users/deactivate', { method: 'PATCH', - body: JSON.stringify(nicknames), + body: nicknames, }) .then(({ json: { users } }) => { dispatch({ type: ADMIN_USERS_DEACTIVATE_SUCCESS, users, accountIds }); @@ -314,8 +317,8 @@ const deleteUsers = (accountIds: string[]) => (dispatch: AppDispatch, getState: () => RootState) => { const nicknames = accountIdsToAccts(getState(), accountIds); dispatch({ type: ADMIN_USERS_DELETE_REQUEST, accountIds }); - return api(getState)('/api/v1/pleroma/admin/users', { - method: 'DELETE', body: JSON.stringify({ nicknames }), + return getClient(getState).request('/api/v1/pleroma/admin/users', { + method: 'DELETE', body: { nicknames }, }).then(({ json: nicknames }) => { dispatch({ type: ADMIN_USERS_DELETE_SUCCESS, nicknames, accountIds }); }).catch(error => { @@ -324,22 +327,23 @@ const deleteUsers = (accountIds: string[]) => }; const approveMastodonUsers = (accountIds: string[]) => - (dispatch: AppDispatch, getState: () => RootState) => + (dispatch: AppDispatch, getState: () => RootState) => { + const client = getClient(getState); Promise.all(accountIds.map(accountId => { - api(getState)(`/api/v1/admin/accounts/${accountId}/approve`, { method: 'POST' }) + client.request(`/api/v1/admin/accounts/${accountId}/approve`, { method: 'POST' }) .then(({ json: user }) => { dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, users: [user], accountIds: [accountId] }); }).catch(error => { dispatch({ type: ADMIN_USERS_APPROVE_FAIL, error, accountIds: [accountId] }); }); })); + }; const approvePleromaUsers = (accountIds: string[]) => (dispatch: AppDispatch, getState: () => RootState) => { const nicknames = accountIdsToAccts(getState(), accountIds); - return api(getState)('/api/v1/pleroma/admin/users/approve', { - method: 'POST', - body: JSON.stringify({ nicknames }), + return getClient(getState).request('/api/v1/pleroma/admin/users/approve', { + method: 'POST', body: { nicknames }, }).then(({ json: { users } }) => { dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, users, accountIds }); }).catch(error => { @@ -366,7 +370,7 @@ const approveUsers = (accountIds: string[]) => const deleteStatus = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: ADMIN_STATUS_DELETE_REQUEST, id }); - return api(getState)(`/api/v1/pleroma/admin/statuses/${id}`, { method: 'DELETE' }) + return getClient(getState).request(`/api/v1/pleroma/admin/statuses/${id}`, { method: 'DELETE' }) .then(() => { dispatch({ type: ADMIN_STATUS_DELETE_SUCCESS, id }); }).catch(error => { @@ -377,8 +381,8 @@ const deleteStatus = (id: string) => const toggleStatusSensitivity = (id: string, sensitive: boolean) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST, id }); - return api(getState)(`/api/v1/pleroma/admin/statuses/${id}`, { - method: 'PUT', body: JSON.stringify({ sensitive: !sensitive }), + return getClient(getState).request(`/api/v1/pleroma/admin/statuses/${id}`, { + method: 'PUT', body: { sensitive: !sensitive }, }).then(() => { dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS, id }); }).catch(error => { @@ -390,9 +394,9 @@ const tagUsers = (accountIds: string[], tags: string[]) => (dispatch: AppDispatch, getState: () => RootState) => { const nicknames = accountIdsToAccts(getState(), accountIds); dispatch({ type: ADMIN_USERS_TAG_REQUEST, accountIds, tags }); - return api(getState)('/api/v1/pleroma/admin/users/tag', { + return getClient(getState).request('/api/v1/pleroma/admin/users/tag', { method: 'PUT', - body: JSON.stringify({ nicknames, tags }), + body: { nicknames, tags }, }).then(() => { dispatch({ type: ADMIN_USERS_TAG_SUCCESS, accountIds, tags }); }).catch(error => { @@ -410,9 +414,9 @@ const untagUsers = (accountIds: string[], tags: string[]) => } dispatch({ type: ADMIN_USERS_UNTAG_REQUEST, accountIds, tags }); - return api(getState)('/api/v1/pleroma/admin/users/tag', { + return getClient(getState).request('/api/v1/pleroma/admin/users/tag', { method: 'DELETE', - body: JSON.stringify({ nicknames, tags }), + body: { nicknames, tags }, }) .then(() => { dispatch({ type: ADMIN_USERS_UNTAG_SUCCESS, accountIds, tags }); @@ -443,11 +447,9 @@ const addPermission = (accountIds: string[], permissionGroup: string) => (dispatch: AppDispatch, getState: () => RootState) => { const nicknames = accountIdsToAccts(getState(), accountIds); dispatch({ type: ADMIN_ADD_PERMISSION_GROUP_REQUEST, accountIds, permissionGroup }); - return api(getState)(`/api/v1/pleroma/admin/users/permission_group/${permissionGroup}`, { - method: 'POST', - body: JSON.stringify({ nicknames }), - }) - .then(({ json: data }) => { + return getClient(getState).request(`/api/v1/pleroma/admin/users/permission_group/${permissionGroup}`, { + method: 'POST', body: { nicknames }, + }).then(({ json: data }) => { dispatch({ type: ADMIN_ADD_PERMISSION_GROUP_SUCCESS, accountIds, permissionGroup, data }); }).catch(error => { dispatch({ type: ADMIN_ADD_PERMISSION_GROUP_FAIL, error, accountIds, permissionGroup }); @@ -458,11 +460,9 @@ const removePermission = (accountIds: string[], permissionGroup: string) => (dispatch: AppDispatch, getState: () => RootState) => { const nicknames = accountIdsToAccts(getState(), accountIds); dispatch({ type: ADMIN_REMOVE_PERMISSION_GROUP_REQUEST, accountIds, permissionGroup }); - return api(getState)(`/api/v1/pleroma/admin/users/permission_group/${permissionGroup}`, { - method: 'DELETE', - body: JSON.stringify({ nicknames }), - }) - .then(({ json: data }) => { + return getClient(getState).request(`/api/v1/pleroma/admin/users/permission_group/${permissionGroup}`, { + method: 'DELETE', body: { nicknames }, + }).then(({ json: data }) => { dispatch({ type: ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS, accountIds, permissionGroup, data }); }).catch(error => { dispatch({ type: ADMIN_REMOVE_PERMISSION_GROUP_FAIL, error, accountIds, permissionGroup }); @@ -546,7 +546,6 @@ const expandUserIndex = () => }); }; - export { ADMIN_CONFIG_FETCH_REQUEST, ADMIN_CONFIG_FETCH_SUCCESS, diff --git a/src/actions/aliases.ts b/src/actions/aliases.ts index e03c900b0..2bf13863e 100644 --- a/src/actions/aliases.ts +++ b/src/actions/aliases.ts @@ -4,7 +4,7 @@ import toast from 'soapbox/toast'; import { isLoggedIn } from 'soapbox/utils/auth'; import { getFeatures } from 'soapbox/utils/features'; -import api from '../api'; +import { getClient } from '../api'; import { importFetchedAccounts } from './importer'; import { patchMeSuccess } from './me'; @@ -44,7 +44,7 @@ const fetchAliases = (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchAliasesRequest()); - api(getState)('/api/pleroma/aliases') + return getClient(getState).request('/api/pleroma/aliases') .then(response => { dispatch(fetchAliasesSuccess(response.json.aliases)); }) @@ -69,16 +69,11 @@ const fetchAliasesSuggestions = (q: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - const params = { - q, - resolve: true, - limit: 4, - }; - - api(getState)('/api/v1/accounts/search', { params }).then(({ json: data }) => { - dispatch(importFetchedAccounts(data)); - dispatch(fetchAliasesSuggestionsReady(q, data)); - }).catch(error => toast.showAlertForError(error)); + return getClient(getState()).accounts.searchAccounts(q, { resolve: true, limit: 4 }) + .then((data) => { + dispatch(importFetchedAccounts(data)); + dispatch(fetchAliasesSuggestionsReady(q, data)); + }).catch(error => toast.showAlertForError(error)); }; const fetchAliasesSuggestionsReady = (query: string, accounts: unknown[]) => ({ @@ -110,29 +105,21 @@ const addToAliases = (account: Account) => dispatch(addToAliasesRequest()); - api(getState)('/api/v1/accounts/update_credentials', { - method: 'PATCH', - body: JSON.stringify({ also_known_as: [...alsoKnownAs, account.pleroma?.ap_id] }), - }) + return getClient(getState()).accounts.updateCredentials({ also_known_as: [...alsoKnownAs, account.pleroma?.ap_id] }) .then((response => { toast.success(messages.createSuccess); dispatch(addToAliasesSuccess); - dispatch(patchMeSuccess(response.json)); + dispatch(patchMeSuccess(response)); })) .catch(err => dispatch(addToAliasesFail(err))); - return; } dispatch(addToAliasesRequest()); - api(getState)('/api/pleroma/aliases', { - method: 'PUT', - body: JSON.stringify({ - alias: account.acct, - }), - }) - .then(() => { + return getClient(getState).request('/api/pleroma/aliases', { + method: 'PUT', body: { alias: account.acct }, + }).then(() => { toast.success(messages.createSuccess); dispatch(addToAliasesSuccess); dispatch(fetchAliases); @@ -167,16 +154,12 @@ const removeFromAliases = (account: string) => dispatch(removeFromAliasesRequest()); - api(getState)('/api/v1/accounts/update_credentials', { - method: 'PATCH', - body: JSON.stringify({ - also_known_as: alsoKnownAs.filter((id: string) => id !== account), - }), - }) - .then(response => { + return getClient(getState()).accounts.updateCredentials({ + also_known_as: alsoKnownAs.filter((id: string) => id !== account), + }).then(response => { toast.success(messages.removeSuccess); dispatch(removeFromAliasesSuccess); - dispatch(patchMeSuccess(response.json)); + dispatch(patchMeSuccess(response)); }) .catch(err => dispatch(removeFromAliasesFail(err))); @@ -185,13 +168,9 @@ const removeFromAliases = (account: string) => dispatch(addToAliasesRequest()); - api(getState)('/api/pleroma/aliases', { - method: 'DELETE', - body: JSON.stringify({ - alias: account, - }), - }) - .then(() => { + return getClient(getState).request('/api/pleroma/aliases', { + method: 'DELETE', body: { alias: account }, + }).then(() => { toast.success(messages.removeSuccess); dispatch(removeFromAliasesSuccess); dispatch(fetchAliases); diff --git a/src/actions/apps.ts b/src/actions/apps.ts index 7fd8146b1..0ebe76cbf 100644 --- a/src/actions/apps.ts +++ b/src/actions/apps.ts @@ -6,7 +6,9 @@ * @see module:soapbox/actions/auth */ -import { getFetch } from '../api'; +import { PlApiClient } from 'pl-api'; + +import * as BuildConfig from 'soapbox/build-config'; import type { AnyAction } from 'redux'; @@ -14,15 +16,12 @@ const APP_CREATE_REQUEST = 'APP_CREATE_REQUEST'; const APP_CREATE_SUCCESS = 'APP_CREATE_SUCCESS'; const APP_CREATE_FAIL = 'APP_CREATE_FAIL'; -const APP_VERIFY_CREDENTIALS_REQUEST = 'APP_VERIFY_CREDENTIALS_REQUEST'; -const APP_VERIFY_CREDENTIALS_SUCCESS = 'APP_VERIFY_CREDENTIALS_SUCCESS'; -const APP_VERIFY_CREDENTIALS_FAIL = 'APP_VERIFY_CREDENTIALS_FAIL'; - const createApp = (params?: Record, baseURL?: string) => (dispatch: React.Dispatch) => { dispatch({ type: APP_CREATE_REQUEST, params }); - return getFetch(null, baseURL)('/api/v1/apps', { method: 'POST', body: JSON.stringify(params) }).then(({ json: app }) => { + const client = new PlApiClient(baseURL || BuildConfig.BACKEND_URL || '', undefined, { fetchInstance: false }); + return client.apps.createApplication(params).then((app) => { dispatch({ type: APP_CREATE_SUCCESS, params, app }); return app as Record; }).catch(error => { @@ -31,25 +30,9 @@ const createApp = (params?: Record, baseURL?: string) => }); }; -const verifyAppCredentials = (token: string) => - (dispatch: React.Dispatch) => { - dispatch({ type: APP_VERIFY_CREDENTIALS_REQUEST, token }); - return getFetch(token)('/api/v1/apps/verify_credentials').then(({ json: app }) => { - dispatch({ type: APP_VERIFY_CREDENTIALS_SUCCESS, token, app }); - return app; - }).catch(error => { - dispatch({ type: APP_VERIFY_CREDENTIALS_FAIL, token, error }); - throw error; - }); - }; - export { APP_CREATE_REQUEST, APP_CREATE_SUCCESS, APP_CREATE_FAIL, - APP_VERIFY_CREDENTIALS_REQUEST, - APP_VERIFY_CREDENTIALS_SUCCESS, - APP_VERIFY_CREDENTIALS_FAIL, createApp, - verifyAppCredentials, }; diff --git a/src/actions/auth.ts b/src/actions/auth.ts index 7d6856451..5585933ac 100644 --- a/src/actions/auth.ts +++ b/src/actions/auth.ts @@ -5,8 +5,8 @@ * @see module:soapbox/actions/apps * @see module:soapbox/actions/oauth * @see module:soapbox/actions/security - */ - +*/ +import { PlApiClient } from 'pl-api'; import { defineMessages } from 'react-intl'; import { createAccount } from 'soapbox/actions/accounts'; @@ -14,6 +14,7 @@ import { createApp } from 'soapbox/actions/apps'; import { fetchMeSuccess, fetchMeFail } from 'soapbox/actions/me'; import { obtainOAuthToken, revokeOAuthToken } from 'soapbox/actions/oauth'; import { startOnboarding } from 'soapbox/actions/onboarding'; +import * as BuildConfig from 'soapbox/build-config'; import { custom } from 'soapbox/custom'; import { queryClient } from 'soapbox/queries/client'; import { selectAccount } from 'soapbox/selectors'; @@ -26,7 +27,7 @@ import { normalizeUsername } from 'soapbox/utils/input'; import { getScopes } from 'soapbox/utils/scopes'; import { isStandalone } from 'soapbox/utils/state'; -import api, { type PlfeResponse, getFetch } from '../api'; +import { type PlfeResponse, getClient } from '../api'; import { importFetchedAccount } from './importer'; @@ -124,10 +125,13 @@ const createUserToken = (username: string, password: string) => const otpVerify = (code: string, mfa_token: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const app = getState().auth.app; - return api(getState, 'app')('/oauth/mfa/challenge', { + const state = getState(); + const app = state.auth.app; + const baseUrl = parseBaseURL(state.me) || BuildConfig.BACKEND_URL; + const client = new PlApiClient(baseUrl, app.access_token!, { fetchInstance: false }); + return client.request('/oauth/mfa/challenge', { method: 'POST', - body: JSON.stringify({ + body: { client_id: app.client_id, client_secret: app.client_secret, mfa_token: mfa_token, @@ -135,17 +139,19 @@ const otpVerify = (code: string, mfa_token: string) => challenge_type: 'totp', redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', scope: getScopes(getState()), - }), + }, }).then(({ json: token }) => dispatch(authLoggedIn(token))); }; -const verifyCredentials = (token: string, accountUrl?: string) => { - const baseURL = parseBaseURL(accountUrl); +const verifyCredentials = (token: string, accountUrl?: string) => + (dispatch: AppDispatch, getState: () => RootState) => { + const baseURL = parseBaseURL(accountUrl); - return (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: VERIFY_CREDENTIALS_REQUEST, token }); - return getFetch(token, baseURL)('/api/v1/accounts/verify_credentials').then(({ json: account }) => { + const client = new PlApiClient(baseURL, token, { fetchInstance: false }); + + return client.accounts.verifyCredentials().then((account) => { dispatch(importFetchedAccount(account)); dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account }); if (account.id === getState().me) dispatch(fetchMeSuccess(account)); @@ -165,7 +171,6 @@ const verifyCredentials = (token: string, accountUrl?: string) => { } }); }; -}; const rememberAuthAccount = (accountUrl: string) => (dispatch: AppDispatch, getState: () => RootState) => { @@ -201,7 +206,7 @@ const logIn = (username: string, password: string) => }); const deleteSession = () => - (dispatch: AppDispatch, getState: () => RootState) => api(getState)('/api/sign_out', { method: 'DELETE' }); + (dispatch: AppDispatch, getState: () => RootState) => getClient(getState).request('/api/sign_out', { method: 'DELETE' }); const logOut = () => (dispatch: AppDispatch, getState: () => RootState) => { @@ -267,7 +272,7 @@ const register = (params: Record) => }; const fetchCaptcha = () => - (_dispatch: AppDispatch, getState: () => RootState) => api(getState)('/api/pleroma/captcha'); + (_dispatch: AppDispatch, getState: () => RootState) => getClient(getState).request('/api/pleroma/captcha'); const authLoggedIn = (token: Record) => (dispatch: AppDispatch) => { diff --git a/src/actions/backups.ts b/src/actions/backups.ts index 0cf5ace85..b935b4b45 100644 --- a/src/actions/backups.ts +++ b/src/actions/backups.ts @@ -1,4 +1,4 @@ -import api from '../api'; +import { getClient } from '../api'; import type { AppDispatch, RootState } from 'soapbox/store'; @@ -13,7 +13,7 @@ const BACKUPS_CREATE_FAIL = 'BACKUPS_CREATE_FAIL'; const fetchBackups = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: BACKUPS_FETCH_REQUEST }); - return api(getState)('/api/v1/pleroma/backups').then(({ json: backups }) => + return getClient(getState).request('/api/v1/pleroma/backups').then(({ json: backups }) => dispatch({ type: BACKUPS_FETCH_SUCCESS, backups }), ).catch(error => { dispatch({ type: BACKUPS_FETCH_FAIL, error }); @@ -23,7 +23,7 @@ const fetchBackups = () => const createBackup = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: BACKUPS_CREATE_REQUEST }); - return api(getState)('/api/v1/pleroma/backups').then(({ json: backups }) => + return getClient(getState).request('/api/v1/pleroma/backups').then(({ json: backups }) => dispatch({ type: BACKUPS_CREATE_SUCCESS, backups }), ).catch(error => { dispatch({ type: BACKUPS_CREATE_FAIL, error }); diff --git a/src/actions/bookmarks.ts b/src/actions/bookmarks.ts index e418d295d..92a9de498 100644 --- a/src/actions/bookmarks.ts +++ b/src/actions/bookmarks.ts @@ -1,9 +1,10 @@ -import api, { getNextLink } from '../api'; +import { getClient, getNextLink } from '../api'; import { importFetchedStatuses } from './importer'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; +import type { PaginatedResponse, Status } from 'pl-api'; const BOOKMARKED_STATUSES_FETCH_REQUEST = 'BOOKMARKED_STATUSES_FETCH_REQUEST'; const BOOKMARKED_STATUSES_FETCH_SUCCESS = 'BOOKMARKED_STATUSES_FETCH_SUCCESS'; @@ -23,10 +24,9 @@ const fetchBookmarkedStatuses = (folderId?: string) => dispatch(fetchBookmarkedStatusesRequest(folderId)); - return api(getState)(`/api/v1/bookmarks${folderId ? `?folder_id=${folderId}` : ''}`).then(response => { - const next = getNextLink(response); - dispatch(importFetchedStatuses(response.json)); - return dispatch(fetchBookmarkedStatusesSuccess(response.json, next || null, folderId)); + return getClient(getState()).accounts.getBookmarks({ folder_id: folderId }).then(response => { + dispatch(importFetchedStatuses(response.items)); + return dispatch(fetchBookmarkedStatusesSuccess(response.items, response.next, folderId)); }).catch(error => { dispatch(fetchBookmarkedStatusesFail(error, folderId)); }); @@ -37,7 +37,7 @@ const fetchBookmarkedStatusesRequest = (folderId?: string) => ({ folderId, }); -const fetchBookmarkedStatusesSuccess = (statuses: APIEntity[], next: string | null, folderId?: string) => ({ +const fetchBookmarkedStatusesSuccess = (statuses: APIEntity[], next: (() => Promise>) | null, folderId?: string) => ({ type: BOOKMARKED_STATUSES_FETCH_SUCCESS, statuses, next, @@ -53,18 +53,17 @@ const fetchBookmarkedStatusesFail = (error: unknown, folderId?: string) => ({ const expandBookmarkedStatuses = (folderId?: string) => (dispatch: AppDispatch, getState: () => RootState) => { const list = folderId ? `bookmarks:${folderId}` : 'bookmarks'; - const url = getState().status_lists.get(list)?.next || null; + const next = getState().status_lists.get(list)?.next || null; - if (url === null || getState().status_lists.get(list)?.isLoading) { + if (next === null || getState().status_lists.get(list)?.isLoading) { return dispatch(noOp); } dispatch(expandBookmarkedStatusesRequest(folderId)); - return api(getState)(url).then(response => { - const next = getNextLink(response); - dispatch(importFetchedStatuses(response.json)); - return dispatch(expandBookmarkedStatusesSuccess(response.json, next || null, folderId)); + return next().then(response => { + dispatch(importFetchedStatuses(response.items)); + return dispatch(expandBookmarkedStatusesSuccess(response.items, response.next, folderId)); }).catch(error => { dispatch(expandBookmarkedStatusesFail(error, folderId)); }); @@ -75,7 +74,7 @@ const expandBookmarkedStatusesRequest = (folderId?: string) => ({ folderId, }); -const expandBookmarkedStatusesSuccess = (statuses: APIEntity[], next: string | null, folderId?: string) => ({ +const expandBookmarkedStatusesSuccess = (statuses: APIEntity[], next: (() => Promise>) | null, folderId?: string) => ({ type: BOOKMARKED_STATUSES_EXPAND_SUCCESS, statuses, next, diff --git a/src/actions/circle.ts b/src/actions/circle.ts index a43a9fea9..c9106b8d4 100644 --- a/src/actions/circle.ts +++ b/src/actions/circle.ts @@ -1,9 +1,9 @@ // Loosely adapted from twitter-interaction-circles, licensed under MIT License // https://github.com/duiker101/twitter-interaction-circles -import api, { getNextLink } from 'soapbox/api'; +import { getClient } from 'soapbox/api'; +import type { PaginatedResponse, Status } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; interface Interaction { acct: string; @@ -21,8 +21,8 @@ const processCircle = (setProgress: (progress: { async (dispatch: AppDispatch, getState: () => RootState) => { setProgress({ state: 'pending', progress: 0 }); - const fetch = api(getState); - const me = getState().me; + const client = getClient(getState()); + const me = getState().me as string; const interactions: Record = {}; @@ -36,11 +36,10 @@ const processCircle = (setProgress: (progress: { }; }; - const fetchStatuses = async (url = `/api/v1/accounts/${me}/statuses?with_muted=true&limit=40`) => { - const response = await fetch(url); - const next = getNextLink(response); + const fetchStatuses = async (next?: () => Promise>) => { + const response = await (next?.() || client.accounts.getAccountStatuses(me, { limit: 40 })); - response.json.forEach((status) => { + response.items.forEach((status) => { if (status.reblog) { if (status.reblog.account.id === me) return; @@ -61,14 +60,13 @@ const processCircle = (setProgress: (progress: { } }); - return next; + return response.next; }; - const fetchFavourites = async (url = '/api/v1/favourites?limit=40') => { - const response = await fetch(url); - const next = getNextLink(response); + const fetchFavourites = async (next?: () => Promise>) => { // limit 40 + const response = await (next?.() || client.accounts.getFavourites({ limit: 40 })); - response.json.forEach((status) => { + response.items.forEach((status) => { if (status.account.id === me) return; initInteraction(status.account.id); @@ -80,19 +78,19 @@ const processCircle = (setProgress: (progress: { interaction.avatar_description = status.account.avatar_description; }); - return next; + return response.next; }; - for (let link: string | undefined, i = 0; i < 20; i++) { - link = await fetchStatuses(link); + for (let next: (() => Promise>) | null | undefined, i = 0; i < 20; i++) { + next = await fetchStatuses(next); setProgress({ state: 'fetchingStatuses', progress: (i / 20) * 40 }); - if (!link) break; + if (!next) break; } - for (let link: string | undefined, i = 0; i < 20; i++) { - link = await fetchFavourites(link); + for (let next: (() => Promise>) | null | undefined, i = 0; i < 20; i++) { + next = await fetchFavourites(next); setProgress({ state: 'fetchingFavourites', progress: 40 + (i / 20) * 40 }); - if (!link) break; + if (!next) break; } const result = await Promise.all(Object.entries(interactions).map(([id, { acct, avatar, avatar_description, favourites, reblogs, replies }]) => { @@ -103,7 +101,7 @@ const processCircle = (setProgress: (progress: { if (interaction.acct) return interaction; - const { json: account } = await fetch(`/api/v1/accounts/${interaction.id}`); + const account = await client.accounts.getAccount(interaction.id); interaction.acct = account.acct; interaction.avatar = account.avatar_static || account.avatar; diff --git a/src/actions/compose.ts b/src/actions/compose.ts index 5808dc345..17e78e079 100644 --- a/src/actions/compose.ts +++ b/src/actions/compose.ts @@ -1,12 +1,10 @@ -import { List as ImmutableList } from 'immutable'; import throttle from 'lodash/throttle'; import { defineMessages, IntlShape } from 'react-intl'; -import api from 'soapbox/api'; +import { getClient } from 'soapbox/api'; import { isNativeEmoji } from 'soapbox/features/emoji'; import emojiSearch from 'soapbox/features/emoji/search'; import { Language } from 'soapbox/features/preferences'; -import { normalizeTag } from 'soapbox/normalizers'; import { selectAccount, selectOwnAccount, makeGetAccount } from 'soapbox/selectors'; import { tagHistory } from 'soapbox/settings'; import toast from 'soapbox/toast'; @@ -22,11 +20,12 @@ import { getSettings } from './settings'; import { createStatus } from './statuses'; import type { EditorState } from 'lexical'; +import type { Tag } from 'pl-api'; import type { AutoSuggestion } from 'soapbox/components/autosuggest-input'; import type { Emoji } from 'soapbox/features/emoji'; import type { Account, Group } from 'soapbox/schemas'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity, Status, Tag } from 'soapbox/types/entities'; +import type { APIEntity, Status } from 'soapbox/types/entities'; import type { History } from 'soapbox/types/history'; let cancelFetchComposeSuggestions = new AbortController(); @@ -510,7 +509,7 @@ const changeUploadCompose = (composeId: string, id: string, params: Record { - dispatch(changeUploadComposeSuccess(composeId, response.json)); + dispatch(changeUploadComposeSuccess(composeId, response)); }).catch(error => { dispatch(changeUploadComposeFail(composeId, id, error)); }); @@ -568,21 +567,15 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, composeId, cancelFetchComposeSuggestions = new AbortController(); } - api(getState)('/api/v1/accounts/search', { - params: { - q: token.slice(1), - resolve: false, - limit: 10, - }, - signal: cancelFetchComposeSuggestions.signal, - }).then(response => { - dispatch(importFetchedAccounts(response.json)); - dispatch(readyComposeSuggestionsAccounts(composeId, token, response.json)); - }).catch(error => { - if (!signal.aborted) { - toast.showAlertForError(error); - } - }); + return getClient(getState()).accounts.searchAccounts(token.slice(1), { resolve: false, limit: 10 }) // WIP: signal + .then(response => { + dispatch(importFetchedAccounts(response)); + dispatch(readyComposeSuggestionsAccounts(composeId, token, response)); + }).catch(error => { + if (!signal.aborted) { + toast.showAlertForError(error); + } + }); }, 200, { leading: true, trailing: true }); const fetchComposeSuggestionsEmojis = (dispatch: AppDispatch, getState: () => RootState, composeId: string, token: string) => { @@ -611,15 +604,8 @@ const fetchComposeSuggestionsTags = (dispatch: AppDispatch, getState: () => Root return dispatch(updateSuggestionTags(composeId, token, currentTrends)); } - api(getState)('/api/v2/search', { - params: { - q: token.slice(1), - limit: 10, - type: 'hashtags', - }, - signal: cancelFetchComposeSuggestions.signal, - }).then(response => { - dispatch(updateSuggestionTags(composeId, token, response.json?.hashtags.map(normalizeTag))); + return getClient(state).search.search(token.slice(1), { limit: 10, type: 'hashtags' }).then(response => { // TODO signals + dispatch(updateSuggestionTags(composeId, token, response.hashtags)); }).catch(error => { if (!signal.aborted) { toast.showAlertForError(error); @@ -702,7 +688,7 @@ const selectComposeSuggestion = (composeId: string, position: number, token: str dispatch(action); }; -const updateSuggestionTags = (composeId: string, token: string, tags: ImmutableList) => ({ +const updateSuggestionTags = (composeId: string, token: string, tags: Array) => ({ type: COMPOSE_SUGGESTION_TAGS_UPDATE, id: composeId, token, diff --git a/src/actions/conversations.ts b/src/actions/conversations.ts index 6a91a5ba9..4de129724 100644 --- a/src/actions/conversations.ts +++ b/src/actions/conversations.ts @@ -1,6 +1,6 @@ import { isLoggedIn } from 'soapbox/utils/auth'; -import api, { getNextLink } from '../api'; +import { getClient } from '../api'; import { importFetchedAccounts, @@ -8,6 +8,7 @@ import { importFetchedStatus, } from './importer'; +import type { Conversation, PaginatedResponse } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -37,7 +38,7 @@ const markConversationRead = (conversationId: string) => (dispatch: AppDispatch, id: conversationId, }); - api(getState)(`/api/v1/conversations/${conversationId}/read`, { method: 'POST' }); + return getClient(getState).timelines.markConversationRead(conversationId); }; const expandConversations = ({ maxId }: Record = {}) => (dispatch: AppDispatch, getState: () => RootState) => { @@ -53,13 +54,11 @@ const expandConversations = ({ maxId }: Record = {}) => (dispatch: const isLoadingRecent = !!params.since_id; - api(getState)('/api/v1/conversations', { params }) + return getClient(getState).timelines.getConversations(params) .then(response => { - const next = getNextLink(response); - - dispatch(importFetchedAccounts(response.json.reduce((aggr: Array, item: APIEntity) => aggr.concat(item.accounts), []))); - dispatch(importFetchedStatuses(response.json.map((item: Record) => item.last_status).filter((x?: APIEntity) => !!x))); - dispatch(expandConversationsSuccess(response.json, next || null, isLoadingRecent)); + dispatch(importFetchedAccounts(response.items.reduce((aggr: Array, item: APIEntity) => aggr.concat(item.accounts), []))); + dispatch(importFetchedStatuses(response.items.map((item: Record) => item.last_status).filter((x?: APIEntity) => !!x))); + dispatch(expandConversationsSuccess(response.items, response.next, isLoadingRecent)); }) .catch(err => dispatch(expandConversationsFail(err))); }; @@ -68,7 +67,11 @@ const expandConversationsRequest = () => ({ type: CONVERSATIONS_FETCH_REQUEST, }); -const expandConversationsSuccess = (conversations: APIEntity[], next: string | null, isLoadingRecent: boolean) => ({ +const expandConversationsSuccess = ( + conversations: APIEntity[], + next: (() => Promise>) | null, + isLoadingRecent: boolean, +) => ({ type: CONVERSATIONS_FETCH_SUCCESS, conversations, next, diff --git a/src/actions/custom-emojis.ts b/src/actions/custom-emojis.ts index 2315d8cfa..fd40a9cbe 100644 --- a/src/actions/custom-emojis.ts +++ b/src/actions/custom-emojis.ts @@ -1,4 +1,4 @@ -import api from '../api'; +import { getClient } from '../api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -14,8 +14,8 @@ const fetchCustomEmojis = () => dispatch(fetchCustomEmojisRequest()); - api(getState)('/api/v1/custom_emojis').then(response => { - dispatch(fetchCustomEmojisSuccess(response.json)); + return getClient(getState()).instance.getCustomEmojis().then(response => { + dispatch(fetchCustomEmojisSuccess(response)); }).catch(error => { dispatch(fetchCustomEmojisFail(error)); }); diff --git a/src/actions/directory.ts b/src/actions/directory.ts index 255ffdb5b..f423ac5c4 100644 --- a/src/actions/directory.ts +++ b/src/actions/directory.ts @@ -1,4 +1,4 @@ -import api from '../api'; +import { getClient } from '../api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts } from './importer'; @@ -18,7 +18,7 @@ const fetchDirectory = (params: Record) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchDirectoryRequest()); - api(getState)('/api/v1/directory', { params: { ...params, limit: 20 } }).then(({ json: data }) => { + return getClient(getState()).instance.profileDirectory({...params, limit: 20}).then((data) => { dispatch(importFetchedAccounts(data)); dispatch(fetchDirectorySuccess(data)); dispatch(fetchRelationships(data.map((x: APIEntity) => x.id))); @@ -45,7 +45,7 @@ const expandDirectory = (params: Record) => const loadedItems = getState().user_lists.directory.items.size; - api(getState)('/api/v1/directory', { params: { ...params, offset: loadedItems, limit: 20 } }).then(({ json: data }) => { + return getClient(getState()).instance.profileDirectory({ ...params, offset: loadedItems, limit: 20 }).then((data) => { dispatch(importFetchedAccounts(data)); dispatch(expandDirectorySuccess(data)); dispatch(fetchRelationships(data.map((x: APIEntity) => x.id))); diff --git a/src/actions/domain-blocks.ts b/src/actions/domain-blocks.ts index 9f597b957..d94bb75b5 100644 --- a/src/actions/domain-blocks.ts +++ b/src/actions/domain-blocks.ts @@ -1,8 +1,9 @@ import { Entities } from 'soapbox/entity-store/entities'; import { isLoggedIn } from 'soapbox/utils/auth'; -import api, { getNextLink } from '../api'; +import { getClient, getNextLink } from '../api'; +import type { PaginatedResponse } from 'pl-api'; import type { EntityStore } from 'soapbox/entity-store/types'; import type { Account } from 'soapbox/schemas'; import type { AppDispatch, RootState } from 'soapbox/store'; @@ -29,10 +30,7 @@ const blockDomain = (domain: string) => dispatch(blockDomainRequest(domain)); - api(getState)('/api/v1/domain_blocks', { - method: 'POST', - body: JSON.stringify(domain), - }).then(() => { + return getClient(getState).filtering.blockDomain(domain).then(() => { const accounts = selectAccountsByDomain(getState(), domain); if (!accounts) return; dispatch(blockDomainSuccess(domain, accounts)); @@ -64,11 +62,7 @@ const unblockDomain = (domain: string) => dispatch(unblockDomainRequest(domain)); - api(getState)('/api/v1/domain_blocks', { - method: 'DELETE', - params: { domain }, - body: JSON.stringify({ domain }), - }).then(() => { + return getClient(getState).filtering.unblockDomain(domain).then(() => { const accounts = selectAccountsByDomain(getState(), domain); if (!accounts) return; dispatch(unblockDomainSuccess(domain, accounts)); @@ -100,9 +94,8 @@ const fetchDomainBlocks = () => dispatch(fetchDomainBlocksRequest()); - api(getState)('/api/v1/domain_blocks').then(response => { - const next = getNextLink(response); - dispatch(fetchDomainBlocksSuccess(response.json, next || null)); + return getClient(getState).filtering.getDomainBlocks().then(response => { + dispatch(fetchDomainBlocksSuccess(response.items, response.next)); }).catch(err => { dispatch(fetchDomainBlocksFail(err)); }); @@ -112,7 +105,7 @@ const fetchDomainBlocksRequest = () => ({ type: DOMAIN_BLOCKS_FETCH_REQUEST, }); -const fetchDomainBlocksSuccess = (domains: string[], next: string | null) => ({ +const fetchDomainBlocksSuccess = (domains: string[], next: (() => Promise>) | null) => ({ type: DOMAIN_BLOCKS_FETCH_SUCCESS, domains, next, @@ -127,17 +120,14 @@ const expandDomainBlocks = () => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - const url = getState().domain_lists.blocks.next; + const next = getState().domain_lists.blocks.next; - if (!url) { - return; - } + if (!next) return; dispatch(expandDomainBlocksRequest()); - api(getState)(url).then(response => { - const next = getNextLink(response); - dispatch(expandDomainBlocksSuccess(response.json, next || null)); + next().then(response => { + dispatch(expandDomainBlocksSuccess(response.items, response.next)); }).catch(err => { dispatch(expandDomainBlocksFail(err)); }); @@ -156,7 +146,7 @@ const expandDomainBlocksRequest = () => ({ type: DOMAIN_BLOCKS_EXPAND_REQUEST, }); -const expandDomainBlocksSuccess = (domains: string[], next: string | null) => ({ +const expandDomainBlocksSuccess = (domains: string[], next: (() => Promise>) | null) => ({ type: DOMAIN_BLOCKS_EXPAND_SUCCESS, domains, next, diff --git a/src/actions/email-list.ts b/src/actions/email-list.ts index 93fe172dd..11750a124 100644 --- a/src/actions/email-list.ts +++ b/src/actions/email-list.ts @@ -1,18 +1,18 @@ -import api from '../api'; +import { getClient } from '../api'; import type { RootState } from 'soapbox/store'; const getSubscribersCsv = () => (dispatch: any, getState: () => RootState) => - api(getState)('/api/v1/pleroma/admin/email_list/subscribers.csv'); + getClient(getState).request('/api/v1/pleroma/admin/email_list/subscribers.csv', { contentType: '' }); const getUnsubscribersCsv = () => (dispatch: any, getState: () => RootState) => - api(getState)('/api/v1/pleroma/admin/email_list/unsubscribers.csv'); + getClient(getState).request('/api/v1/pleroma/admin/email_list/unsubscribers.csv', { contentType: '' }); const getCombinedCsv = () => (dispatch: any, getState: () => RootState) => - api(getState)('/api/v1/pleroma/admin/email_list/combined.csv'); + getClient(getState).request('/api/v1/pleroma/admin/email_list/combined.csv', { contentType: '' }); export { getSubscribersCsv, diff --git a/src/actions/emoji-reacts.ts b/src/actions/emoji-reacts.ts index e73d56ca1..69ef8dd4a 100644 --- a/src/actions/emoji-reacts.ts +++ b/src/actions/emoji-reacts.ts @@ -2,7 +2,7 @@ import { List as ImmutableList } from 'immutable'; import { isLoggedIn } from 'soapbox/utils/auth'; -import api from '../api'; +import { getClient } from '../api'; import { importFetchedAccounts, importFetchedStatus } from './importer'; import { favourite, unfavourite } from './interactions'; @@ -59,7 +59,7 @@ const fetchEmojiReacts = (id: string, emoji: string) => ? `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` : `/api/v1/pleroma/statuses/${id}/reactions`; - return api(getState)(url).then(response => { + return getClient(getState).request(url).then(response => { response.json.forEach((emojiReact: APIEntity) => { dispatch(importFetchedAccounts(emojiReact.accounts)); }); @@ -75,7 +75,7 @@ const emojiReact = (status: Status, emoji: string, custom?: string) => dispatch(emojiReactRequest(status, emoji, custom)); - return api(getState)(`/api/v1/pleroma/statuses/${status.id}/reactions/${emoji}`, { + return getClient(getState).request(`/api/v1/pleroma/statuses/${status.id}/reactions/${emoji}`, { method: 'PUT', }).then((response) => { dispatch(importFetchedStatus(response.json)); @@ -91,7 +91,7 @@ const unEmojiReact = (status: Status, emoji: string) => dispatch(unEmojiReactRequest(status, emoji)); - return api(getState)(`/api/v1/pleroma/statuses/${status.id}/reactions/${emoji}`, { + return getClient(getState).request(`/api/v1/pleroma/statuses/${status.id}/reactions/${emoji}`, { method: 'DELETE', }).then(response => { diff --git a/src/actions/events.ts b/src/actions/events.ts index 6b0f313aa..d9eea2dce 100644 --- a/src/actions/events.ts +++ b/src/actions/events.ts @@ -1,6 +1,6 @@ import { defineMessages, IntlShape } from 'react-intl'; -import api, { getNextLink } from 'soapbox/api'; +import { getClient, getNextLink } from 'soapbox/api'; import toast from 'soapbox/toast'; import { importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from './importer'; @@ -97,7 +97,7 @@ const messages = defineMessages({ const locationSearch = (query: string, signal?: AbortSignal) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: LOCATION_SEARCH_REQUEST, query }); - return api(getState)('/api/v1/pleroma/search/location', { params: { q: query }, signal }).then(({ json: locations }) => { + return getClient(getState).request('/api/v1/pleroma/search/location', { params: { q: query }, signal }).then(({ json: locations }) => { dispatch({ type: LOCATION_SEARCH_SUCCESS, locations }); return locations; }).catch(error => { @@ -223,9 +223,9 @@ const submitEvent = () => if (banner) params.banner_id = banner.id; if (location) params.location_id = location.origin_id; - return api(getState)(id === null ? '/api/v1/pleroma/events' : `/api/v1/pleroma/events/${id}`, { + return getClient(getState).request(id === null ? '/api/v1/pleroma/events' : `/api/v1/pleroma/events/${id}`, { method: id === null ? 'POST' : 'PUT', - body: JSON.stringify(params), + body: params, }).then(({ json: data }) => { dispatch(closeModal('COMPOSE_EVENT')); dispatch(importFetchedStatus(data)); @@ -266,11 +266,9 @@ const joinEvent = (id: string, participationMessage?: string) => dispatch(joinEventRequest(status)); - return api(getState)(`/api/v1/pleroma/events/${id}/join`, { + return getClient(getState).request(`/api/v1/pleroma/events/${id}/join`, { method: 'POST', - body: JSON.stringify({ - participation_message: participationMessage, - }), + body: { participation_message: participationMessage }, }).then(({ json: data }) => { dispatch(importFetchedStatus(data)); dispatch(joinEventSuccess(data)); @@ -313,7 +311,7 @@ const leaveEvent = (id: string) => dispatch(leaveEventRequest(status)); - return api(getState)(`/api/v1/pleroma/events/${id}/leave`, { + return getClient(getState).request(`/api/v1/pleroma/events/${id}/leave`, { method: 'POST', }).then(({ json: data }) => { dispatch(importFetchedStatus(data)); @@ -343,7 +341,7 @@ const fetchEventParticipations = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchEventParticipationsRequest(id)); - return api(getState)(`/api/v1/pleroma/events/${id}/participations`).then(response => { + return getClient(getState).request(`/api/v1/pleroma/events/${id}/participations`).then(response => { const next = getNextLink(response); dispatch(importFetchedAccounts(response.json)); return dispatch(fetchEventParticipationsSuccess(id, response.json, next || null)); @@ -372,18 +370,17 @@ const fetchEventParticipationsFail = (id: string, error: unknown) => ({ const expandEventParticipations = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const url = getState().user_lists.event_participations.get(id)?.next || null; + const next = getState().user_lists.event_participations.get(id)?.next || null; - if (url === null) { + if (next === null) { return dispatch(noOp); } dispatch(expandEventParticipationsRequest(id)); - return api(getState)(url).then(response => { - const next = getNextLink(response); - dispatch(importFetchedAccounts(response.json)); - return dispatch(expandEventParticipationsSuccess(id, response.json, next || null)); + return next().then(response => { + dispatch(importFetchedAccounts(response.items)); + return dispatch(expandEventParticipationsSuccess(id, response.items, response.next || null)); }).catch(error => { dispatch(expandEventParticipationsFail(id, error)); }); @@ -411,7 +408,7 @@ const fetchEventParticipationRequests = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchEventParticipationRequestsRequest(id)); - return api(getState)(`/api/v1/pleroma/events/${id}/participation_requests`).then(response => { + return getClient(getState).request(`/api/v1/pleroma/events/${id}/participation_requests`).then(response => { const next = getNextLink(response); dispatch(importFetchedAccounts(response.json.map(({ account }: APIEntity) => account))); return dispatch(fetchEventParticipationRequestsSuccess(id, response.json, next || null)); @@ -448,7 +445,7 @@ const expandEventParticipationRequests = (id: string) => dispatch(expandEventParticipationRequestsRequest(id)); - return api(getState)(url).then(response => { + return getClient(getState).request(url).then(response => { const next = getNextLink(response); dispatch(importFetchedAccounts(response.json.map(({ account }: APIEntity) => account))); return dispatch(expandEventParticipationRequestsSuccess(id, response.json, next || null)); @@ -479,7 +476,7 @@ const authorizeEventParticipationRequest = (id: string, accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(authorizeEventParticipationRequestRequest(id, accountId)); - return api(getState)(`/api/v1/pleroma/events/${id}/participation_requests/${accountId}/authorize`, { + return getClient(getState).request(`/api/v1/pleroma/events/${id}/participation_requests/${accountId}/authorize`, { method: 'POST', }).then(() => { dispatch(authorizeEventParticipationRequestSuccess(id, accountId)); @@ -510,7 +507,7 @@ const rejectEventParticipationRequest = (id: string, accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(rejectEventParticipationRequestRequest(id, accountId)); - return api(getState)(`/api/v1/pleroma/events/${id}/participation_requests/${accountId}/reject`, { + return getClient(getState).request(`/api/v1/pleroma/events/${id}/participation_requests/${accountId}/reject`, { method: 'POST', }).then(() => { dispatch(rejectEventParticipationRequestSuccess(id, accountId)); @@ -539,7 +536,7 @@ const rejectEventParticipationRequestFail = (id: string, accountId: string, erro const fetchEventIcs = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => - api(getState)(`/api/v1/pleroma/events/${id}/ics`); + getClient(getState).request(`/api/v1/pleroma/events/${id}/ics`); const cancelEventCompose = () => ({ type: EVENT_COMPOSE_CANCEL, @@ -557,13 +554,13 @@ const editEvent = (id: string) => (dispatch: AppDispatch, getState: () => RootSt dispatch({ type: STATUS_FETCH_SOURCE_REQUEST }); - api(getState)(`/api/v1/statuses/${id}/source`).then(response => { + return getClient(getState()).statuses.getStatusSource(id).then(response => { dispatch({ type: STATUS_FETCH_SOURCE_SUCCESS }); dispatch({ type: EVENT_FORM_SET, status, - text: response.json.text, - location: response.json.location, + text: response.text, + location: response.location, }); dispatch(openModal('COMPOSE_EVENT')); }).catch(error => { @@ -579,13 +576,14 @@ const fetchRecentEvents = () => dispatch({ type: RECENT_EVENTS_FETCH_REQUEST }); - api(getState)('/api/v1/timelines/public?only_events=true').then(response => { - const next = getNextLink(response); - dispatch(importFetchedStatuses(response.json)); + return getClient(getState()).timelines.publicTimeline({ + only_events: true + }).then(response => { + dispatch(importFetchedStatuses(response.items)); dispatch({ type: RECENT_EVENTS_FETCH_SUCCESS, - statuses: response.json, - next: next || null, + statuses: response.items, + next: response.next, }); }).catch(error => { dispatch({ type: RECENT_EVENTS_FETCH_FAIL, error }); @@ -600,7 +598,7 @@ const fetchJoinedEvents = () => dispatch({ type: JOINED_EVENTS_FETCH_REQUEST }); - api(getState)('/api/v1/pleroma/events/joined_events').then(response => { + getClient(getState).request('/api/v1/pleroma/events/joined_events').then(response => { const next = getNextLink(response); dispatch(importFetchedStatuses(response.json)); dispatch({ diff --git a/src/actions/export-data.ts b/src/actions/export-data.ts index 1db097bd4..ac51345c3 100644 --- a/src/actions/export-data.ts +++ b/src/actions/export-data.ts @@ -1,9 +1,10 @@ import { defineMessages } from 'react-intl'; -import api, { type PlfeResponse, getLinks } from 'soapbox/api'; +import { getClient } from 'soapbox/api'; import { normalizeAccount } from 'soapbox/normalizers'; import toast from 'soapbox/toast'; +import type { Account, PaginatedResponse } from 'pl-api'; import type { RootState } from 'soapbox/store'; const EXPORT_FOLLOWS_REQUEST = 'EXPORT_FOLLOWS_REQUEST'; @@ -48,25 +49,25 @@ const fileExport = (content: string, fileName: string) => { document.body.removeChild(fileToDownload); }; -const listAccounts = (getState: () => RootState) => async(apiResponse: PlfeResponse) => { - const followings = apiResponse.json; +const listAccounts = async (response: PaginatedResponse) => { + const followings = response.items; let accounts = []; - let next = getLinks(apiResponse).refs.find(link => link.rel === 'next'); - while (next) { - apiResponse = await api(getState)(next.uri); - next = getLinks(apiResponse).refs.find(link => link.rel === 'next'); - Array.prototype.push.apply(followings, apiResponse.json); + while (response.next) { + response = await response.next(); + Array.prototype.push.apply(followings, response.items); } accounts = followings.map((account: any) => normalizeAccount(account).fqn); return Array.from(new Set(accounts)); }; -const exportFollows = () => (dispatch: React.Dispatch, getState: () => RootState) => { +const exportFollows = () => async (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: EXPORT_FOLLOWS_REQUEST }); const me = getState().me; - return api(getState)(`/api/v1/accounts/${me}/following?limit=40`) - .then(listAccounts(getState)) + if (!me) return; + + return getClient(getState()).accounts.getAccountFollowing(me, { limit: 40 }) + .then(listAccounts) .then((followings) => { followings = followings.map(fqn => fqn + ',true'); followings.unshift('Account address,Show boosts'); @@ -81,8 +82,8 @@ const exportFollows = () => (dispatch: React.Dispatch, getSta const exportBlocks = () => (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: EXPORT_BLOCKS_REQUEST }); - return api(getState)('/api/v1/blocks?limit=40') - .then(listAccounts(getState)) + return getClient(getState()).filtering.getBlocks({ limit: 40 }) + .then(listAccounts) .then((blocks) => { fileExport(blocks.join('\n'), 'export_block.csv'); @@ -95,8 +96,8 @@ const exportBlocks = () => (dispatch: React.Dispatch, getStat const exportMutes = () => (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: EXPORT_MUTES_REQUEST }); - return api(getState)('/api/v1/mutes?limit=40') - .then(listAccounts(getState)) + return getClient(getState()).filtering.getMutes({ limit: 40 }) + .then(listAccounts) .then((mutes) => { fileExport(mutes.join('\n'), 'export_mutes.csv'); diff --git a/src/actions/external-auth.ts b/src/actions/external-auth.ts index cb79da5b9..2f83ce90d 100644 --- a/src/actions/external-auth.ts +++ b/src/actions/external-auth.ts @@ -6,6 +6,8 @@ * @see module:soapbox/actions/oauth */ +import { PlApiClient } from 'pl-api'; + import { createApp } from 'soapbox/actions/apps'; import { authLoggedIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth'; import { obtainOAuthToken } from 'soapbox/actions/oauth'; @@ -15,13 +17,11 @@ import sourceCode from 'soapbox/utils/code'; import { getQuirks } from 'soapbox/utils/quirks'; import { getInstanceScopes } from 'soapbox/utils/scopes'; -import { getFetch } from '../api'; - import type { AppDispatch, RootState } from 'soapbox/store'; -const fetchExternalInstance = (baseURL?: string) => - getFetch(null, baseURL)('/api/v1/instance') - .then(({ json: instance }) => instanceSchema.parse(instance)) +const fetchExternalInstance = (baseURL: string) => + (new PlApiClient(baseURL, undefined, { fetchInstance: false })).instance.getInstance() + .then(instance => instance) .catch(error => { if (error.response?.status === 401) { // Authenticated fetch is enabled. diff --git a/src/actions/familiar-followers.ts b/src/actions/familiar-followers.ts index deb13cdaa..fba1ba6ad 100644 --- a/src/actions/familiar-followers.ts +++ b/src/actions/familiar-followers.ts @@ -1,6 +1,6 @@ import { AppDispatch, RootState } from 'soapbox/store'; -import api from '../api'; +import { getClient } from '../api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts } from './importer'; @@ -17,9 +17,9 @@ const fetchAccountFamiliarFollowers = (accountId: string) => (dispatch: AppDispa id: accountId, }); - api(getState)(`/api/v1/accounts/familiar_followers?id=${accountId}`) - .then(({ json: data }) => { - const accounts = data.find(({ id }: { id: string }) => id === accountId).accounts; + getClient(getState()).accounts.getFamiliarFollowers([accountId]) + .then((data) => { + const accounts = data.find(({ id }: { id: string }) => id === accountId)!.accounts; dispatch(importFetchedAccounts(accounts)); dispatch(fetchRelationships(accounts.map((item: APIEntity) => item.id))); diff --git a/src/actions/favourites.ts b/src/actions/favourites.ts index 3cf175101..48660c4d7 100644 --- a/src/actions/favourites.ts +++ b/src/actions/favourites.ts @@ -1,9 +1,10 @@ import { isLoggedIn } from 'soapbox/utils/auth'; -import api, { getNextLink } from '../api'; +import { getClient, getNextLink } from '../api'; import { importFetchedStatuses } from './importer'; +import type { PaginatedResponse, Status } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -33,10 +34,9 @@ const fetchFavouritedStatuses = () => dispatch(fetchFavouritedStatusesRequest()); - api(getState)('/api/v1/favourites').then(response => { - const next = getNextLink(response); - dispatch(importFetchedStatuses(response.json)); - dispatch(fetchFavouritedStatusesSuccess(response.json, next || null)); + return getClient(getState()).accounts.getFavourites().then(response => { + dispatch(importFetchedStatuses(response.items)); + dispatch(fetchFavouritedStatusesSuccess(response.items, response.next)); }).catch(error => { dispatch(fetchFavouritedStatusesFail(error)); }); @@ -47,7 +47,7 @@ const fetchFavouritedStatusesRequest = () => ({ skipLoading: true, }); -const fetchFavouritedStatusesSuccess = (statuses: APIEntity[], next: string | null) => ({ +const fetchFavouritedStatusesSuccess = (statuses: APIEntity[], next: (() => Promise>) | null) => ({ type: FAVOURITED_STATUSES_FETCH_SUCCESS, statuses, next, @@ -64,18 +64,17 @@ const expandFavouritedStatuses = () => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - const url = getState().status_lists.get('favourites')?.next || null; + const next = getState().status_lists.get('favourites')?.next || null; - if (url === null || getState().status_lists.get('favourites')?.isLoading) { + if (next === null || getState().status_lists.get('favourites')?.isLoading) { return; } dispatch(expandFavouritedStatusesRequest()); - api(getState)(url).then(response => { - const next = getNextLink(response); - dispatch(importFetchedStatuses(response.json)); - dispatch(expandFavouritedStatusesSuccess(response.json, next || null)); + return next().then(response => { + dispatch(importFetchedStatuses(response.items)); + dispatch(expandFavouritedStatusesSuccess(response.items, response.next)); }).catch(error => { dispatch(expandFavouritedStatusesFail(error)); }); @@ -85,7 +84,7 @@ const expandFavouritedStatusesRequest = () => ({ type: FAVOURITED_STATUSES_EXPAND_REQUEST, }); -const expandFavouritedStatusesSuccess = (statuses: APIEntity[], next: string | null) => ({ +const expandFavouritedStatusesSuccess = (statuses: APIEntity[], next: (() => Promise>) | null) => ({ type: FAVOURITED_STATUSES_EXPAND_SUCCESS, statuses, next, @@ -106,7 +105,7 @@ const fetchAccountFavouritedStatuses = (accountId: string) => dispatch(fetchAccountFavouritedStatusesRequest(accountId)); - api(getState)(`/api/v1/pleroma/accounts/${accountId}/favourites`).then(response => { + return getClient(getState).request(`/api/v1/pleroma/accounts/${accountId}/favourites`).then(response => { const next = getNextLink(response); dispatch(importFetchedStatuses(response.json)); dispatch(fetchAccountFavouritedStatusesSuccess(accountId, response.json, next || null)); @@ -148,7 +147,7 @@ const expandAccountFavouritedStatuses = (accountId: string) => dispatch(expandAccountFavouritedStatusesRequest(accountId)); - api(getState)(url).then(response => { + return getClient(getState).request(url).then(response => { const next = getNextLink(response); dispatch(importFetchedStatuses(response.json)); dispatch(expandAccountFavouritedStatusesSuccess(accountId, response.json, next || null)); diff --git a/src/actions/filters.ts b/src/actions/filters.ts index ab880d84b..6bbbd6d88 100644 --- a/src/actions/filters.ts +++ b/src/actions/filters.ts @@ -2,9 +2,8 @@ import { defineMessages } from 'react-intl'; import toast from 'soapbox/toast'; import { isLoggedIn } from 'soapbox/utils/auth'; -import { getFeatures } from 'soapbox/utils/features'; -import api from '../api'; +import { getClient } from '../api'; import type { AppDispatch, RootState } from 'soapbox/store'; @@ -35,97 +34,23 @@ const messages = defineMessages({ type FilterKeywords = { keyword: string; whole_word: boolean }[]; -const fetchFiltersV1 = () => - (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ - type: FILTERS_FETCH_REQUEST, - skipLoading: true, - }); - - return api(getState)('/api/v1/filters') - .then(({ json: data }) => dispatch({ - type: FILTERS_FETCH_SUCCESS, - filters: data, - skipLoading: true, - })) - .catch(err => dispatch({ - type: FILTERS_FETCH_FAIL, - err, - skipLoading: true, - skipAlert: true, - })); - }; - -const fetchFiltersV2 = () => - (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ - type: FILTERS_FETCH_REQUEST, - skipLoading: true, - }); - - return api(getState)('/api/v2/filters') - .then(({ json: data }) => dispatch({ - type: FILTERS_FETCH_SUCCESS, - filters: data, - skipLoading: true, - })) - .catch(err => dispatch({ - type: FILTERS_FETCH_FAIL, - err, - skipLoading: true, - skipAlert: true, - })); - }; - const fetchFilters = (fromFiltersPage = false) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - const state = getState(); - const instance = state.instance; - const features = getFeatures(instance); - - if (features.filtersV2 && fromFiltersPage) return dispatch(fetchFiltersV2()); - - if (features.filters) return dispatch(fetchFiltersV1()); - }; - -const fetchFilterV1 = (id: string) => - (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ - type: FILTER_FETCH_REQUEST, + type: FILTERS_FETCH_REQUEST, skipLoading: true, }); - return api(getState)(`/api/v1/filters/${id}`) - .then(({ json: data }) => dispatch({ - type: FILTER_FETCH_SUCCESS, - filter: data, + return getClient(getState).filtering.getFilters() + .then((data) => dispatch({ + type: FILTERS_FETCH_SUCCESS, + filters: data, skipLoading: true, })) .catch(err => dispatch({ - type: FILTER_FETCH_FAIL, - err, - skipLoading: true, - skipAlert: true, - })); - }; - -const fetchFilterV2 = (id: string) => - (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ - type: FILTER_FETCH_REQUEST, - skipLoading: true, - }); - - return api(getState)(`/api/v2/filters/${id}`) - .then(({ json: data }) => dispatch({ - type: FILTER_FETCH_SUCCESS, - filter: data, - skipLoading: true, - })) - .catch(err => dispatch({ - type: FILTER_FETCH_FAIL, + type: FILTERS_FETCH_FAIL, err, skipLoading: true, skipAlert: true, @@ -134,148 +59,70 @@ const fetchFilterV2 = (id: string) => const fetchFilter = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const instance = state.instance; - const features = getFeatures(instance); + dispatch({ + type: FILTER_FETCH_REQUEST, + skipLoading: true, + }); - if (features.filtersV2) return dispatch(fetchFilterV2(id)); - - if (features.filters) return dispatch(fetchFilterV1(id)); + return getClient(getState).filtering.getFilter(id) + .then((data) => dispatch({ + type: FILTER_FETCH_SUCCESS, + filter: data, + skipLoading: true, + })) + .catch(err => dispatch({ + type: FILTER_FETCH_FAIL, + err, + skipLoading: true, + skipAlert: true, + })); }; -const createFilterV1 = (title: string, expires_in: string | null, context: Array, hide: boolean, keywords: FilterKeywords) => +const createFilter = (title: string, expires_in: number | undefined, context: Array, hide: boolean, keywords_attributes: FilterKeywords) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: FILTERS_CREATE_REQUEST }); - return api(getState)('/api/v1/filters', { - method: 'POST', - body: JSON.stringify({ - phrase: keywords[0].keyword, - context, - irreversible: hide, - whole_word: keywords[0].whole_word, - expires_in, - }), + + return getClient(getState).filtering.createFilter({ + title, + context, + filter_action: hide ? 'hide' : 'warn', + expires_in, + keywords_attributes, }).then(response => { - dispatch({ type: FILTERS_CREATE_SUCCESS, filter: response.json }); + dispatch({ type: FILTERS_CREATE_SUCCESS, filter: response }); toast.success(messages.added); }).catch(error => { dispatch({ type: FILTERS_CREATE_FAIL, error }); }); }; -const createFilterV2 = (title: string, expires_in: string | null, context: Array, hide: boolean, keywords_attributes: FilterKeywords) => - (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ type: FILTERS_CREATE_REQUEST }); - return api(getState)('/api/v2/filters', { - method: 'POST', - body: JSON.stringify({ - title, - context, - filter_action: hide ? 'hide' : 'warn', - expires_in, - keywords_attributes, - }), - }).then(response => { - dispatch({ type: FILTERS_CREATE_SUCCESS, filter: response.json }); - toast.success(messages.added); - }).catch(error => { - dispatch({ type: FILTERS_CREATE_FAIL, error }); - }); - }; - -const createFilter = (title: string, expires_in: string | null, context: Array, hide: boolean, keywords: FilterKeywords) => - (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const instance = state.instance; - const features = getFeatures(instance); - - if (features.filtersV2) return dispatch(createFilterV2(title, expires_in, context, hide, keywords)); - - return dispatch(createFilterV1(title, expires_in, context, hide, keywords)); - }; - -const updateFilterV1 = (id: string, title: string, expires_in: string | null, context: Array, hide: boolean, keywords: FilterKeywords) => +const updateFilter = (id: string, title: string, expires_in: number | undefined, context: Array, hide: boolean, keywords_attributes: FilterKeywords) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: FILTERS_UPDATE_REQUEST }); - return api(getState)(`/api/v1/filters/${id}`, { - method: 'PATCH', - body: JSON.stringify({ - phrase: keywords[0].keyword, - context, - irreversible: hide, - whole_word: keywords[0].whole_word, - expires_in, - }), + + return getClient(getState).filtering.updateFilter(id, { + title, + context, + filter_action: hide ? 'hide' : 'warn', + expires_in, + keywords_attributes, }).then(response => { - dispatch({ type: FILTERS_UPDATE_SUCCESS, filter: response.json }); + dispatch({ type: FILTERS_UPDATE_SUCCESS, filter: response }); toast.success(messages.added); }).catch(error => { dispatch({ type: FILTERS_UPDATE_FAIL, error }); }); }; -const updateFilterV2 = (id: string, title: string, expires_in: string | null, context: Array, hide: boolean, keywords_attributes: FilterKeywords) => - (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ type: FILTERS_UPDATE_REQUEST }); - return api(getState)(`/api/v2/filters/${id}`, { - method: 'PATCH', - body: JSON.stringify({ - title, - context, - filter_action: hide ? 'hide' : 'warn', - expires_in, - keywords_attributes, - }), - }).then(response => { - dispatch({ type: FILTERS_UPDATE_SUCCESS, filter: response.json }); - toast.success(messages.added); - }).catch(error => { - dispatch({ type: FILTERS_UPDATE_FAIL, error }); - }); - }; - -const updateFilter = (id: string, title: string, expires_in: string | null, context: Array, hide: boolean, keywords: FilterKeywords) => - (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const instance = state.instance; - const features = getFeatures(instance); - - if (features.filtersV2) return dispatch(updateFilterV2(id, title, expires_in, context, hide, keywords)); - - return dispatch(updateFilterV1(id, title, expires_in, context, hide, keywords)); - }; - -const deleteFilterV1 = (id: string) => - (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ type: FILTERS_DELETE_REQUEST }); - return api(getState)(`/api/v1/filters/${id}`, { method: 'DELETE' }).then(response => { - dispatch({ type: FILTERS_DELETE_SUCCESS, filter: response.json }); - toast.success(messages.removed); - }).catch(error => { - dispatch({ type: FILTERS_DELETE_FAIL, error }); - }); - }; - -const deleteFilterV2 = (id: string) => - (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ type: FILTERS_DELETE_REQUEST }); - return api(getState)(`/api/v2/filters/${id}`, { method: 'DELETE' }).then(response => { - dispatch({ type: FILTERS_DELETE_SUCCESS, filter: response.json }); - toast.success(messages.removed); - }).catch(error => { - dispatch({ type: FILTERS_DELETE_FAIL, error }); - }); - }; - const deleteFilter = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const instance = state.instance; - const features = getFeatures(instance); - - if (features.filtersV2) return dispatch(deleteFilterV2(id)); - - return dispatch(deleteFilterV1(id)); + dispatch({ type: FILTERS_DELETE_REQUEST }); + return getClient(getState).filtering.deleteFilter(id).then(response => { + dispatch({ type: FILTERS_DELETE_SUCCESS, filter: response }); + toast.success(messages.removed); + }).catch(error => { + dispatch({ type: FILTERS_DELETE_FAIL, error }); + }); }; export { diff --git a/src/actions/groups.ts b/src/actions/groups.ts index 9dc582b6b..952c8f337 100644 --- a/src/actions/groups.ts +++ b/src/actions/groups.ts @@ -1,8 +1,8 @@ - -import api, { getNextLink } from '../api'; +import { getClient } from '../api'; import { importFetchedAccounts } from './importer'; +import type { Account, PaginatedResponse } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -16,22 +16,16 @@ const GROUP_UNBLOCK_FAIL = 'GROUP_UNBLOCK_FAIL'; const groupKick = (groupId: string, accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - - return api(getState)(`/api/v1/groups/${groupId}/kick`, { - method: 'POST', - body: JSON.stringify({ account_ids: [accountId] }), - }); + return getClient(getState).experimental.groups.kickGroupUsers(groupId, [accountId]); }; const fetchGroupBlocks = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchGroupBlocksRequest(id)); - return api(getState)(`/api/v1/groups/${id}/blocks`).then(response => { - const next = getNextLink(response); - - dispatch(importFetchedAccounts(response.json)); - dispatch(fetchGroupBlocksSuccess(id, response.json, next || null)); + return getClient(getState).experimental.groups.getGroupBlocks(id).then(response => { + dispatch(importFetchedAccounts(response.items)); + dispatch(fetchGroupBlocksSuccess(id, response.items, response.next)); }).catch(error => { dispatch(fetchGroupBlocksFail(id, error)); }); @@ -42,7 +36,7 @@ const fetchGroupBlocksRequest = (id: string) => ({ id, }); -const fetchGroupBlocksSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({ +const fetchGroupBlocksSuccess = (id: string, accounts: APIEntity[], next: (() => Promise>) | null) => ({ type: GROUP_BLOCKS_FETCH_SUCCESS, id, accounts, @@ -60,9 +54,8 @@ const groupUnblock = (groupId: string, accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(groupUnblockRequest(groupId, accountId)); - return api(getState)(`/api/v1/groups/${groupId}/blocks?account_ids[]=${accountId}`, { - method: 'DELETE', - }).then(() => dispatch(groupUnblockSuccess(groupId, accountId))) + return getClient(getState).experimental.groups.unblockGroupUsers(groupId, [accountId]) + .then(() => dispatch(groupUnblockSuccess(groupId, accountId))) .catch(err => dispatch(groupUnblockFail(groupId, accountId, err))); }; @@ -85,7 +78,6 @@ const groupUnblockFail = (groupId: string, accountId: string, error: unknown) => error, }); - export { GROUP_BLOCKS_FETCH_REQUEST, GROUP_BLOCKS_FETCH_SUCCESS, diff --git a/src/actions/history.ts b/src/actions/history.ts index eed644501..71d53f3c8 100644 --- a/src/actions/history.ts +++ b/src/actions/history.ts @@ -1,4 +1,4 @@ -import api from 'soapbox/api'; +import { getClient } from 'soapbox/api'; import { importFetchedAccounts } from './importer'; @@ -19,8 +19,8 @@ const fetchHistory = (statusId: string) => dispatch(fetchHistoryRequest(statusId)); - api(getState)(`/api/v1/statuses/${statusId}/history`).then(({ json: data }) => { - dispatch(importFetchedAccounts(data.map((x: APIEntity) => x.account))); + return getClient(getState()).statuses.getStatusHistory(statusId).then(data => { + dispatch(importFetchedAccounts(data.map((x) => x.account))); dispatch(fetchHistorySuccess(statusId, data)); }).catch(error => dispatch(fetchHistoryFail(error))); }; diff --git a/src/actions/import-data.ts b/src/actions/import-data.ts index 0fbd5dd01..63d4b02a3 100644 --- a/src/actions/import-data.ts +++ b/src/actions/import-data.ts @@ -2,7 +2,7 @@ import { defineMessages } from 'react-intl'; import toast from 'soapbox/toast'; -import api from '../api'; +import { getClient } from '../api'; import type { RootState } from 'soapbox/store'; @@ -41,9 +41,8 @@ const messages = defineMessages({ const importFollows = (params: FormData) => (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: IMPORT_FOLLOWS_REQUEST }); - return api(getState)('/api/pleroma/follow_import', { - method: 'POST', - body: JSON.stringify(params), + return getClient(getState).request('/api/pleroma/follow_import', { + method: 'POST', body: params, }).then(response => { toast.success(messages.followersSuccess); dispatch({ type: IMPORT_FOLLOWS_SUCCESS, config: response.json }); @@ -55,9 +54,8 @@ const importFollows = (params: FormData) => const importBlocks = (params: FormData) => (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: IMPORT_BLOCKS_REQUEST }); - return api(getState)('/api/pleroma/blocks_import', { - method: 'POST', - body: JSON.stringify(params), + return getClient(getState).request('/api/pleroma/blocks_import', { + method: 'POST', body: params, }).then(response => { toast.success(messages.blocksSuccess); dispatch({ type: IMPORT_BLOCKS_SUCCESS, config: response.json }); @@ -69,9 +67,8 @@ const importBlocks = (params: FormData) => const importMutes = (params: FormData) => (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: IMPORT_MUTES_REQUEST }); - return api(getState)('/api/pleroma/mutes_import', { - method: 'POST', - body: JSON.stringify(params), + return getClient(getState).request('/api/pleroma/mutes_import', { + method: 'POST', body: params, }).then(response => { toast.success(messages.mutesSuccess); dispatch({ type: IMPORT_MUTES_SUCCESS, config: response.json }); diff --git a/src/actions/instance.ts b/src/actions/instance.ts index d845b887b..898972ee1 100644 --- a/src/actions/instance.ts +++ b/src/actions/instance.ts @@ -1,12 +1,11 @@ -import { createAsyncThunk } from '@reduxjs/toolkit'; -import get from 'lodash/get'; -import { gte } from 'semver'; - -import { RootState } from 'soapbox/store'; import { getAuthUserUrl, getMeUrl } from 'soapbox/utils/auth'; -import { MASTODON, parseVersion, PLEROMA, REBASED } from 'soapbox/utils/features'; -import api from '../api'; +import { getClient } from '../api'; + +import type { AppDispatch, RootState } from 'soapbox/store'; + +const INSTANCE_FETCH_FAIL = 'INSTANCE_FETCH_FAIL' as const; +const INSTANCE_FETCH_SUCCESS = 'INSTANCE_FETCH_SUCCESS' as const; /** Figure out the appropriate instance to fetch depending on the state */ const getHost = (state: RootState) => { @@ -19,51 +18,19 @@ const getHost = (state: RootState) => { } }; -const supportsInstanceV2 = (instance: Record): boolean => { - const v = parseVersion(get(instance, 'version')); - return (v.software === MASTODON && gte(v.compatVersion, '4.0.0')) || - (v.software === PLEROMA && v.build === REBASED && gte(v.version, '2.5.54')); +const fetchInstance = () => async (dispatch: AppDispatch, getState: () => RootState) => { + try { + const instance = await getClient(getState).instance.getInstance(); + + dispatch({ type: INSTANCE_FETCH_SUCCESS, instance }); + } catch (error) { + dispatch({ type: INSTANCE_FETCH_FAIL, error }); + } }; -interface InstanceData { - instance: Record; - host: string | null | undefined; -} - -const fetchInstance = createAsyncThunk( - 'instance/fetch', - async(host, { dispatch, getState, rejectWithValue }) => { - try { - const response = await api(getState)('/api/v1/instance'); - const instance = response.json; - - if (supportsInstanceV2(instance)) { - dispatch(fetchInstanceV2(host)); - } - - return { instance, host }; - } catch (e) { - return rejectWithValue(e); - } - }, -); - -const fetchInstanceV2 = createAsyncThunk( - 'instanceV2/fetch', - async(host, { getState, rejectWithValue }) => { - try { - const response = await api(getState)('/api/v2/instance'); - const instance = response.json; - - return { instance, host }; - } catch (e) { - return rejectWithValue(e); - } - }, -); - export { + INSTANCE_FETCH_FAIL, + INSTANCE_FETCH_SUCCESS, getHost, fetchInstance, - fetchInstanceV2, }; diff --git a/src/actions/interactions.ts b/src/actions/interactions.ts index fd8ff8ced..d20bc556c 100644 --- a/src/actions/interactions.ts +++ b/src/actions/interactions.ts @@ -4,12 +4,13 @@ import toast, { type IToastOptions } from 'soapbox/toast'; import { isLoggedIn } from 'soapbox/utils/auth'; import { getFeatures } from 'soapbox/utils/features'; -import api, { getNextLink } from '../api'; +import { getClient } from '../api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts, importFetchedStatus } from './importer'; import { openModal } from './modals'; +import type { Account, PaginatedResponse } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity, Status as StatusEntity } from 'soapbox/types/entities'; @@ -79,6 +80,8 @@ const FAVOURITES_EXPAND_FAIL = 'FAVOURITES_EXPAND_FAIL' as const; const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS' as const; const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL' as const; +type AccountListLink = () => Promise>; + const messages = defineMessages({ bookmarkAdded: { id: 'status.bookmarked', defaultMessage: 'Bookmark added.' }, bookmarkRemoved: { id: 'status.unbookmarked', defaultMessage: 'Bookmark removed.' }, @@ -93,10 +96,10 @@ const reblog = (status: StatusEntity) => dispatch(reblogRequest(status)); - api(getState)(`/api/v1/statuses/${status.id}/reblog`, { method: 'POST' }).then((response) => { + return getClient(getState()).statuses.reblogStatus(status.id).then((response) => { // The reblog API method returns a new status wrapped around the original. In this case we are only // interested in how the original is modified, hence passing it skipping the wrapper - dispatch(importFetchedStatus(response.json.reblog)); + if (response.reblog) dispatch(importFetchedStatus(response.reblog)); dispatch(reblogSuccess(status)); }).catch(error => { dispatch(reblogFail(status, error)); @@ -109,7 +112,7 @@ const unreblog = (status: StatusEntity) => dispatch(unreblogRequest(status)); - api(getState)(`/api/v1/statuses/${status.id}/unreblog`, { method: 'POST' }).then(() => { + return getClient(getState()).statuses.unreblogStatus(status.id).then(() => { dispatch(unreblogSuccess(status)); }).catch(error => { dispatch(unreblogFail(status, error)); @@ -169,7 +172,7 @@ const favourite = (status: StatusEntity) => dispatch(favouriteRequest(status)); - api(getState)(`/api/v1/statuses/${status.id}/favourite`, { method: 'POST' }).then(() => { + return getClient(getState()).statuses.favouriteStatus(status.id).then(() => { dispatch(favouriteSuccess(status)); }).catch((error) => { dispatch(favouriteFail(status, error)); @@ -182,7 +185,7 @@ const unfavourite = (status: StatusEntity) => dispatch(unfavouriteRequest(status)); - api(getState)(`/api/v1/statuses/${status.id}/unfavourite`, { method: 'POST' }).then(() => { + return getClient(getState()).statuses.unfavouriteStatus(status.id).then(() => { dispatch(unfavouriteSuccess(status)); }).catch(error => { dispatch(unfavouriteFail(status, error)); @@ -242,7 +245,7 @@ const dislike = (status: StatusEntity) => dispatch(dislikeRequest(status)); - api(getState)(`/api/friendica/statuses/${status.id}/dislike`, { method: 'POST' }).then(() => { + return getClient(getState).request(`/api/friendica/statuses/${status.id}/dislike`, { method: 'POST' }).then(() => { dispatch(dislikeSuccess(status)); }).catch((error) => { dispatch(dislikeFail(status, error)); @@ -255,7 +258,7 @@ const undislike = (status: StatusEntity) => dispatch(undislikeRequest(status)); - api(getState)(`/api/friendica/statuses/${status.id}/undislike`, { method: 'POST' }).then(() => { + return getClient(getState).request(`/api/friendica/statuses/${status.id}/undislike`, { method: 'POST' }).then(() => { dispatch(undislikeSuccess(status)); }).catch(error => { dispatch(undislikeFail(status, error)); @@ -318,17 +321,15 @@ const bookmark = (status: StatusEntity, folderId?: string) => dispatch(bookmarkRequest(status)); - return api(getState)(`/api/v1/statuses/${status.id}/bookmark`, { - method: 'POST', - body: folderId ? JSON.stringify({ folder_id: folderId }) : undefined, - }).then((response) => { - dispatch(importFetchedStatus(response.json)); - dispatch(bookmarkSuccess(status, response.json)); + return getClient(getState()).statuses.bookmarkStatus(status.id, folderId).then((response) => { + dispatch(importFetchedStatus(response)); + dispatch(bookmarkSuccess(status, response)); let opts: IToastOptions = { actionLabel: messages.view, actionLink: folderId ? `/bookmarks/${folderId}` : '/bookmarks/all', }; + if (features.bookmarkFolders && typeof folderId !== 'string') { opts = { actionLabel: messages.selectFolder, @@ -348,9 +349,9 @@ const unbookmark = (status: StatusEntity) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(unbookmarkRequest(status)); - api(getState)(`/api/v1/statuses/${status.id}/unbookmark`, { method: 'POST' }).then(response => { - dispatch(importFetchedStatus(response.json)); - dispatch(unbookmarkSuccess(status, response.json)); + return getClient(getState()).statuses.unbookmarkStatus(status.id).then(response => { + dispatch(importFetchedStatus(response)); + dispatch(unbookmarkSuccess(status, response)); toast.success(messages.bookmarkRemoved); }).catch(error => { dispatch(unbookmarkFail(status, error)); @@ -406,11 +407,10 @@ const fetchReblogs = (id: string) => dispatch(fetchReblogsRequest(id)); - api(getState)(`/api/v1/statuses/${id}/reblogged_by`).then(response => { - const next = getNextLink(response); - dispatch(importFetchedAccounts(response.json)); - dispatch(fetchRelationships(response.json.map((item: APIEntity) => item.id))); - dispatch(fetchReblogsSuccess(id, response.json, next || null)); + return getClient(getState()).statuses.getRebloggedBy(id).then(response => { + dispatch(importFetchedAccounts(response.items)); + dispatch(fetchRelationships(response.items.map((item) => item.id))); + dispatch(fetchReblogsSuccess(id, response.items, response.next)); }).catch(error => { dispatch(fetchReblogsFail(id, error)); }); @@ -421,7 +421,7 @@ const fetchReblogsRequest = (id: string) => ({ id, }); -const fetchReblogsSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({ +const fetchReblogsSuccess = (id: string, accounts: APIEntity[], next: AccountListLink | null) => ({ type: REBLOGS_FETCH_SUCCESS, id, accounts, @@ -434,19 +434,18 @@ const fetchReblogsFail = (id: string, error: unknown) => ({ error, }); -const expandReblogs = (id: string, path: string) => +const expandReblogs = (id: string, next: AccountListLink) => (dispatch: AppDispatch, getState: () => RootState) => { - api(getState)(path).then(response => { - const next = getNextLink(response); - dispatch(importFetchedAccounts(response.json)); - dispatch(fetchRelationships(response.json.map((item: APIEntity) => item.id))); - dispatch(expandReblogsSuccess(id, response.json, next || null)); + next().then(response => { + dispatch(importFetchedAccounts(response.items)); + dispatch(fetchRelationships(response.items.map((item: APIEntity) => item.id))); + dispatch(expandReblogsSuccess(id, response.items, response.next)); }).catch(error => { dispatch(expandReblogsFail(id, error)); }); }; -const expandReblogsSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({ +const expandReblogsSuccess = (id: string, accounts: APIEntity[], next: AccountListLink | null) => ({ type: REBLOGS_EXPAND_SUCCESS, id, accounts, @@ -465,11 +464,10 @@ const fetchFavourites = (id: string) => dispatch(fetchFavouritesRequest(id)); - api(getState)(`/api/v1/statuses/${id}/favourited_by`).then(response => { - const next = getNextLink(response); - dispatch(importFetchedAccounts(response.json)); - dispatch(fetchRelationships(response.json.map((item: APIEntity) => item.id))); - dispatch(fetchFavouritesSuccess(id, response.json, next || null)); + return getClient(getState()).statuses.getFavouritedBy(id).then(response => { + dispatch(importFetchedAccounts(response.items)); + dispatch(fetchRelationships(response.items.map((item) => item.id))); + dispatch(fetchFavouritesSuccess(id, response.items, response.next)); }).catch(error => { dispatch(fetchFavouritesFail(id, error)); }); @@ -480,7 +478,7 @@ const fetchFavouritesRequest = (id: string) => ({ id, }); -const fetchFavouritesSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({ +const fetchFavouritesSuccess = (id: string, accounts: APIEntity[], next: AccountListLink | null) => ({ type: FAVOURITES_FETCH_SUCCESS, id, accounts, @@ -493,19 +491,18 @@ const fetchFavouritesFail = (id: string, error: unknown) => ({ error, }); -const expandFavourites = (id: string, path: string) => - (dispatch: AppDispatch, getState: () => RootState) => { - api(getState)(path).then(response => { - const next = getNextLink(response); - dispatch(importFetchedAccounts(response.json)); - dispatch(fetchRelationships(response.json.map((item: APIEntity) => item.id))); - dispatch(expandFavouritesSuccess(id, response.json, next || null)); +const expandFavourites = (id: string, next: AccountListLink) => + (dispatch: AppDispatch) => { + next().then(response => { + dispatch(importFetchedAccounts(response.items)); + dispatch(fetchRelationships(response.items.map((item) => item.id))); + dispatch(expandFavouritesSuccess(id, response.items, response.next)); }).catch(error => { dispatch(expandFavouritesFail(id, error)); }); }; -const expandFavouritesSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({ +const expandFavouritesSuccess = (id: string, accounts: APIEntity[], next: AccountListLink | null) => ({ type: FAVOURITES_EXPAND_SUCCESS, id, accounts, @@ -524,7 +521,7 @@ const fetchDislikes = (id: string) => dispatch(fetchDislikesRequest(id)); - api(getState)(`/api/friendica/statuses/${id}/disliked_by`).then(response => { + return getClient(getState).request(`/api/friendica/statuses/${id}/disliked_by`).then(response => { dispatch(importFetchedAccounts(response.json)); dispatch(fetchRelationships(response.json.map((item: APIEntity) => item.id))); dispatch(fetchDislikesSuccess(id, response.json)); @@ -554,7 +551,7 @@ const fetchReactions = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchReactionsRequest(id)); - api(getState)(`/api/v1/pleroma/statuses/${id}/reactions`).then(response => { + return getClient(getState).request(`/api/v1/pleroma/statuses/${id}/reactions`).then(response => { dispatch(importFetchedAccounts((response.json as APIEntity[]).map(({ accounts }) => accounts).flat())); dispatch(fetchReactionsSuccess(id, response.json)); }).catch(error => { @@ -585,8 +582,8 @@ const pin = (status: StatusEntity, accountId: string) => dispatch(pinRequest(status, accountId)); - api(getState)(`/api/v1/statuses/${status.id}/pin`, { method: 'POST' }).then(response => { - dispatch(importFetchedStatus(response.json)); + return getClient(getState()).statuses.pinStatus(status.id).then(response => { + dispatch(importFetchedStatus(response)); dispatch(pinSuccess(status, accountId)); }).catch(error => { dispatch(pinFail(status, error, accountId)); @@ -621,8 +618,8 @@ const unpin = (status: StatusEntity, accountId: string) => dispatch(unpinRequest(status, accountId)); - api(getState)(`/api/v1/statuses/${status.id}/unpin`, { method: 'POST' }).then(response => { - dispatch(importFetchedStatus(response.json)); + return getClient(getState()).statuses.unpinStatus(status.id).then(response => { + dispatch(importFetchedStatus(response)); dispatch(unpinSuccess(status, accountId)); }).catch(error => { dispatch(unpinFail(status, error, accountId)); @@ -668,7 +665,7 @@ const remoteInteraction = (ap_id: string, profile: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(remoteInteractionRequest(ap_id, profile)); - return api(getState)('/api/v1/pleroma/remote_interaction', { + return getClient(getState).request('/api/v1/pleroma/remote_interaction', { method: 'POST', body: JSON.stringify({ ap_id, profile }), }).then(({ json: data }) => { diff --git a/src/actions/lists.ts b/src/actions/lists.ts index 2184d24ef..ccd425d21 100644 --- a/src/actions/lists.ts +++ b/src/actions/lists.ts @@ -2,10 +2,11 @@ import { selectAccount } from 'soapbox/selectors'; import toast from 'soapbox/toast'; import { isLoggedIn } from 'soapbox/utils/auth'; -import api from '../api'; +import { getClient } from '../api'; import { importFetchedAccounts } from './importer'; +import type { Account, PaginatedResponse } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -56,21 +57,21 @@ const LIST_ADDER_LISTS_FETCH_REQUEST = 'LIST_ADDER_LISTS_FETCH_REQUEST'; const LIST_ADDER_LISTS_FETCH_SUCCESS = 'LIST_ADDER_LISTS_FETCH_SUCCESS'; const LIST_ADDER_LISTS_FETCH_FAIL = 'LIST_ADDER_LISTS_FETCH_FAIL'; -const fetchList = (id: string | number) => (dispatch: AppDispatch, getState: () => RootState) => { +const fetchList = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - if (getState().lists.get(String(id))) { + if (getState().lists.get(id)) { return; } dispatch(fetchListRequest(id)); - api(getState)(`/api/v1/lists/${id}`) - .then(({ json: data }) => dispatch(fetchListSuccess(data))) + return getClient(getState()).lists.getList(id) + .then((data) => dispatch(fetchListSuccess(data))) .catch(err => dispatch(fetchListFail(id, err))); }; -const fetchListRequest = (id: string | number) => ({ +const fetchListRequest = (id: string) => ({ type: LIST_FETCH_REQUEST, id, }); @@ -80,7 +81,7 @@ const fetchListSuccess = (list: APIEntity) => ({ list, }); -const fetchListFail = (id: string | number, error: unknown) => ({ +const fetchListFail = (id: string, error: unknown) => ({ type: LIST_FETCH_FAIL, id, error, @@ -91,8 +92,8 @@ const fetchLists = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchListsRequest()); - api(getState)('/api/v1/lists') - .then(({ json: data }) => dispatch(fetchListsSuccess(data))) + return getClient(getState()).lists.getLists() + .then((data) => dispatch(fetchListsSuccess(data))) .catch(err => dispatch(fetchListsFail(err))); }; @@ -121,7 +122,7 @@ const submitListEditor = (shouldReset?: boolean) => (dispatch: AppDispatch, getS } }; -const setupListEditor = (listId: string | number) => (dispatch: AppDispatch, getState: () => RootState) => { +const setupListEditor = (listId: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: LIST_EDITOR_SETUP, list: getState().lists.get(String(listId)), @@ -140,10 +141,7 @@ const createList = (title: string, shouldReset?: boolean) => (dispatch: AppDispa dispatch(createListRequest()); - api(getState)('/api/v1/lists', { - method: 'POST', - body: JSON.stringify({ title }), - }).then(({ json: data }) => { + return getClient(getState()).lists.createList({ title }).then((data) => { dispatch(createListSuccess(data)); if (shouldReset) { @@ -166,15 +164,12 @@ const createListFail = (error: unknown) => ({ error, }); -const updateList = (id: string | number, title: string, shouldReset?: boolean) => (dispatch: AppDispatch, getState: () => RootState) => { +const updateList = (id: string, title: string, shouldReset?: boolean) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; dispatch(updateListRequest(id)); - api(getState)(`/api/v1/lists/${id}`, { - method: 'PUT', - body: JSON.stringify({ title }), - }).then(({ json: data }) => { + return getClient(getState()).lists.updateList(id, { title }).then((data) => { dispatch(updateListSuccess(data)); if (shouldReset) { @@ -183,7 +178,7 @@ const updateList = (id: string | number, title: string, shouldReset?: boolean) = }).catch(err => dispatch(updateListFail(id, err))); }; -const updateListRequest = (id: string | number) => ({ +const updateListRequest = (id: string) => ({ type: LIST_UPDATE_REQUEST, id, }); @@ -193,7 +188,7 @@ const updateListSuccess = (list: APIEntity) => ({ list, }); -const updateListFail = (id: string | number, error: unknown) => ({ +const updateListFail = (id: string, error: unknown) => ({ type: LIST_UPDATE_FAIL, id, error, @@ -203,56 +198,56 @@ const resetListEditor = () => ({ type: LIST_EDITOR_RESET, }); -const deleteList = (id: string | number) => (dispatch: AppDispatch, getState: () => RootState) => { +const deleteList = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; dispatch(deleteListRequest(id)); - api(getState)(`/api/v1/lists/${id}`, { method: 'DELETE' }) + return getClient(getState()).lists.deleteList(id) .then(() => dispatch(deleteListSuccess(id))) .catch(err => dispatch(deleteListFail(id, err))); }; -const deleteListRequest = (id: string | number) => ({ +const deleteListRequest = (id: string) => ({ type: LIST_DELETE_REQUEST, id, }); -const deleteListSuccess = (id: string | number) => ({ +const deleteListSuccess = (id: string) => ({ type: LIST_DELETE_SUCCESS, id, }); -const deleteListFail = (id: string | number, error: unknown) => ({ +const deleteListFail = (id: string, error: unknown) => ({ type: LIST_DELETE_FAIL, id, error, }); -const fetchListAccounts = (listId: string | number) => (dispatch: AppDispatch, getState: () => RootState) => { +const fetchListAccounts = (listId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; dispatch(fetchListAccountsRequest(listId)); - api(getState)(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }).then(({ json: data }) => { - dispatch(importFetchedAccounts(data)); - dispatch(fetchListAccountsSuccess(listId, data, null)); + return getClient(getState()).lists.getListAccounts(listId).then(({ items, next }) => { + dispatch(importFetchedAccounts(items)); + dispatch(fetchListAccountsSuccess(listId, items, next)); }).catch(err => dispatch(fetchListAccountsFail(listId, err))); }; -const fetchListAccountsRequest = (id: string | number) => ({ +const fetchListAccountsRequest = (id: string) => ({ type: LIST_ACCOUNTS_FETCH_REQUEST, id, }); -const fetchListAccountsSuccess = (id: string | number, accounts: APIEntity[], next: string | null) => ({ +const fetchListAccountsSuccess = (id: string, accounts: Account[], next: (() => Promise>) | null) => ({ type: LIST_ACCOUNTS_FETCH_SUCCESS, id, accounts, next, }); -const fetchListAccountsFail = (id: string | number, error: unknown) => ({ +const fetchListAccountsFail = (id: string, error: unknown) => ({ type: LIST_ACCOUNTS_FETCH_FAIL, id, error, @@ -261,14 +256,7 @@ const fetchListAccountsFail = (id: string | number, error: unknown) => ({ const fetchListSuggestions = (q: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - const params = { - q, - resolve: false, - limit: 4, - following: true, - }; - - api(getState)('/api/v1/accounts/search', { params }).then(({ json: data }) => { + return getClient(getState()).accounts.searchAccounts(q, { resolve: false, limit: 4, following: true }).then((data) => { dispatch(importFetchedAccounts(data)); dispatch(fetchListSuggestionsReady(q, data)); }).catch(error => toast.showAlertForError(error)); @@ -293,32 +281,29 @@ const addToListEditor = (accountId: string) => (dispatch: AppDispatch, getState: dispatch(addToList(getState().listEditor.listId!, accountId)); }; -const addToList = (listId: string | number, accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { +const addToList = (listId: string, accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; dispatch(addToListRequest(listId, accountId)); - api(getState)(`/api/v1/lists/${listId}/accounts`, { - method: 'POST', - body: JSON.stringify({ account_ids: [accountId] }), - }) + return getClient(getState()).lists.addListAccounts(listId, [accountId]) .then(() => dispatch(addToListSuccess(listId, accountId))) .catch(err => dispatch(addToListFail(listId, accountId, err))); }; -const addToListRequest = (listId: string | number, accountId: string) => ({ +const addToListRequest = (listId: string, accountId: string) => ({ type: LIST_EDITOR_ADD_REQUEST, listId, accountId, }); -const addToListSuccess = (listId: string | number, accountId: string) => ({ +const addToListSuccess = (listId: string, accountId: string) => ({ type: LIST_EDITOR_ADD_SUCCESS, listId, accountId, }); -const addToListFail = (listId: string | number, accountId: string, error: APIEntity) => ({ +const addToListFail = (listId: string, accountId: string, error: APIEntity) => ({ type: LIST_EDITOR_ADD_FAIL, listId, accountId, @@ -329,32 +314,29 @@ const removeFromListEditor = (accountId: string) => (dispatch: AppDispatch, getS dispatch(removeFromList(getState().listEditor.listId!, accountId)); }; -const removeFromList = (listId: string | number, accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { +const removeFromList = (listId: string, accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; dispatch(removeFromListRequest(listId, accountId)); - api(getState)(`/api/v1/lists/${listId}/accounts`, { - method: 'DELETE', - body: JSON.stringify({ account_ids: [accountId] }), - }) + return getClient(getState()).lists.deleteListAccounts(listId, [accountId]) .then(() => dispatch(removeFromListSuccess(listId, accountId))) .catch(err => dispatch(removeFromListFail(listId, accountId, err))); }; -const removeFromListRequest = (listId: string | number, accountId: string) => ({ +const removeFromListRequest = (listId: string, accountId: string) => ({ type: LIST_EDITOR_REMOVE_REQUEST, listId, accountId, }); -const removeFromListSuccess = (listId: string | number, accountId: string) => ({ +const removeFromListSuccess = (listId: string, accountId: string) => ({ type: LIST_EDITOR_REMOVE_SUCCESS, listId, accountId, }); -const removeFromListFail = (listId: string | number, accountId: string, error: unknown) => ({ +const removeFromListFail = (listId: string, accountId: string, error: unknown) => ({ type: LIST_EDITOR_REMOVE_FAIL, listId, accountId, @@ -379,8 +361,8 @@ const fetchAccountLists = (accountId: string) => (dispatch: AppDispatch, getStat dispatch(fetchAccountListsRequest(accountId)); - api(getState)(`/api/v1/accounts/${accountId}/lists`) - .then(({ json: data }) => dispatch(fetchAccountListsSuccess(accountId, data))) + return getClient(getState()).accounts.getAccountLists(accountId) + .then((data) => dispatch(fetchAccountListsSuccess(accountId, data))) .catch(err => dispatch(fetchAccountListsFail(accountId, err))); }; @@ -401,11 +383,11 @@ const fetchAccountListsFail = (id: string, err: unknown) => ({ err, }); -const addToListAdder = (listId: string | number) => (dispatch: AppDispatch, getState: () => RootState) => { +const addToListAdder = (listId: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(addToList(listId, getState().listAdder.accountId!)); }; -const removeFromListAdder = (listId: string | number) => (dispatch: AppDispatch, getState: () => RootState) => { +const removeFromListAdder = (listId: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(removeFromList(listId, getState().listAdder.accountId!)); }; diff --git a/src/actions/markers.ts b/src/actions/markers.ts index 8a353eca1..3440b7bd3 100644 --- a/src/actions/markers.ts +++ b/src/actions/markers.ts @@ -1,4 +1,4 @@ -import api from '../api'; +import { getClient } from '../api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -14,9 +14,7 @@ const MARKER_SAVE_FAIL = 'MARKER_SAVE_FAIL'; const fetchMarker = (timeline: Array) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: MARKER_FETCH_REQUEST }); - return api(getState)('/api/v1/markers', { - params: { timeline }, - }).then(({ json: marker }) => { + return getClient(getState).timelines.getMarkers(timeline).then((marker) => { dispatch({ type: MARKER_FETCH_SUCCESS, marker }); }).catch(error => { dispatch({ type: MARKER_FETCH_FAIL, error }); @@ -26,10 +24,7 @@ const fetchMarker = (timeline: Array) => const saveMarker = (marker: APIEntity) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: MARKER_SAVE_REQUEST, marker }); - return api(getState)('/api/v1/markers', { - method: 'POST', - body: JSON.stringify(marker), - }).then(({ json: marker }) => { + return getClient(getState).timelines.saveMarkers(marker).then((marker) => { dispatch({ type: MARKER_SAVE_SUCCESS, marker }); }).catch(error => { dispatch({ type: MARKER_SAVE_FAIL, error }); diff --git a/src/actions/me.ts b/src/actions/me.ts index e9dcfae03..26c8c9ae5 100644 --- a/src/actions/me.ts +++ b/src/actions/me.ts @@ -5,7 +5,7 @@ import { setSentryAccount } from 'soapbox/sentry'; import KVStore from 'soapbox/storage/kv-store'; import { getAuthUserId, getAuthUserUrl } from 'soapbox/utils/auth'; -import api from '../api'; +import { getClient } from '../api'; import { loadCredentials } from './auth'; import { importFetchedAccount } from './importer'; @@ -72,23 +72,10 @@ const patchMe = (params: Record, isFormData = false) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(patchMeRequest()); - const headers: HeadersInit = isFormData ? { 'Content-Type': '' } : {}; - - let body: FormData | string; - if (isFormData) { - body = serialize(params, { indices: true }); - } else { - body = JSON.stringify(params); - } - - return api(getState)('/api/v1/accounts/update_credentials', { - method: 'PATCH', - body, - headers, - }) + return getClient(getState()).accounts.updateCredentials(params) .then(response => { - persistAuthAccount(response.json, params); - dispatch(patchMeSuccess(response.json)); + persistAuthAccount(response, params); + dispatch(patchMeSuccess(response)); }).catch(error => { dispatch(patchMeFail(error)); throw error; diff --git a/src/actions/media.ts b/src/actions/media.ts index 45c23ee45..2e4ce5217 100644 --- a/src/actions/media.ts +++ b/src/actions/media.ts @@ -6,8 +6,9 @@ import { getFeatures } from 'soapbox/utils/features'; import { formatBytes, getVideoDuration } from 'soapbox/utils/media'; import resizeImage from 'soapbox/utils/resize-image'; -import api from '../api'; +import { getClient } from '../api'; +import type { UploadMediaParams } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -19,28 +20,13 @@ const messages = defineMessages({ const noOp = (e: any) => {}; -const fetchMedia = (mediaId: string) => - (dispatch: any, getState: () => RootState) => api(getState)(`/api/v1/media/${mediaId}`); - const updateMedia = (mediaId: string, params: Record) => - (dispatch: any, getState: () => RootState) => api(getState)(`/api/v1/media/${mediaId}`, { - method: 'PUT', - body: JSON.stringify(params), - }); + (dispatch: any, getState: () => RootState) => + getClient(getState()).media.updateMedia(mediaId, params); -const uploadMedia = (body: FormData, onUploadProgress: (e: ProgressEvent) => void = noOp) => - (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const instance = state.instance; - const features = getFeatures(instance); - - return api(getState)(features.mediaV2 ? '/api/v2/media' : '/api/v1/media', { - method: 'POST', - body, - headers: { 'Content-Type': '' }, - onUploadProgress, - }); - }; +const uploadMedia = (body: UploadMediaParams, onUploadProgress: (e: ProgressEvent) => void = noOp) => + (dispatch: AppDispatch, getState: () => RootState) => + getClient(getState()).media.uploadMedia(body, onUploadProgress); const uploadFile = ( file: File, @@ -85,18 +71,18 @@ const uploadFile = ( // Account for disparity in size of original image and resized data changeTotal(resized.size - file.size); - return dispatch(uploadMedia(data, onProgress)) - .then(({ status, json }) => { + return dispatch(uploadMedia({ file: resized }, onProgress)) + .then((data) => { // If server-side processing of the media attachment has not completed yet, // poll the server until it is, before showing the media attachment as uploaded - if (status === 200) { - onSuccess(json); - } else if (status === 202) { + if (data.url) { + onSuccess(data); + } else if (data.url === null) { const poll = () => { - dispatch(fetchMedia(json.id)).then(({ status, data }) => { - if (status === 200) { - onSuccess(json); - } else if (status === 206) { + getClient(getState()).media.getMedia(data.id).then((data) => { + if (data.url) { + onSuccess(data); + } else if (data.url === null) { setTimeout(() => poll(), 1000); } }).catch(error => onFail(error)); @@ -109,7 +95,6 @@ const uploadFile = ( }; export { - fetchMedia, updateMedia, uploadMedia, uploadFile, diff --git a/src/actions/mfa.ts b/src/actions/mfa.ts index ca4b562b3..198a9d746 100644 --- a/src/actions/mfa.ts +++ b/src/actions/mfa.ts @@ -1,4 +1,4 @@ -import api from '../api'; +import { getClient } from '../api'; import type { AppDispatch, RootState } from 'soapbox/store'; @@ -25,7 +25,7 @@ const MFA_DISABLE_FAIL = 'MFA_DISABLE_FAIL'; const fetchMfa = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: MFA_FETCH_REQUEST }); - return api(getState)('/api/pleroma/accounts/mfa').then(({ json: data }) => { + return getClient(getState).request('/api/pleroma/accounts/mfa').then(({ json: data }) => { dispatch({ type: MFA_FETCH_SUCCESS, data }); }).catch(() => { dispatch({ type: MFA_FETCH_FAIL }); @@ -35,7 +35,7 @@ const fetchMfa = () => const fetchBackupCodes = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: MFA_BACKUP_CODES_FETCH_REQUEST }); - return api(getState)('/api/pleroma/accounts/mfa/backup_codes').then(({ json: data }) => { + return getClient(getState).request('/api/pleroma/accounts/mfa/backup_codes').then(({ json: data }) => { dispatch({ type: MFA_BACKUP_CODES_FETCH_SUCCESS, data }); return data; }).catch(() => { @@ -46,7 +46,7 @@ const fetchBackupCodes = () => const setupMfa = (method: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: MFA_SETUP_REQUEST, method }); - return api(getState)(`/api/pleroma/accounts/mfa/setup/${method}`).then(({ json: data }) => { + return getClient(getState).request(`/api/pleroma/accounts/mfa/setup/${method}`).then(({ json: data }) => { dispatch({ type: MFA_SETUP_SUCCESS, data }); return data; }).catch((error: unknown) => { @@ -59,9 +59,8 @@ const confirmMfa = (method: string, code: string, password: string) => (dispatch: AppDispatch, getState: () => RootState) => { const params = { code, password }; dispatch({ type: MFA_CONFIRM_REQUEST, method, code }); - return api(getState)(`/api/pleroma/accounts/mfa/confirm/${method}`, { - method: 'POST', - body: JSON.stringify(params), + return getClient(getState).request(`/api/pleroma/accounts/mfa/confirm/${method}`, { + method: 'POST', body: params, }).then(({ json: data }) => { dispatch({ type: MFA_CONFIRM_SUCCESS, method, code }); return data; @@ -74,9 +73,8 @@ const confirmMfa = (method: string, code: string, password: string) => const disableMfa = (method: string, password: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: MFA_DISABLE_REQUEST, method }); - return api(getState)(`/api/pleroma/accounts/mfa/${method}`, { - method: 'DELETE', - body: JSON.stringify({ password }), + return getClient(getState).request(`/api/pleroma/accounts/mfa/${method}`, { + method: 'DELETE', body: { password }, }).then(({ json: data }) => { dispatch({ type: MFA_DISABLE_SUCCESS, method }); return data; diff --git a/src/actions/notifications.ts b/src/actions/notifications.ts index 539bdbf80..71e518919 100644 --- a/src/actions/notifications.ts +++ b/src/actions/notifications.ts @@ -2,7 +2,7 @@ import IntlMessageFormat from 'intl-messageformat'; import 'intl-pluralrules'; import { defineMessages } from 'react-intl'; -import api, { getNextLink } from 'soapbox/api'; +import { getClient } from 'soapbox/api'; import { getFilters, regexFromFilters } from 'soapbox/selectors'; import { isLoggedIn } from 'soapbox/utils/auth'; import { compareId } from 'soapbox/utils/comparators'; @@ -21,6 +21,7 @@ import { import { saveMarker } from './markers'; import { getSettings, saveSettings } from './settings'; +import type { Account, Notification, PaginatedResponse } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -271,10 +272,9 @@ const expandNotifications = ({ maxId }: Record = {}, done: () => an dispatch(expandNotificationsRequest(isLoadingMore)); - return api(getState)('/api/v1/notifications', { params, signal: abortExpandNotifications.signal }).then(response => { - const next = getNextLink(response); + return getClient(state).notifications.getNotifications(params).then(response => { // WIP signal - const entries = (response.json as APIEntity[]).reduce((acc, item) => { + const entries = (response.items).reduce((acc, item) => { if (item.account?.id) { acc.accounts[item.account.id] = item.account; } @@ -289,15 +289,15 @@ const expandNotifications = ({ maxId }: Record = {}, done: () => an } return acc; - }, { accounts: {}, statuses: {} }); + }, { accounts: {}, statuses: {} } as { accounts: Record; statuses: Record }); dispatch(importFetchedAccounts(Object.values(entries.accounts))); dispatch(importFetchedStatuses(Object.values(entries.statuses))); - const deduplicatedNotifications = deduplicateNotifications(response.json); + const deduplicatedNotifications = deduplicateNotifications(response.items); - dispatch(expandNotificationsSuccess(deduplicatedNotifications, next || null, isLoadingMore)); - fetchRelatedRelationships(dispatch, response.json); + dispatch(expandNotificationsSuccess(deduplicatedNotifications, response.next, isLoadingMore)); + fetchRelatedRelationships(dispatch, response.items); done(); }).catch(error => { dispatch(expandNotificationsFail(error, isLoadingMore)); @@ -310,7 +310,7 @@ const expandNotificationsRequest = (isLoadingMore: boolean) => ({ skipLoading: !isLoadingMore, }); -const expandNotificationsSuccess = (notifications: APIEntity[], next: string | null, isLoadingMore: boolean) => ({ +const expandNotificationsSuccess = (notifications: APIEntity[], next: (() => Promise>) | null, isLoadingMore: boolean) => ({ type: NOTIFICATIONS_EXPAND_SUCCESS, notifications, next, @@ -323,17 +323,6 @@ const expandNotificationsFail = (error: unknown, isLoadingMore: boolean) => ({ skipLoading: !isLoadingMore, }); -const clearNotifications = () => - (dispatch: AppDispatch, getState: () => RootState) => { - if (!isLoggedIn(getState)) return; - - dispatch({ - type: NOTIFICATIONS_CLEAR, - }); - - api(getState)('/api/v1/notifications/clear', { method: 'POST' }); - }; - const scrollTopNotifications = (top: boolean) => (dispatch: AppDispatch) => { dispatch({ @@ -358,9 +347,9 @@ const setFilter = (filterType: FilterType, abort?: boolean) => // https://git.pleroma.social/pleroma/pleroma/-/issues/2769 const markReadPleroma = (max_id: string | number) => (dispatch: AppDispatch, getState: () => RootState) => - api(getState)('/api/v1/pleroma/notifications/read', { + getClient(getState).request('/api/v1/pleroma/notifications/read', { method: 'POST', - body: JSON.stringify({ max_id }), + body: { max_id }, }); const markReadNotifications = () => @@ -414,7 +403,6 @@ export { expandNotificationsRequest, expandNotificationsSuccess, expandNotificationsFail, - clearNotifications, scrollTopNotifications, setFilter, markReadPleroma, diff --git a/src/actions/oauth.ts b/src/actions/oauth.ts index 00c82382a..56b9ebb27 100644 --- a/src/actions/oauth.ts +++ b/src/actions/oauth.ts @@ -6,9 +6,10 @@ * @see module:soapbox/actions/auth */ -import { getBaseURL } from 'soapbox/utils/state'; +import { PlApiClient } from 'pl-api'; -import { getFetch } from '../api'; +import * as BuildConfig from 'soapbox/build-config'; +import { getBaseURL } from 'soapbox/utils/state'; import type { AppDispatch, RootState } from 'soapbox/store'; @@ -23,10 +24,9 @@ const OAUTH_TOKEN_REVOKE_FAIL = 'OAUTH_TOKEN_REVOKE_FAIL'; const obtainOAuthToken = (params: Record, baseURL?: string) => (dispatch: AppDispatch) => { dispatch({ type: OAUTH_TOKEN_CREATE_REQUEST, params }); - return getFetch(null, baseURL)('/oauth/token', { - method: 'POST', - body: JSON.stringify(params), - }).then(({ json: token }) => { + const client = new PlApiClient(baseURL || BuildConfig.BACKEND_URL || '', undefined, { fetchInstance: false }); + + return client.oauth.getToken(params).then((token) => { dispatch({ type: OAUTH_TOKEN_CREATE_SUCCESS, params, token }); return token; }).catch(error => { @@ -39,10 +39,8 @@ const revokeOAuthToken = (params: Record) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: OAUTH_TOKEN_REVOKE_REQUEST, params }); const baseURL = getBaseURL(getState()); - return getFetch(null, baseURL)('/oauth/revoke', { - method: 'POST', - body: JSON.stringify(params), - }).then(({ json: data }) => { + const client = new PlApiClient(baseURL || '', undefined, { fetchInstance: false }); + return client.oauth.revokeToken(params).then((data) => { dispatch({ type: OAUTH_TOKEN_REVOKE_SUCCESS, params, data }); return data; }).catch(error => { diff --git a/src/actions/pin-statuses.ts b/src/actions/pin-statuses.ts index bea53da63..dec240f25 100644 --- a/src/actions/pin-statuses.ts +++ b/src/actions/pin-statuses.ts @@ -1,6 +1,6 @@ import { isLoggedIn } from 'soapbox/utils/auth'; -import api from '../api'; +import { getClient } from '../api'; import { importFetchedStatuses } from './importer'; @@ -18,9 +18,9 @@ const fetchPinnedStatuses = () => dispatch(fetchPinnedStatusesRequest()); - api(getState)(`/api/v1/accounts/${me}/statuses`, { params: { pinned: true } }).then(response => { - dispatch(importFetchedStatuses(response.json)); - dispatch(fetchPinnedStatusesSuccess(response.json, null)); + return getClient(getState()).accounts.getAccountStatuses(me as string, { pinned: true }).then(response => { + dispatch(importFetchedStatuses(response.items)); + dispatch(fetchPinnedStatusesSuccess(response.items, null)); }).catch(error => { dispatch(fetchPinnedStatusesFail(error)); }); diff --git a/src/actions/polls.ts b/src/actions/polls.ts index 5d7cdee59..959a0f317 100644 --- a/src/actions/polls.ts +++ b/src/actions/polls.ts @@ -1,4 +1,4 @@ -import api from '../api'; +import { getClient } from '../api'; import { importFetchedPoll } from './importer'; @@ -13,14 +13,11 @@ const POLL_FETCH_REQUEST = 'POLL_FETCH_REQUEST'; const POLL_FETCH_SUCCESS = 'POLL_FETCH_SUCCESS'; const POLL_FETCH_FAIL = 'POLL_FETCH_FAIL'; -const vote = (pollId: string, choices: string[]) => +const vote = (pollId: string, choices: number[]) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(voteRequest()); - api(getState)(`/api/v1/polls/${pollId}/votes`, { - method: 'POST', - body: JSON.stringify({ choices }), - }).then(({ json: data }) => { + return getClient(getState()).polls.vote(pollId, choices).then((data) => { dispatch(importFetchedPoll(data)); dispatch(voteSuccess(data)); }).catch(err => dispatch(voteFail(err))); @@ -30,8 +27,7 @@ const fetchPoll = (pollId: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchPollRequest()); - api(getState)(`/api/v1/polls/${pollId}`) - .then(({ json: data }) => { + return getClient(getState()).polls.getPoll(pollId).then((data) => { dispatch(importFetchedPoll(data)); dispatch(fetchPollSuccess(data)); }) diff --git a/src/actions/push-notifications/registerer.ts b/src/actions/push-notifications/registerer.ts index 48a0bc8fd..5e2dfd9c7 100644 --- a/src/actions/push-notifications/registerer.ts +++ b/src/actions/push-notifications/registerer.ts @@ -44,7 +44,7 @@ const unsubscribe = ({ registration, subscription }: { const sendSubscriptionToBackend = (subscription: PushSubscription, me: Me) => (dispatch: AppDispatch, getState: () => RootState) => { - const alerts = getState().push_notifications.alerts.toJS(); + const alerts = getState().push_notifications.alerts.toJS() as Record; const params = { subscription, data: { alerts } }; if (me) { diff --git a/src/actions/push-subscriptions.ts b/src/actions/push-subscriptions.ts index c2a43da27..f3777664e 100644 --- a/src/actions/push-subscriptions.ts +++ b/src/actions/push-subscriptions.ts @@ -1,4 +1,4 @@ -import api from '../api'; +import { getClient } from '../api'; const PUSH_SUBSCRIPTION_CREATE_REQUEST = 'PUSH_SUBSCRIPTION_CREATE_REQUEST'; const PUSH_SUBSCRIPTION_CREATE_SUCCESS = 'PUSH_SUBSCRIPTION_CREATE_SUCCESS'; @@ -21,10 +21,8 @@ import type { AppDispatch, RootState } from 'soapbox/store'; const createPushSubscription = (params: Record) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: PUSH_SUBSCRIPTION_CREATE_REQUEST, params }); - return api(getState)('/api/v1/push/subscription', { - method: 'POST', - body: JSON.stringify(params), - }).then(({ json: subscription }) => + return getClient(getState).pushNotifications.createSubscription(params) + .then((subscription) => dispatch({ type: PUSH_SUBSCRIPTION_CREATE_SUCCESS, params, subscription }), ).catch(error => dispatch({ type: PUSH_SUBSCRIPTION_CREATE_FAIL, params, error }), @@ -34,7 +32,7 @@ const createPushSubscription = (params: Record) => const fetchPushSubscription = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: PUSH_SUBSCRIPTION_FETCH_REQUEST }); - return api(getState)('/api/v1/push/subscription').then(({ json: subscription }) => + return getClient(getState).pushNotifications.getSubscription().then((subscription) => dispatch({ type: PUSH_SUBSCRIPTION_FETCH_SUCCESS, subscription }), ).catch(error => dispatch({ type: PUSH_SUBSCRIPTION_FETCH_FAIL, error }), @@ -44,10 +42,7 @@ const fetchPushSubscription = () => const updatePushSubscription = (params: Record) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: PUSH_SUBSCRIPTION_UPDATE_REQUEST, params }); - return api(getState)('/api/v1/push/subscription', { - method: 'PUT', - body: JSON.stringify(params), - }).then(({ json: subscription }) => + return getClient(getState).pushNotifications.updateSubscription(params).then((subscription) => dispatch({ type: PUSH_SUBSCRIPTION_UPDATE_SUCCESS, params, subscription }), ).catch(error => dispatch({ type: PUSH_SUBSCRIPTION_UPDATE_FAIL, params, error }), @@ -57,7 +52,7 @@ const updatePushSubscription = (params: Record) => const deletePushSubscription = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: PUSH_SUBSCRIPTION_DELETE_REQUEST }); - return api(getState)('/api/v1/push/subscription', { method: 'DELETE' }).then(() => + return getClient(getState).pushNotifications.deleteSubscription().then(() => dispatch({ type: PUSH_SUBSCRIPTION_DELETE_SUCCESS }), ).catch(error => dispatch({ type: PUSH_SUBSCRIPTION_DELETE_FAIL, error }), diff --git a/src/actions/reports.ts b/src/actions/reports.ts index b7bd9f7bf..7639e7966 100644 --- a/src/actions/reports.ts +++ b/src/actions/reports.ts @@ -1,4 +1,4 @@ -import api from '../api'; +import { getClient } from '../api'; import { openModal } from './modals'; @@ -61,18 +61,13 @@ const submitReport = () => dispatch(submitReportRequest()); const { reports } = getState(); - return api(getState)('/api/v1/reports', { - method: 'POST', - body: JSON.stringify({ - account_id: reports.getIn(['new', 'account_id']), - status_ids: reports.getIn(['new', 'status_ids']), - message_ids: [reports.getIn(['new', 'chat_message', 'id'])].filter(Boolean), - group_id: reports.getIn(['new', 'group', 'id']), - rule_ids: reports.getIn(['new', 'rule_ids']), - comment: reports.getIn(['new', 'comment']), - forward: reports.getIn(['new', 'forward']), - }), - }); + return getClient(getState()).accounts.reportAccount(reports.new.account_id!, { + status_ids: reports.new.status_ids.toArray(), + // group_id: reports.getIn(['new', 'group', 'id']), + rule_ids: reports.new.rule_ids.toArray(), + comment: reports.new.comment, + forward: reports.new.forward, + }) }; const submitReportRequest = () => ({ diff --git a/src/actions/scheduled-statuses.ts b/src/actions/scheduled-statuses.ts index b33cc1e8f..d19baaaff 100644 --- a/src/actions/scheduled-statuses.ts +++ b/src/actions/scheduled-statuses.ts @@ -1,7 +1,8 @@ import { getFeatures } from 'soapbox/utils/features'; -import api, { getNextLink } from '../api'; +import { getClient, getNextLink } from '../api'; +import type { PaginatedResponse, ScheduledStatus } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -32,9 +33,8 @@ const fetchScheduledStatuses = () => dispatch(fetchScheduledStatusesRequest()); - api(getState)('/api/v1/scheduled_statuses').then(response => { - const next = getNextLink(response); - dispatch(fetchScheduledStatusesSuccess(response.json, next || null)); + return getClient(getState()).scheduledStatuses.getScheduledStatuses().then(({ next, items }) => { + dispatch(fetchScheduledStatusesSuccess(items, next)); }).catch(error => { dispatch(fetchScheduledStatusesFail(error)); }); @@ -43,7 +43,7 @@ const fetchScheduledStatuses = () => const cancelScheduledStatus = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: SCHEDULED_STATUS_CANCEL_REQUEST, id }); - api(getState)(`/api/v1/scheduled_statuses/${id}`, { method: 'DELETE' }).then(({ json: data }) => { + return getClient(getState()).scheduledStatuses.cancelScheduledStatus(id).then((data) => { dispatch({ type: SCHEDULED_STATUS_CANCEL_SUCCESS, id, data }); }).catch(error => { dispatch({ type: SCHEDULED_STATUS_CANCEL_FAIL, id, error }); @@ -54,7 +54,7 @@ const fetchScheduledStatusesRequest = () => ({ type: SCHEDULED_STATUSES_FETCH_REQUEST, }); -const fetchScheduledStatusesSuccess = (statuses: APIEntity[], next: string | null) => ({ +const fetchScheduledStatusesSuccess = (statuses: APIEntity[], next: (() => Promise>) | null) => ({ type: SCHEDULED_STATUSES_FETCH_SUCCESS, statuses, next, @@ -67,17 +67,16 @@ const fetchScheduledStatusesFail = (error: unknown) => ({ const expandScheduledStatuses = () => (dispatch: AppDispatch, getState: () => RootState) => { - const url = getState().status_lists.get('scheduled_statuses')?.next || null; + const next = getState().status_lists.get('scheduled_statuses')?.next || null; - if (url === null || getState().status_lists.get('scheduled_statuses')?.isLoading) { + if (next === null || getState().status_lists.get('scheduled_statuses')?.isLoading) { return; } dispatch(expandScheduledStatusesRequest()); - api(getState)(url).then(response => { - const next = getNextLink(response); - dispatch(expandScheduledStatusesSuccess(response.json, next || null)); + next().then(response => { + dispatch(expandScheduledStatusesSuccess(response.items, response.next)); }).catch(error => { dispatch(expandScheduledStatusesFail(error)); }); @@ -87,7 +86,7 @@ const expandScheduledStatusesRequest = () => ({ type: SCHEDULED_STATUSES_EXPAND_REQUEST, }); -const expandScheduledStatusesSuccess = (statuses: APIEntity[], next: string | null) => ({ +const expandScheduledStatusesSuccess = (statuses: APIEntity[], next: (() => Promise>) | null) => ({ type: SCHEDULED_STATUSES_EXPAND_SUCCESS, statuses, next, diff --git a/src/actions/search.ts b/src/actions/search.ts index fc689ea03..8281a0a19 100644 --- a/src/actions/search.ts +++ b/src/actions/search.ts @@ -1,26 +1,27 @@ -import api, { getNextLink } from '../api'; +import { getClient } from '../api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts, importFetchedStatuses } from './importer'; +import type { Search } from 'pl-api'; import type { SearchFilter } from 'soapbox/reducers/search'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; -const SEARCH_CHANGE = 'SEARCH_CHANGE'; -const SEARCH_CLEAR = 'SEARCH_CLEAR'; -const SEARCH_SHOW = 'SEARCH_SHOW'; +const SEARCH_CHANGE = 'SEARCH_CHANGE'; +const SEARCH_CLEAR = 'SEARCH_CLEAR'; +const SEARCH_SHOW = 'SEARCH_SHOW'; const SEARCH_RESULTS_CLEAR = 'SEARCH_RESULTS_CLEAR'; const SEARCH_FETCH_REQUEST = 'SEARCH_FETCH_REQUEST'; const SEARCH_FETCH_SUCCESS = 'SEARCH_FETCH_SUCCESS'; -const SEARCH_FETCH_FAIL = 'SEARCH_FETCH_FAIL'; +const SEARCH_FETCH_FAIL = 'SEARCH_FETCH_FAIL'; const SEARCH_FILTER_SET = 'SEARCH_FILTER_SET'; const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST'; const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS'; -const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL'; +const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL'; const SEARCH_ACCOUNT_SET = 'SEARCH_ACCOUNT_SET'; @@ -63,29 +64,24 @@ const submitSearch = (filter?: SearchFilter) => dispatch(fetchSearchRequest(value)); const params: Record = { - q: value, resolve: true, limit: 20, - type, + type: type as any, }; if (accountId) params.account_id = accountId; - api(getState)('/api/v2/search', { - params, - }).then(response => { - if (response.json.accounts) { - dispatch(importFetchedAccounts(response.json.accounts)); + return getClient(getState()).search.search(value, params).then(response => { + if (response.accounts) { + dispatch(importFetchedAccounts(response.accounts)); } - if (response.json.statuses) { - dispatch(importFetchedStatuses(response.json.statuses)); + if (response.statuses) { + dispatch(importFetchedStatuses(response.statuses)); } - const next = getNextLink(response); - - dispatch(fetchSearchSuccess(response.json, value, type, next || null)); - dispatch(fetchRelationships(response.json.accounts.map((item: APIEntity) => item.id))); + dispatch(fetchSearchSuccess(response, value, type)); + dispatch(fetchRelationships(response.accounts.map((item: APIEntity) => item.id))); }).catch(error => { dispatch(fetchSearchFail(error)); }); @@ -96,12 +92,11 @@ const fetchSearchRequest = (value: string) => ({ value, }); -const fetchSearchSuccess = (results: APIEntity[], searchTerm: string, searchType: SearchFilter, next: string | null) => ({ +const fetchSearchSuccess = (results: Search, searchTerm: string, searchType: SearchFilter) => ({ type: SEARCH_FETCH_SUCCESS, results, searchTerm, searchType, - next, }); const fetchSearchFail = (error: unknown) => ({ @@ -127,38 +122,24 @@ const expandSearch = (type: SearchFilter) => (dispatch: AppDispatch, getState: ( dispatch(expandSearchRequest(type)); - let url = getState().search.next as string; - let params: Record = {}; + let params: Record = { + type, + offset, + }; + if (accountId) params.account_id = accountId; - // if no URL was extracted from the Link header, - // fall back on querying with the offset - if (!url) { - url = '/api/v2/search'; - params = { - q: value, - type, - offset, - }; - if (accountId) params.account_id = accountId; - } + return getClient(getState()).search.search(value, params).then(response => { - api(getState)(url, { - params, - }).then(response => { - const data = response.json; - - if (data.accounts) { - dispatch(importFetchedAccounts(data.accounts)); + if (response.accounts) { + dispatch(importFetchedAccounts(response.accounts)); } - if (data.statuses) { - dispatch(importFetchedStatuses(data.statuses)); + if (response.statuses) { + dispatch(importFetchedStatuses(response.statuses)); } - const next = getNextLink(response); - - dispatch(expandSearchSuccess(data, value, type, next || null)); - dispatch(fetchRelationships(data.accounts.map((item: APIEntity) => item.id))); + dispatch(expandSearchSuccess(response, value, type)); + dispatch(fetchRelationships(response.accounts.map((item: APIEntity) => item.id))); }).catch(error => { dispatch(expandSearchFail(error)); }); @@ -169,12 +150,11 @@ const expandSearchRequest = (searchType: SearchFilter) => ({ searchType, }); -const expandSearchSuccess = (results: APIEntity[], searchTerm: string, searchType: SearchFilter, next: string | null) => ({ +const expandSearchSuccess = (results: Search, searchTerm: string, searchType: SearchFilter) => ({ type: SEARCH_EXPAND_SUCCESS, results, searchTerm, searchType, - next, }); const expandSearchFail = (error: unknown) => ({ diff --git a/src/actions/security.ts b/src/actions/security.ts index dcc26c22d..9cbe6224b 100644 --- a/src/actions/security.ts +++ b/src/actions/security.ts @@ -4,7 +4,7 @@ * @see module:soapbox/actions/auth */ -import api from 'soapbox/api'; +import { getClient } from 'soapbox/api'; import toast from 'soapbox/toast'; import { getLoggedInAccount } from 'soapbox/utils/auth'; import { GOTOSOCIAL, parseVersion } from 'soapbox/utils/features'; @@ -49,7 +49,7 @@ const MOVE_ACCOUNT_FAIL = 'MOVE_ACCOUNT_FAIL'; const fetchOAuthTokens = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: FETCH_TOKENS_REQUEST }); - return api(getState)('/api/oauth_tokens').then(({ json: tokens }) => { + return getClient(getState).request('/api/oauth_tokens').then(({ json: tokens }) => { dispatch({ type: FETCH_TOKENS_SUCCESS, tokens }); }).catch(() => { dispatch({ type: FETCH_TOKENS_FAIL }); @@ -59,7 +59,7 @@ const fetchOAuthTokens = () => const revokeOAuthTokenById = (id: number) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: REVOKE_TOKEN_REQUEST, id }); - return api(getState)(`/api/oauth_tokens/${id}`, { method: 'DELETE' }).then(() => { + return getClient(getState).request(`/api/oauth_tokens/${id}`, { method: 'DELETE' }).then(() => { dispatch({ type: REVOKE_TOKEN_SUCCESS, id }); }).catch(() => { dispatch({ type: REVOKE_TOKEN_FAIL, id }); @@ -74,12 +74,12 @@ const changePassword = (oldPassword: string, newPassword: string, confirmation: const v = parseVersion(instance.version); if (v.software === GOTOSOCIAL) { - return api(getState)('/api/v1/user/password_change', { + return getClient(getState).request('/api/v1/user/password_change', { method: 'POST', - body: JSON.stringify({ + body: { old_password: oldPassword, new_password: newPassword, - }), + }, }).then(response => { dispatch({ type: CHANGE_PASSWORD_SUCCESS, response }); }).catch(error => { @@ -87,13 +87,13 @@ const changePassword = (oldPassword: string, newPassword: string, confirmation: throw error; }); } else { - return api(getState)('/api/pleroma/change_password', { + return getClient(getState).request('/api/pleroma/change_password', { method: 'POST', - body: JSON.stringify({ + body: { password: oldPassword, new_password: newPassword, new_password_confirmation: confirmation, - }), + }, }).then(response => { if (response.json.error) throw response.json.error; // This endpoint returns HTTP 200 even on failure dispatch({ type: CHANGE_PASSWORD_SUCCESS, response }); @@ -115,9 +115,8 @@ const resetPassword = (usernameOrEmail: string) => ? { email: input } : { nickname: input, username: input }; - return api(getState)('/auth/password', { - method: 'POST', - body: JSON.stringify(params), + return getClient(getState).request('/auth/password', { + method: 'POST', body: params, }).then(() => { dispatch({ type: RESET_PASSWORD_SUCCESS }); }).catch(error => { @@ -133,12 +132,12 @@ const changeEmail = (email: string, password: string) => const instance = state.instance; const v = parseVersion(instance.version); - return api(getState)(v.software === GOTOSOCIAL ? '/api/v1/user/email_change' : '/api/pleroma/change_email', { + return getClient(getState).request(v.software === GOTOSOCIAL ? '/api/v1/user/email_change' : '/api/pleroma/change_email', { method: 'POST', - body: JSON.stringify({ + body: { [v.software === GOTOSOCIAL ? 'new_email' : 'email']: email, password, - }), + }, }).then(response => { if (response.json.error) throw response.json.error; // This endpoint returns HTTP 200 even on failure dispatch({ type: CHANGE_EMAIL_SUCCESS, email, response }); @@ -157,9 +156,8 @@ const deleteAccount = (password: string) => const v = parseVersion(instance.version); dispatch({ type: DELETE_ACCOUNT_REQUEST }); - return api(getState)(v.software === GOTOSOCIAL ? '/api/v1/accounts/delete' : '/api/pleroma/delete_account', { - method: 'POST', - body: JSON.stringify({ password }), + return getClient(getState).request(v.software === GOTOSOCIAL ? '/api/v1/accounts/delete' : '/api/pleroma/delete_account', { + method: 'POST', body: { password }, }).then(response => { if (response.json.error) throw response.json.error; // This endpoint returns HTTP 200 even on failure dispatch({ type: DELETE_ACCOUNT_SUCCESS, response }); @@ -174,12 +172,9 @@ const deleteAccount = (password: string) => const moveAccount = (targetAccount: string, password: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: MOVE_ACCOUNT_REQUEST }); - return api(getState)('/api/pleroma/move_account', { + return getClient(getState).request('/api/pleroma/move_account', { method: 'POST', - body: JSON.stringify({ - password, - target_account: targetAccount, - }), + body: { password, target_account: targetAccount }, }).then(response => { if (response.json.error) throw response.json.error; // This endpoint returns HTTP 200 even on failure dispatch({ type: MOVE_ACCOUNT_SUCCESS, response }); diff --git a/src/actions/soapbox.ts b/src/actions/soapbox.ts index 79b0b7101..fcdf19625 100644 --- a/src/actions/soapbox.ts +++ b/src/actions/soapbox.ts @@ -3,10 +3,9 @@ import { createSelector } from 'reselect'; import { getHost } from 'soapbox/actions/instance'; import { normalizeSoapboxConfig } from 'soapbox/normalizers'; import KVStore from 'soapbox/storage/kv-store'; -import { removeVS16s } from 'soapbox/utils/emoji'; import { getFeatures } from 'soapbox/utils/features'; -import api, { staticFetch } from '../api'; +import { getClient, staticFetch } from '../api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -29,12 +28,6 @@ const getSoapboxConfig = createSelector([ if (soapbox.get('displayFqn') === undefined) { soapboxConfig.set('displayFqn', features.federating); } - - // If RGI reacts aren't supported, strip VS16s - // https://git.pleroma.social/pleroma/pleroma/-/issues/2355 - if (features.emojiReactsNonRGI) { - soapboxConfig.set('allowedEmoji', soapboxConfig.allowedEmoji.map(removeVS16s)); - } }); }); @@ -51,7 +44,7 @@ const rememberSoapboxConfig = (host: string | null) => const fetchFrontendConfigurations = () => (dispatch: AppDispatch, getState: () => RootState) => - api(getState)('/api/pleroma/frontend_configurations') + getClient(getState).request('/api/pleroma/frontend_configurations') .then(({ json: data }) => data); /** Conditionally fetches Soapbox config depending on backend features */ diff --git a/src/actions/status-quotes.ts b/src/actions/status-quotes.ts index 796b46828..d9919a3c9 100644 --- a/src/actions/status-quotes.ts +++ b/src/actions/status-quotes.ts @@ -1,4 +1,4 @@ -import api, { getNextLink } from '../api'; +import { getClient, getNextLink } from '../api'; import { importFetchedStatuses } from './importer'; @@ -25,7 +25,7 @@ const fetchStatusQuotes = (statusId: string) => type: STATUS_QUOTES_FETCH_REQUEST, }); - return api(getState)(`/api/v1/pleroma/statuses/${statusId}/quotes`).then(response => { + return getClient(getState).request(`/api/v1/pleroma/statuses/${statusId}/quotes`).then(response => { const next = getNextLink(response); dispatch(importFetchedStatuses(response.json)); return dispatch({ @@ -56,7 +56,7 @@ const expandStatusQuotes = (statusId: string) => statusId, }); - return api(getState)(url).then(response => { + return getClient(getState).request(url).then(response => { const next = getNextLink(response); dispatch(importFetchedStatuses(response.json)); dispatch({ diff --git a/src/actions/statuses.ts b/src/actions/statuses.ts index d88cdc904..64dffaf82 100644 --- a/src/actions/statuses.ts +++ b/src/actions/statuses.ts @@ -2,7 +2,7 @@ import { isLoggedIn } from 'soapbox/utils/auth'; import { getFeatures } from 'soapbox/utils/features'; import { shouldHaveCard } from 'soapbox/utils/status'; -import api from '../api'; +import { getClient } from '../api'; import { setComposeToStatus } from './compose'; import { importFetchedStatus, importFetchedStatuses } from './importer'; @@ -61,11 +61,8 @@ const createStatus = (params: Record, idempotencyKey: string, statu (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: STATUS_CREATE_REQUEST, params, idempotencyKey, editing: !!statusId }); - return api(getState)(statusId === null ? '/api/v1/statuses' : `/api/v1/statuses/${statusId}`, { - method: statusId === null ? 'POST' : 'PUT', - body: JSON.stringify(params), - headers: { 'Idempotency-Key': idempotencyKey }, - }).then(({ json: status }) => { + return (statusId === null ? getClient(getState()).statuses.createStatus(params) : getClient(getState()).statuses.editStatus(statusId, params)) + .then((status) => { // The backend might still be processing the rich media attachment if (!status.card && shouldHaveCard(status)) { status.expectsCard = true; @@ -79,10 +76,10 @@ const createStatus = (params: Record, idempotencyKey: string, statu const delay = 1000; const poll = (retries = 5) => { - api(getState)(`/api/v1/statuses/${status.id}`).then(response => { - if (response.json?.card) { - dispatch(importFetchedStatus(response.json)); - } else if (retries > 0 && response.status === 200) { + return getClient(getState()).statuses.getStatus(status.id).then(response => { + if (response.card) { + dispatch(importFetchedStatus(response)); + } else if (retries > 0 && response) { setTimeout(() => poll(retries - 1), delay); } }).catch(console.error); @@ -107,9 +104,9 @@ const editStatus = (id: string) => (dispatch: AppDispatch, getState: () => RootS dispatch({ type: STATUS_FETCH_SOURCE_REQUEST }); - api(getState)(`/api/v1/statuses/${id}/source`).then(response => { + return getClient(getState()).statuses.getStatusSource(id).then(response => { dispatch({ type: STATUS_FETCH_SOURCE_SUCCESS }); - dispatch(setComposeToStatus(status, response.json.text, response.json.spoiler_text, response.json.content_type, false)); + dispatch(setComposeToStatus(status, response.text, response.spoiler_text, response.content_type, false)); dispatch(openModal('COMPOSE')); }).catch(error => { dispatch({ type: STATUS_FETCH_SOURCE_FAIL, error }); @@ -123,12 +120,11 @@ const fetchStatus = (id: string, intl?: IntlShape) => dispatch({ type: STATUS_FETCH_REQUEST, id, skipLoading }); - const params = new URLSearchParams(); - if (intl && getSettings(getState()).get('autoTranslate')) { - params.set('language', intl.locale); - } + const params = intl && getSettings(getState()).get('autoTranslate') ? { + language: intl.locale, + } : undefined; - return api(getState)(`/api/v1/statuses/${id}`, { params }).then(({ json: status }) => { + return getClient(getState()).statuses.getStatus(id, params).then(status => { dispatch(importFetchedStatus(status)); dispatch({ type: STATUS_FETCH_SUCCESS, status, skipLoading }); return status; @@ -149,13 +145,12 @@ const deleteStatus = (id: string, withRedraft = false) => dispatch({ type: STATUS_DELETE_REQUEST, params: status }); - return api(getState)(`/api/v1/statuses/${id}`, { method: 'DELETE' }) - .then(response => { + return getClient(getState()).statuses.deleteStatus(id).then(response => { dispatch({ type: STATUS_DELETE_SUCCESS, id }); dispatch(deleteFromTimelines(id)); if (withRedraft) { - dispatch(setComposeToStatus(status, response.json.text, response.json.spoiler_text, response.json.pleroma?.content_type, withRedraft)); + dispatch(setComposeToStatus(status, response.text || '', response.spoiler_text, response.pleroma?.content_type, withRedraft)); dispatch(openModal('COMPOSE')); } }) @@ -171,12 +166,11 @@ const fetchContext = (id: string, intl?: IntlShape) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: CONTEXT_FETCH_REQUEST, id }); - const params = new URLSearchParams(); - if (intl && getSettings(getState()).get('autoTranslate')) { - params.set('language', intl.locale); - } + const params = intl && getSettings(getState()).get('autoTranslate') ? { + language: intl.locale, + } : undefined; - return api(getState)(`/api/v1/statuses/${id}/context`, { params }).then(({ json: context }) => { + return getClient(getState()).statuses.getContext(id, params).then(context => { if (Array.isArray(context)) { // Mitra: returns a list of statuses dispatch(importFetchedStatuses(context)); @@ -210,7 +204,7 @@ const muteStatus = (id: string) => if (!isLoggedIn(getState)) return; dispatch({ type: STATUS_MUTE_REQUEST, id }); - api(getState)(`/api/v1/statuses/${id}/mute`, { method: 'POST' }).then(() => { + return getClient(getState()).statuses.muteStatus(id).then(() => { dispatch({ type: STATUS_MUTE_SUCCESS, id }); }).catch(error => { dispatch({ type: STATUS_MUTE_FAIL, id, error }); @@ -222,7 +216,7 @@ const unmuteStatus = (id: string) => if (!isLoggedIn(getState)) return; dispatch({ type: STATUS_UNMUTE_REQUEST, id }); - api(getState)(`/api/v1/statuses/${id}/unmute`, { method: 'POST' }).then(() => { + return getClient(getState()).statuses.unmuteStatus(id).then(() => { dispatch({ type: STATUS_UNMUTE_SUCCESS, id }); }).catch(error => { dispatch({ type: STATUS_UNMUTE_FAIL, id, error }); @@ -230,7 +224,7 @@ const unmuteStatus = (id: string) => }; const toggleMuteStatus = (status: Status) => - (dispatch: AppDispatch, getState: () => RootState) => { + (dispatch: AppDispatch) => { if (status.muted) { dispatch(unmuteStatus(status.id)); } else { @@ -282,12 +276,8 @@ const translateStatus = (id: string, targetLanguage?: string, lazy?: boolean) => TRANSLATIONS_QUEUE = new Set(); if (TRANSLATIONS_TIMEOUT) clearTimeout(TRANSLATIONS_TIMEOUT); - api(getState)('/api/v1/pl/statuses/translate', { - method: 'POST', - body: JSON.stringify({ - ids: copy, - target_language: targetLanguage, - }), + getClient(getState).request('/api/v1/pl/statuses/translate', { + method: 'POST', body: { ids: copy, lang: targetLanguage }, }).then((response) => { response.json.forEach((translation: APIEntity) => { dispatch({ @@ -322,14 +312,11 @@ const translateStatus = (id: string, targetLanguage?: string, lazy?: boolean) => handleTranslateMany(); } else { - api(getState)(`/api/v1/statuses/${id}/translate`, { - body: JSON.stringify({ target_language: targetLanguage }), - method: 'POST', - }).then(response => { + return getClient(getState()).statuses.translateStatus(id, targetLanguage).then(response => { dispatch({ type: STATUS_TRANSLATE_SUCCESS, id, - translation: response.json, + translation: response, }); }).catch(error => { dispatch({ diff --git a/src/actions/suggestions.ts b/src/actions/suggestions.ts index 483c2f1cb..5c82004c1 100644 --- a/src/actions/suggestions.ts +++ b/src/actions/suggestions.ts @@ -1,14 +1,12 @@ import { isLoggedIn } from 'soapbox/utils/auth'; -import { getFeatures } from 'soapbox/utils/features'; -import api, { getNextLink } from '../api'; +import { getClient } from '../api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts } from './importer'; import { insertSuggestionsIntoTimeline } from './timelines'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST'; const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS'; @@ -16,66 +14,29 @@ const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL'; const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS'; -const SUGGESTIONS_V2_FETCH_REQUEST = 'SUGGESTIONS_V2_FETCH_REQUEST'; -const SUGGESTIONS_V2_FETCH_SUCCESS = 'SUGGESTIONS_V2_FETCH_SUCCESS'; -const SUGGESTIONS_V2_FETCH_FAIL = 'SUGGESTIONS_V2_FETCH_FAIL'; - -const fetchSuggestionsV1 = (params: Record = {}) => - (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ type: SUGGESTIONS_FETCH_REQUEST, skipLoading: true }); - return api(getState)('/api/v1/suggestions', { params }).then(({ json: accounts }) => { - dispatch(importFetchedAccounts(accounts)); - dispatch({ type: SUGGESTIONS_FETCH_SUCCESS, accounts, skipLoading: true }); - return accounts; - }).catch(error => { - dispatch({ type: SUGGESTIONS_FETCH_FAIL, error, skipLoading: true, skipAlert: true }); - throw error; - }); - }; - -const fetchSuggestionsV2 = (params: Record = {}) => - (dispatch: AppDispatch, getState: () => RootState) => { - const next = getState().suggestions.next; - - dispatch({ type: SUGGESTIONS_V2_FETCH_REQUEST, skipLoading: true }); - - return api(getState)(next ? next : '/api/v2/suggestions', next ? {} : { params }).then((response) => { - const suggestions: APIEntity[] = response.json; - const accounts = suggestions.map(({ account }) => account); - const next = getNextLink(response) || null; - - dispatch(importFetchedAccounts(accounts)); - dispatch({ type: SUGGESTIONS_V2_FETCH_SUCCESS, suggestions, next, skipLoading: true }); - return suggestions; - }).catch(error => { - dispatch({ type: SUGGESTIONS_V2_FETCH_FAIL, error, skipLoading: true, skipAlert: true }); - throw error; - }); - }; - -const fetchSuggestions = (params: Record = { limit: 50 }) => +const fetchSuggestions = (limit = 50) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); + const client = getClient(state); const me = state.me; - const instance = state.instance; - const features = getFeatures(instance); if (!me) return null; - if (features.suggestionsV2) { - return dispatch(fetchSuggestionsV2(params)) - .then((suggestions: APIEntity[]) => { - const accountIds = suggestions.map(({ account }) => account.id); - dispatch(fetchRelationships(accountIds)); - }) - .catch(() => { }); - } else if (features.suggestions) { - return dispatch(fetchSuggestionsV1(params)) - .then((accounts: APIEntity[]) => { - const accountIds = accounts.map(({ id }) => id); - dispatch(fetchRelationships(accountIds)); - }) - .catch(() => { }); + if (client.features.suggestions) { + dispatch({ type: SUGGESTIONS_FETCH_REQUEST, skipLoading: true }); + + return getClient(getState).accounts.getSuggestions(limit).then((suggestions) => { + const accounts = suggestions.map(({ account }) => account); + + dispatch(importFetchedAccounts(accounts)); + dispatch({ type: SUGGESTIONS_FETCH_SUCCESS, suggestions, skipLoading: true }); + + dispatch(fetchRelationships(accounts.map(({ id }) => id))); + return suggestions; + }).catch(error => { + dispatch({ type: SUGGESTIONS_FETCH_FAIL, error, skipLoading: true, skipAlert: true }); + throw error; + }); } else { // Do nothing return null; @@ -83,7 +44,7 @@ const fetchSuggestions = (params: Record = { limit: 50 }) => }; const fetchSuggestionsForTimeline = () => (dispatch: AppDispatch, _getState: () => RootState) => { - dispatch(fetchSuggestions({ limit: 20 }))?.then(() => dispatch(insertSuggestionsIntoTimeline())); + dispatch(fetchSuggestions(20))?.then(() => dispatch(insertSuggestionsIntoTimeline())); }; const dismissSuggestion = (accountId: string) => @@ -95,7 +56,7 @@ const dismissSuggestion = (accountId: string) => id: accountId, }); - api(getState)(`/api/v1/suggestions/${accountId}`, { method: 'DELETE' }); + return getClient(getState).accounts.dismissSuggestions(accountId); }; export { @@ -103,11 +64,6 @@ export { SUGGESTIONS_FETCH_SUCCESS, SUGGESTIONS_FETCH_FAIL, SUGGESTIONS_DISMISS, - SUGGESTIONS_V2_FETCH_REQUEST, - SUGGESTIONS_V2_FETCH_SUCCESS, - SUGGESTIONS_V2_FETCH_FAIL, - fetchSuggestionsV1, - fetchSuggestionsV2, fetchSuggestions, fetchSuggestionsForTimeline, dismissSuggestion, diff --git a/src/actions/tags.ts b/src/actions/tags.ts index 4cc4a1c96..19e2fff64 100644 --- a/src/actions/tags.ts +++ b/src/actions/tags.ts @@ -1,5 +1,6 @@ -import api, { getNextLink } from '../api'; +import { getClient, getNextLink } from '../api'; +import type { PaginatedResponse, Tag } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -26,7 +27,7 @@ const FOLLOWED_HASHTAGS_EXPAND_FAIL = 'FOLLOWED_HASHTAGS_EXPAND_FAIL'; const fetchHashtag = (name: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchHashtagRequest()); - api(getState)(`/api/v1/tags/${name}`).then(({ json: data }) => { + return getClient(getState()).accounts.getTag(name).then((data) => { dispatch(fetchHashtagSuccess(name, data)); }).catch(err => { dispatch(fetchHashtagFail(err)); @@ -51,7 +52,7 @@ const fetchHashtagFail = (error: unknown) => ({ const followHashtag = (name: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(followHashtagRequest(name)); - api(getState)(`/api/v1/tags/${name}/follow`, { method: 'POST' }).then(({ json: data }) => { + return getClient(getState()).accounts.followTag(name).then((data) => { dispatch(followHashtagSuccess(name, data)); }).catch(err => { dispatch(followHashtagFail(name, err)); @@ -78,7 +79,7 @@ const followHashtagFail = (name: string, error: unknown) => ({ const unfollowHashtag = (name: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(unfollowHashtagRequest(name)); - api(getState)(`/api/v1/tags/${name}/unfollow`, { method: 'POST' }).then(({ json: data }) => { + return getClient(getState()).accounts.unfollowTag(name).then((data) => { dispatch(unfollowHashtagSuccess(name, data)); }).catch(err => { dispatch(unfollowHashtagFail(name, err)); @@ -105,9 +106,8 @@ const unfollowHashtagFail = (name: string, error: unknown) => ({ const fetchFollowedHashtags = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchFollowedHashtagsRequest()); - api(getState)('/api/v1/followed_tags').then(response => { - const next = getNextLink(response); - dispatch(fetchFollowedHashtagsSuccess(response.json, next || null)); + return getClient(getState()).accounts.getFollowedTags().then(response => { + dispatch(fetchFollowedHashtagsSuccess(response.items, response.next)); }).catch(err => { dispatch(fetchFollowedHashtagsFail(err)); }); @@ -117,7 +117,7 @@ const fetchFollowedHashtagsRequest = () => ({ type: FOLLOWED_HASHTAGS_FETCH_REQUEST, }); -const fetchFollowedHashtagsSuccess = (followed_tags: APIEntity[], next: string | null) => ({ +const fetchFollowedHashtagsSuccess = (followed_tags: APIEntity[], next: (() => Promise>) | null) => ({ type: FOLLOWED_HASHTAGS_FETCH_SUCCESS, followed_tags, next, @@ -129,17 +129,14 @@ const fetchFollowedHashtagsFail = (error: unknown) => ({ }); const expandFollowedHashtags = () => (dispatch: AppDispatch, getState: () => RootState) => { - const url = getState().followed_tags.next; + const next = getState().followed_tags.next; - if (url === null) { - return; - } + if (next === null) return; dispatch(expandFollowedHashtagsRequest()); - api(getState)(url).then(response => { - const next = getNextLink(response); - dispatch(expandFollowedHashtagsSuccess(response.json, next || null)); + return next().then(response => { + dispatch(expandFollowedHashtagsSuccess(response.items, response.next)); }).catch(error => { dispatch(expandFollowedHashtagsFail(error)); }); @@ -149,7 +146,7 @@ const expandFollowedHashtagsRequest = () => ({ type: FOLLOWED_HASHTAGS_EXPAND_REQUEST, }); -const expandFollowedHashtagsSuccess = (followed_tags: APIEntity[], next: string | null) => ({ +const expandFollowedHashtagsSuccess = (followed_tags: APIEntity[], next: (() => Promise>) | null) => ({ type: FOLLOWED_HASHTAGS_EXPAND_SUCCESS, followed_tags, next, @@ -160,7 +157,6 @@ const expandFollowedHashtagsFail = (error: unknown) => ({ error, }); - export { HASHTAG_FETCH_REQUEST, HASHTAG_FETCH_SUCCESS, diff --git a/src/actions/timelines.ts b/src/actions/timelines.ts index 77e14dcca..286d8fc04 100644 --- a/src/actions/timelines.ts +++ b/src/actions/timelines.ts @@ -4,7 +4,7 @@ import { getSettings } from 'soapbox/actions/settings'; import { normalizeStatus } from 'soapbox/normalizers'; import { shouldFilter } from 'soapbox/utils/timelines'; -import api, { getNextLink, getPrevLink } from '../api'; +import { getClient, getNextLink, getPrevLink } from '../api'; import { importFetchedStatus, importFetchedStatuses } from './importer'; @@ -189,7 +189,7 @@ const expandTimeline = (timelineId: string, path: string, params: Record { + return getClient(getState).request(path, { params }).then(response => { dispatch(importFetchedStatuses(response.json)); const statuses = deduplicateStatuses(response.json); @@ -245,9 +245,6 @@ const expandCommunityTimeline = ({ url, maxId, onlyMedia }: Record const expandBubbleTimeline = ({ url, maxId, onlyMedia }: Record = {}, intl?: IntlShape, done = noOp) => expandTimeline(`bubble${onlyMedia ? ':media' : ''}`, url || '/api/v1/timelines/bubble', url ? {} : { max_id: maxId, only_media: !!onlyMedia }, intl, done); -const expandDirectTimeline = ({ url, maxId }: Record = {}, intl?: IntlShape, done = noOp) => - expandTimeline('direct', url || '/api/v1/timelines/direct', url ? {} : { max_id: maxId }, intl, done); - const expandAccountTimeline = (accountId: string, { url, maxId, withReplies }: Record = {}, intl?: IntlShape) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, url || `/api/v1/accounts/${accountId}/statuses`, url ? {} : { exclude_replies: !withReplies, max_id: maxId, with_muted: true }, intl); @@ -358,7 +355,6 @@ export { expandRemoteTimeline, expandCommunityTimeline, expandBubbleTimeline, - expandDirectTimeline, expandAccountTimeline, expandAccountFeaturedTimeline, expandAccountMediaTimeline, diff --git a/src/actions/trending-statuses.ts b/src/actions/trending-statuses.ts index 2557d3627..1e33e6575 100644 --- a/src/actions/trending-statuses.ts +++ b/src/actions/trending-statuses.ts @@ -1,6 +1,6 @@ import { getFeatures } from 'soapbox/utils/features'; -import api from '../api'; +import { getClient } from '../api'; import { importFetchedStatuses } from './importer'; @@ -20,7 +20,8 @@ const fetchTrendingStatuses = () => if (!features.trendingStatuses) return; dispatch({ type: TRENDING_STATUSES_FETCH_REQUEST }); - return api(getState)('/api/v1/trends/statuses').then(({ json: statuses }) => { + + return getClient(getState()).trends.getTrendingStatuses().then((statuses) => { dispatch(importFetchedStatuses(statuses)); dispatch({ type: TRENDING_STATUSES_FETCH_SUCCESS, statuses }); return statuses; diff --git a/src/actions/trends.ts b/src/actions/trends.ts index b93cbd1fc..f0482a6bf 100644 --- a/src/actions/trends.ts +++ b/src/actions/trends.ts @@ -1,25 +1,6 @@ -import api from '../api'; - -import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; -const TRENDS_FETCH_REQUEST = 'TRENDS_FETCH_REQUEST'; const TRENDS_FETCH_SUCCESS = 'TRENDS_FETCH_SUCCESS'; -const TRENDS_FETCH_FAIL = 'TRENDS_FETCH_FAIL'; - -const fetchTrends = () => - (dispatch: AppDispatch, getState: () => RootState) => { - dispatch(fetchTrendsRequest()); - - api(getState)('/api/v1/trends').then(response => { - dispatch(fetchTrendsSuccess(response.json)); - }).catch(error => dispatch(fetchTrendsFail(error))); - }; - -const fetchTrendsRequest = () => ({ - type: TRENDS_FETCH_REQUEST, - skipLoading: true, -}); const fetchTrendsSuccess = (tags: APIEntity[]) => ({ type: TRENDS_FETCH_SUCCESS, @@ -27,19 +8,7 @@ const fetchTrendsSuccess = (tags: APIEntity[]) => ({ skipLoading: true, }); -const fetchTrendsFail = (error: unknown) => ({ - type: TRENDS_FETCH_FAIL, - error, - skipLoading: true, - skipAlert: true, -}); - export { - TRENDS_FETCH_REQUEST, TRENDS_FETCH_SUCCESS, - TRENDS_FETCH_FAIL, - fetchTrends, - fetchTrendsRequest, fetchTrendsSuccess, - fetchTrendsFail, }; diff --git a/src/api/hooks/accounts/useAccount.ts b/src/api/hooks/accounts/useAccount.ts index 1bc5fcdfe..d83e52967 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 { useClient, useFeatures, useLoggedIn } from 'soapbox/hooks'; import { type Account, accountSchema } from 'soapbox/schemas'; import { useRelationship } from './useRelationship'; @@ -14,7 +13,7 @@ interface UseAccountOpts { } const useAccount = (accountId?: string, opts: UseAccountOpts = {}) => { - const api = useApi(); + const client = useClient(); const history = useHistory(); const features = useFeatures(); const { me } = useLoggedIn(); @@ -22,7 +21,7 @@ const useAccount = (accountId?: string, opts: UseAccountOpts = {}) => { const { entity, isUnauthorized, ...result } = useEntity( [Entities.ACCOUNTS, accountId!], - () => api(`/api/v1/accounts/${accountId}`), + () => client.request(`/api/v1/accounts/${accountId}`), { schema: accountSchema, enabled: !!accountId }, ); diff --git a/src/api/hooks/accounts/useAccountList.ts b/src/api/hooks/accounts/useAccountList.ts index f5d74849a..f085f4270 100644 --- a/src/api/hooks/accounts/useAccountList.ts +++ b/src/api/hooks/accounts/useAccountList.ts @@ -1,6 +1,6 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks'; +import { useClient } from 'soapbox/hooks'; import { Account, accountSchema } from 'soapbox/schemas'; import { useRelationships } from './useRelationships'; @@ -32,31 +32,31 @@ const useAccountList = (listKey: string[], entityFn: EntityFn, opts: useAc }; const useBlocks = () => { - const api = useApi(); - return useAccountList(['blocks'], () => api('/api/v1/blocks')); + const client = useClient(); + return useAccountList(['blocks'], () => client.request('/api/v1/blocks')); }; const useMutes = () => { - const api = useApi(); - return useAccountList(['mutes'], () => api('/api/v1/mutes')); + const client = useClient(); + return useAccountList(['mutes'], () => client.request('/api/v1/mutes')); }; const useFollowing = (accountId: string | undefined) => { - const api = useApi(); + const client = useClient(); return useAccountList( [accountId!, 'following'], - () => api(`/api/v1/accounts/${accountId}/following`), + () => client.request(`/api/v1/accounts/${accountId}/following`), { enabled: !!accountId }, ); }; const useFollowers = (accountId: string | undefined) => { - const api = useApi(); + const client = useClient(); return useAccountList( [accountId!, 'followers'], - () => api(`/api/v1/accounts/${accountId}/followers`), + () => client.request(`/api/v1/accounts/${accountId}/followers`), { enabled: !!accountId }, ); }; diff --git a/src/api/hooks/accounts/useAccountLookup.ts b/src/api/hooks/accounts/useAccountLookup.ts index 6f1006e61..8963c792f 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 { useClient, useFeatures, useLoggedIn } from 'soapbox/hooks'; import { type Account, accountSchema } from 'soapbox/schemas'; import { useRelationship } from './useRelationship'; @@ -14,7 +13,7 @@ interface UseAccountLookupOpts { } const useAccountLookup = (acct: string | undefined, opts: UseAccountLookupOpts = {}) => { - const api = useApi(); + const client = useClient(); const features = useFeatures(); const history = useHistory(); const { me } = useLoggedIn(); @@ -23,7 +22,7 @@ const useAccountLookup = (acct: string | undefined, opts: UseAccountLookupOpts = const { entity: account, isUnauthorized, ...result } = useEntityLookup( Entities.ACCOUNTS, (account) => account.acct.toLowerCase() === acct?.toLowerCase(), - () => api(`/api/v1/accounts/lookup?acct=${acct}`), + () => client.request(`/api/v1/accounts/lookup?acct=${acct}`), { schema: accountSchema, enabled: !!acct }, ); diff --git a/src/api/hooks/accounts/useFollow.ts b/src/api/hooks/accounts/useFollow.ts index c6b73b93c..907cdbee4 100644 --- a/src/api/hooks/accounts/useFollow.ts +++ b/src/api/hooks/accounts/useFollow.ts @@ -1,9 +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 { relationshipSchema } from 'soapbox/schemas'; +import { useAppDispatch, useClient, useLoggedIn } from 'soapbox/hooks'; interface FollowOpts { reblogs?: boolean; @@ -12,7 +10,7 @@ interface FollowOpts { } const useFollow = () => { - const api = useApi(); + const client = useClient(); const dispatch = useAppDispatch(); const { isLoggedIn } = useLoggedIn(); const { transaction } = useTransaction(); @@ -56,13 +54,9 @@ const useFollow = () => { followEffect(accountId); try { - const response = await api(`/api/v1/accounts/${accountId}/follow`, { - method: 'POST', - body: JSON.stringify(options), - }); - const result = relationshipSchema.safeParse(response.json); - if (result.success) { - dispatch(importEntities([result.data], Entities.RELATIONSHIPS)); + const response = await client.accounts.followAccount(accountId, options); + if (response.id) { + dispatch(importEntities([response], Entities.RELATIONSHIPS)); } } catch (e) { unfollowEffect(accountId); @@ -74,7 +68,7 @@ const useFollow = () => { unfollowEffect(accountId); try { - await api(`/api/v1/accounts/${accountId}/unfollow`, { method: 'POST' }); + await client.accounts.unfollowAccount(accountId); } catch (e) { followEffect(accountId); } diff --git a/src/api/hooks/accounts/useRelationship.ts b/src/api/hooks/accounts/useRelationship.ts index 73f4ef2ab..ffc4957f2 100644 --- a/src/api/hooks/accounts/useRelationship.ts +++ b/src/api/hooks/accounts/useRelationship.ts @@ -2,20 +2,20 @@ import { z } from 'zod'; import { Entities } from 'soapbox/entity-store/entities'; import { useEntity } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks'; import { type Relationship, relationshipSchema } from 'soapbox/schemas'; +import { useClient } from 'soapbox/hooks'; interface UseRelationshipOpts { enabled?: boolean; } const useRelationship = (accountId: string | undefined, opts: UseRelationshipOpts = {}) => { - const api = useApi(); + const client = useClient(); const { enabled = false } = opts; const { entity: relationship, ...result } = useEntity( [Entities.RELATIONSHIPS, accountId!], - () => api(`/api/v1/accounts/relationships?id[]=${accountId}`), + () => client.request(`/api/v1/accounts/relationships?id[]=${accountId}`), { enabled: enabled && !!accountId, schema: z.array(relationshipSchema).nonempty().transform(arr => arr[0]), diff --git a/src/api/hooks/accounts/useRelationships.ts b/src/api/hooks/accounts/useRelationships.ts index cafc40b3c..6d4264bd4 100644 --- a/src/api/hooks/accounts/useRelationships.ts +++ b/src/api/hooks/accounts/useRelationships.ts @@ -1,15 +1,14 @@ 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 { useClient, useLoggedIn } from 'soapbox/hooks'; import { type Relationship, relationshipSchema } from 'soapbox/schemas'; const useRelationships = (listKey: string[], ids: string[]) => { - const api = useApi(); + const client = useClient(); const { isLoggedIn } = useLoggedIn(); const fetchRelationships = (ids: string[]) => - api('/api/v1/accounts/relationships', { params: { ids } }); + client.request('/api/v1/accounts/relationships', { params: { ids } }); const { entityMap: relationships, ...result } = useBatchedEntities( [Entities.RELATIONSHIPS, ...listKey], diff --git a/src/api/hooks/admin/useAnnouncements.ts b/src/api/hooks/admin/useAnnouncements.ts index 860a814d8..543575313 100644 --- a/src/api/hooks/admin/useAnnouncements.ts +++ b/src/api/hooks/admin/useAnnouncements.ts @@ -1,10 +1,10 @@ import { useMutation, useQuery } from '@tanstack/react-query'; -import { useApi } from 'soapbox/hooks'; import { queryClient } from 'soapbox/queries/client'; import { adminAnnouncementSchema, type AdminAnnouncement } from 'soapbox/schemas'; import { useAnnouncements as useUserAnnouncements } from '../announcements'; +import { useClient } from 'soapbox/hooks'; interface CreateAnnouncementParams { content: string; @@ -18,11 +18,11 @@ interface UpdateAnnouncementParams extends CreateAnnouncementParams { } const useAnnouncements = () => { - const api = useApi(); + const client = useClient(); const userAnnouncements = useUserAnnouncements(); const getAnnouncements = async () => { - const { json: data } = await api('/api/v1/pleroma/admin/announcements'); + const { json: data } = await client.request('/api/v1/pleroma/admin/announcements'); const normalizedData = data.map((announcement) => adminAnnouncementSchema.parse(announcement)); return normalizedData; @@ -38,9 +38,8 @@ const useAnnouncements = () => { mutate: createAnnouncement, isPending: isCreating, } = useMutation({ - mutationFn: (params: CreateAnnouncementParams) => api('/api/v1/pleroma/admin/announcements', { - method: 'POST', - body: JSON.stringify(params), + mutationFn: (params: CreateAnnouncementParams) => client.request('/api/v1/pleroma/admin/announcements', { + method: 'POST', body: params, }), retry: false, onSuccess: ({ json: data }) => @@ -54,9 +53,8 @@ const useAnnouncements = () => { mutate: updateAnnouncement, isPending: isUpdating, } = useMutation({ - mutationFn: ({ id, ...params }: UpdateAnnouncementParams) => api(`/api/v1/pleroma/admin/announcements/${id}`, { - method: 'PATCH', - body: JSON.stringify(params), + mutationFn: ({ id, ...params }: UpdateAnnouncementParams) => client.request(`/api/v1/pleroma/admin/announcements/${id}`, { + method: 'PATCH', body: params, }), retry: false, onSuccess: ({ json: data }) => @@ -70,7 +68,7 @@ const useAnnouncements = () => { mutate: deleteAnnouncement, isPending: isDeleting, } = useMutation({ - mutationFn: (id: string) => api(`/api/v1/pleroma/admin/announcements/${id}`, { method: 'DELETE' }), + mutationFn: (id: string) => client.request(`/api/v1/pleroma/admin/announcements/${id}`, { method: 'DELETE' }), retry: false, onSuccess: (_, id) => queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray) => diff --git a/src/api/hooks/admin/useDomains.ts b/src/api/hooks/admin/useDomains.ts index fc403ed89..ce9aa23ca 100644 --- a/src/api/hooks/admin/useDomains.ts +++ b/src/api/hooks/admin/useDomains.ts @@ -1,6 +1,6 @@ import { useMutation, useQuery } from '@tanstack/react-query'; +import { useClient } from 'soapbox/hooks'; -import { useApi } from 'soapbox/hooks'; import { queryClient } from 'soapbox/queries/client'; import { domainSchema, type Domain } from 'soapbox/schemas'; @@ -15,10 +15,10 @@ interface UpdateDomainParams { } const useDomains = () => { - const api = useApi(); + const client = useClient(); const getDomains = async () => { - const { json: data } = await api('/api/v1/pleroma/admin/domains'); + const { json: data } = await client.request('/api/v1/pleroma/admin/domains'); const normalizedData = data.map((domain) => domainSchema.parse(domain)); return normalizedData; @@ -34,9 +34,8 @@ const useDomains = () => { mutate: createDomain, isPending: isCreating, } = useMutation({ - mutationFn: (params: CreateDomainParams) => api('/api/v1/pleroma/admin/domains', { - method: 'POST', - body: JSON.stringify(params), + mutationFn: (params: CreateDomainParams) => client.request('/api/v1/pleroma/admin/domains', { + method: 'POST', body: params, }), retry: false, onSuccess: ({ data }) => @@ -49,9 +48,8 @@ const useDomains = () => { mutate: updateDomain, isPending: isUpdating, } = useMutation({ - mutationFn: ({ id, ...params }: UpdateDomainParams) => api(`/api/v1/pleroma/admin/domains/${id}`, { - method: 'PATCH', - body: JSON.stringify(params), + mutationFn: ({ id, ...params }: UpdateDomainParams) => client.request(`/api/v1/pleroma/admin/domains/${id}`, { + method: 'PATCH', body: params, }), retry: false, onSuccess: ({ json: data }) => @@ -64,7 +62,7 @@ const useDomains = () => { mutate: deleteDomain, isPending: isDeleting, } = useMutation({ - mutationFn: (id: string) => api(`/api/v1/pleroma/admin/domains/${id}`, { method: 'DELETE' }), + mutationFn: (id: string) => client.request(`/api/v1/pleroma/admin/domains/${id}`, { method: 'DELETE' }), retry: false, onSuccess: (_, id) => queryClient.setQueryData(['admin', 'domains'], (prevResult: ReadonlyArray) => diff --git a/src/api/hooks/admin/useModerationLog.ts b/src/api/hooks/admin/useModerationLog.ts index 51fa0e443..efd2f5e2b 100644 --- a/src/api/hooks/admin/useModerationLog.ts +++ b/src/api/hooks/admin/useModerationLog.ts @@ -1,6 +1,6 @@ import { useInfiniteQuery } from '@tanstack/react-query'; +import { useClient } from 'soapbox/hooks'; -import { useApi } from 'soapbox/hooks'; import { moderationLogEntrySchema, type ModerationLogEntry } from 'soapbox/schemas'; interface ModerationLogResult { @@ -11,10 +11,10 @@ interface ModerationLogResult { const flattenPages = (pages?: ModerationLogResult[]): ModerationLogEntry[] => (pages || []).map(({ items }) => items).flat(); const useModerationLog = () => { - const api = useApi(); + const client = useClient(); const getModerationLog = async (page: number): Promise => { - const { json: data } = await api('/api/v1/pleroma/admin/moderation_log', { params: { page } }); + const { json: data } = await client.request('/api/v1/pleroma/admin/moderation_log', { params: { page } }); 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 379661b6a..2981e4933 100644 --- a/src/api/hooks/admin/useRelays.ts +++ b/src/api/hooks/admin/useRelays.ts @@ -1,14 +1,14 @@ import { useMutation, useQuery } from '@tanstack/react-query'; -import { useApi } from 'soapbox/hooks'; +import { useClient } from 'soapbox/hooks'; import { queryClient } from 'soapbox/queries/client'; import { relaySchema, type Relay } from 'soapbox/schemas'; const useRelays = () => { - const api = useApi(); + const client = useClient(); const getRelays = async () => { - const { json: data } = await api<{ relays: Relay[] }>('/api/v1/pleroma/admin/relay'); + const { json: data } = await client.request<{ relays: Relay[] }>('/api/v1/pleroma/admin/relay'); const normalizedData = data.relays?.map((relay) => relaySchema.parse(relay)); return normalizedData; @@ -24,7 +24,7 @@ const useRelays = () => { mutate: followRelay, isPending: isPendingFollow, } = useMutation({ - mutationFn: (relayUrl: string) => api('/api/v1/pleroma/admin/relays', { + mutationFn: (relayUrl: string) => client.request('/api/v1/pleroma/admin/relays', { method: 'POST', body: JSON.stringify({ relay_url: relayUrl }), }), @@ -39,7 +39,7 @@ const useRelays = () => { mutate: unfollowRelay, isPending: isPendingUnfollow, } = useMutation({ - mutationFn: (relayUrl: string) => api('/api/v1/pleroma/admin/relays', { + mutationFn: (relayUrl: string) => client.request('/api/v1/pleroma/admin/relays', { method: 'DELETE', body: JSON.stringify({ relay_url: relayUrl }), }), diff --git a/src/api/hooks/admin/useRules.ts b/src/api/hooks/admin/useRules.ts index 7cc00c3ef..a10f29b1e 100644 --- a/src/api/hooks/admin/useRules.ts +++ b/src/api/hooks/admin/useRules.ts @@ -1,6 +1,6 @@ import { useMutation, useQuery } from '@tanstack/react-query'; -import { useApi } from 'soapbox/hooks'; +import { useClient } from 'soapbox/hooks'; import { queryClient } from 'soapbox/queries/client'; import { adminRuleSchema, type AdminRule } from 'soapbox/schemas'; @@ -18,10 +18,10 @@ interface UpdateRuleParams { } const useRules = () => { - const api = useApi(); + const client = useClient(); const getRules = async () => { - const { json: data } = await api('/api/v1/pleroma/admin/rules'); + const { json: data } = await client.request('/api/v1/pleroma/admin/rules'); const normalizedData = data.map((rule) => adminRuleSchema.parse(rule)); return normalizedData; @@ -37,9 +37,8 @@ const useRules = () => { mutate: createRule, isPending: isCreating, } = useMutation({ - mutationFn: (params: CreateRuleParams) => api('/api/v1/pleroma/admin/rules', { - method: 'POST', - body: JSON.stringify(params), + mutationFn: (params: CreateRuleParams) => client.request('/api/v1/pleroma/admin/rules', { + method: 'POST', body: params, }), retry: false, onSuccess: ({ json: data }) => @@ -52,9 +51,8 @@ const useRules = () => { mutate: updateRule, isPending: isUpdating, } = useMutation({ - mutationFn: ({ id, ...params }: UpdateRuleParams) => api(`/api/v1/pleroma/admin/rules/${id}`, { - method: 'PATCH', - body: JSON.stringify(params), + mutationFn: ({ id, ...params }: UpdateRuleParams) => client.request(`/api/v1/pleroma/admin/rules/${id}`, { + method: 'PATCH', body: params, }), retry: false, onSuccess: ({ json: data }) => @@ -67,7 +65,7 @@ const useRules = () => { mutate: deleteRule, isPending: isDeleting, } = useMutation({ - mutationFn: (id: string) => api(`/api/v1/pleroma/admin/rules/${id}`, { method: 'DELETE' }), + mutationFn: (id: string) => client.request(`/api/v1/pleroma/admin/rules/${id}`, { method: 'DELETE' }), retry: false, onSuccess: (_, id) => queryClient.setQueryData(['admin', 'rules'], (prevResult: ReadonlyArray) => diff --git a/src/api/hooks/admin/useSuggest.ts b/src/api/hooks/admin/useSuggest.ts index a78cb228d..a34345ee1 100644 --- a/src/api/hooks/admin/useSuggest.ts +++ b/src/api/hooks/admin/useSuggest.ts @@ -1,12 +1,12 @@ import { useTransaction } from 'soapbox/entity-store/hooks'; import { EntityCallbacks } from 'soapbox/entity-store/hooks/types'; -import { useApi, useGetState } from 'soapbox/hooks'; +import { useClient, useGetState } from 'soapbox/hooks'; import { accountIdsToAccts } from 'soapbox/selectors'; import type { Account } from 'soapbox/schemas'; const useSuggest = () => { - const api = useApi(); + const client = useClient(); const getState = useGetState(); const { transaction } = useTransaction(); @@ -29,9 +29,8 @@ const useSuggest = () => { const accts = accountIdsToAccts(getState(), accountIds); suggestEffect(accountIds, true); try { - await api('/api/v1/pleroma/admin/users/suggest', { - method: 'PATCH', - body: JSON.stringify({ nicknames: accts }), + await client.request('/api/v1/pleroma/admin/users/suggest', { + method: 'PATCH', body: { nicknames: accts }, }); callbacks?.onSuccess?.(); } catch (e) { @@ -44,9 +43,8 @@ const useSuggest = () => { const accts = accountIdsToAccts(getState(), accountIds); suggestEffect(accountIds, false); try { - await api('/api/v1/pleroma/admin/users/unsuggest', { - method: 'PATCH', - body: JSON.stringify({ nicknames: accts }), + await client.request('/api/v1/pleroma/admin/users/unsuggest', { + method: 'PATCH', body: { nicknames: accts }, }); callbacks?.onSuccess?.(); } catch (e) { diff --git a/src/api/hooks/admin/useVerify.ts b/src/api/hooks/admin/useVerify.ts index 0f98ca79e..1641a7525 100644 --- a/src/api/hooks/admin/useVerify.ts +++ b/src/api/hooks/admin/useVerify.ts @@ -1,12 +1,12 @@ import { useTransaction } from 'soapbox/entity-store/hooks'; import { EntityCallbacks } from 'soapbox/entity-store/hooks/types'; -import { useApi, useGetState } from 'soapbox/hooks'; +import { useClient, useGetState } from 'soapbox/hooks'; import { accountIdsToAccts } from 'soapbox/selectors'; import type { Account } from 'soapbox/schemas'; const useVerify = () => { - const api = useApi(); + const client = useClient(); const getState = useGetState(); const { transaction } = useTransaction(); @@ -34,9 +34,9 @@ const useVerify = () => { const accts = accountIdsToAccts(getState(), accountIds); verifyEffect(accountIds, true); try { - await api('/api/v1/pleroma/admin/users/tag', { + await client.request('/api/v1/pleroma/admin/users/tag', { method: 'PUT', - body: JSON.stringify({ nicknames: accts, tags: ['verified'] }), + body: { nicknames: accts, tags: ['verified'] }, }); callbacks?.onSuccess?.(); } catch (e) { @@ -49,9 +49,9 @@ const useVerify = () => { const accts = accountIdsToAccts(getState(), accountIds); verifyEffect(accountIds, false); try { - await api('/api/v1/pleroma/admin/users/tag', { + await client.request('/api/v1/pleroma/admin/users/tag', { method: 'DELETE', - body: JSON.stringify({ nicknames: accts, tags: ['verified'] }), + body: { nicknames: accts, tags: ['verified'] }, }); callbacks?.onSuccess?.(); } catch (e) { diff --git a/src/api/hooks/announcements/useAnnouncements.ts b/src/api/hooks/announcements/useAnnouncements.ts index cf767c2d2..fa91865d0 100644 --- a/src/api/hooks/announcements/useAnnouncements.ts +++ b/src/api/hooks/announcements/useAnnouncements.ts @@ -1,8 +1,8 @@ import { useMutation, useQuery } from '@tanstack/react-query'; +import { announcementReactionSchema, announcementSchema, type Announcement, type AnnouncementReaction } from 'pl-api'; -import { useApi } from 'soapbox/hooks'; +import { useClient } from 'soapbox/hooks'; import { queryClient } from 'soapbox/queries/client'; -import { announcementReactionSchema, announcementSchema, type Announcement, type AnnouncementReaction } from 'soapbox/schemas'; const updateReaction = (reaction: AnnouncementReaction, count: number, me?: boolean, overwrite?: boolean) => announcementReactionSchema.parse({ ...reaction, @@ -21,13 +21,11 @@ const updateReactions = (reactions: AnnouncementReaction[], name: string, count: }; const useAnnouncements = () => { - const api = useApi(); + const client = useClient(); const getAnnouncements = async () => { - const { json: data } = await api('/api/v1/announcements'); - - const normalizedData = data?.map((announcement) => announcementSchema.parse(announcement)); - return normalizedData; + const data = await client.announcements.getAnnouncements(); + return data; }; const { data, ...result } = useQuery>({ @@ -40,7 +38,7 @@ const useAnnouncements = () => { mutate: addReaction, } = useMutation({ mutationFn: ({ announcementId, name }: { announcementId: string; name: string }) => - api(`/api/v1/announcements/${announcementId}/reactions/${name}`, { method: 'PUT' }), + client.announcements.addAnnouncementReaction(announcementId, name), retry: false, onMutate: ({ announcementId: id, name }) => { queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) => @@ -64,7 +62,7 @@ const useAnnouncements = () => { mutate: removeReaction, } = useMutation({ mutationFn: ({ announcementId, name }: { announcementId: string; name: string }) => - api(`/api/v1/announcements/${announcementId}/reactions/${name}`, { method: 'DELETE' }), + client.announcements.deleteAnnouncementReaction(announcementId, name), retry: false, onMutate: ({ announcementId: id, name }) => { queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) => diff --git a/src/api/hooks/groups/useCancelMembershipRequest.ts b/src/api/hooks/groups/useCancelMembershipRequest.ts index 03abf31f1..c253c5e0d 100644 --- a/src/api/hooks/groups/useCancelMembershipRequest.ts +++ b/src/api/hooks/groups/useCancelMembershipRequest.ts @@ -1,16 +1,16 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useCreateEntity } from 'soapbox/entity-store/hooks'; -import { useApi, useOwnAccount } from 'soapbox/hooks'; +import { useClient, useOwnAccount } from 'soapbox/hooks'; import type { Group } from 'soapbox/schemas'; const useCancelMembershipRequest = (group: Group) => { - const api = useApi(); + const client = useClient(); const { account: me } = useOwnAccount(); const { createEntity, isSubmitting } = useCreateEntity( [Entities.GROUP_RELATIONSHIPS], - () => api(`/api/v1/groups/${group.id}/membership_requests/${me?.id}/reject`, { method: 'POST' }), + () => client.request(`/client.request/v1/groups/${group.id}/membership_requests/${me?.id}/reject`, { method: 'POST' }), ); return { diff --git a/src/api/hooks/groups/useCreateGroup.ts b/src/api/hooks/groups/useCreateGroup.ts index aad37b97f..69412e22a 100644 --- a/src/api/hooks/groups/useCreateGroup.ts +++ b/src/api/hooks/groups/useCreateGroup.ts @@ -1,8 +1,8 @@ import { serialize } from 'object-to-formdata'; +import { useClient } from 'soapbox/hooks'; import { Entities } from 'soapbox/entity-store/entities'; import { useCreateEntity } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks/useApi'; import { groupSchema } from 'soapbox/schemas'; interface CreateGroupParams { @@ -16,17 +16,14 @@ interface CreateGroupParams { } const useCreateGroup = () => { - const api = useApi(); + const client = useClient(); - const { createEntity, ...rest } = useCreateEntity([Entities.GROUPS, 'search', ''], (params: CreateGroupParams) => { - const formData = serialize(params, { indices: true }); - - return api('/api/v1/groups', { + const { createEntity, ...rest } = useCreateEntity([Entities.GROUPS, 'search', ''], (params: CreateGroupParams) => + client.request('/api/v1/groups', { method: 'POST', - headers: { 'Content-Type': '' }, - body: formData, - }); - }, { schema: groupSchema }); + contentType: '', + body: params, + }), { schema: groupSchema }); return { createGroup: createEntity, diff --git a/src/api/hooks/groups/useDeleteGroupStatus.ts b/src/api/hooks/groups/useDeleteGroupStatus.ts index e3f84c609..5056c47ec 100644 --- a/src/api/hooks/groups/useDeleteGroupStatus.ts +++ b/src/api/hooks/groups/useDeleteGroupStatus.ts @@ -1,14 +1,14 @@ +import { useClient } from 'soapbox/hooks'; import { Entities } from 'soapbox/entity-store/entities'; import { useDeleteEntity } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks'; import type { Group } from 'soapbox/schemas'; const useDeleteGroupStatus = (group: Group, statusId: string) => { - const api = useApi(); + const client = useClient(); const { deleteEntity, isSubmitting } = useDeleteEntity( Entities.STATUSES, - () => api(`/api/v1/groups/${group.id}/statuses/${statusId}`, { method: 'DELETE' }), + () => client.request(`/api/v1/groups/${group.id}/statuses/${statusId}`, { method: 'DELETE' }), ); return { diff --git a/src/api/hooks/groups/useGroup.ts b/src/api/hooks/groups/useGroup.ts index 832cc795c..222b29147 100644 --- a/src/api/hooks/groups/useGroup.ts +++ b/src/api/hooks/groups/useGroup.ts @@ -1,20 +1,20 @@ import { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; +import { useClient } from 'soapbox/hooks'; import { Entities } from 'soapbox/entity-store/entities'; import { useEntity } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks'; import { type Group, groupSchema } from 'soapbox/schemas'; import { useGroupRelationship } from './useGroupRelationship'; const useGroup = (groupId: string, refetch = true) => { - const api = useApi(); + const client = useClient(); const history = useHistory(); const { entity: group, isUnauthorized, ...result } = useEntity( [Entities.GROUPS, groupId], - () => api(`/api/v1/groups/${groupId}`), + () => client.request(`/api/v1/groups/${groupId}`), { schema: groupSchema, refetch, diff --git a/src/api/hooks/groups/useGroupMedia.ts b/src/api/hooks/groups/useGroupMedia.ts index 3cb0a139d..179d4f183 100644 --- a/src/api/hooks/groups/useGroupMedia.ts +++ b/src/api/hooks/groups/useGroupMedia.ts @@ -1,17 +1,17 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks/useApi'; +import { useClient } from 'soapbox/hooks'; import { normalizeStatus } from 'soapbox/normalizers'; import { toSchema } from 'soapbox/utils/normalizers'; const statusSchema = toSchema(normalizeStatus); const useGroupMedia = (groupId: string) => { - const api = useApi(); + const client = useClient(); return useEntities( [Entities.STATUSES, 'groupMedia', groupId], - () => api(`/api/v1/timelines/group/${groupId}?only_media=true`), + () => client.request(`/api/v1/timelines/group/${groupId}?only_media=true`), { schema: statusSchema }) ; }; diff --git a/src/api/hooks/groups/useGroupMembers.ts b/src/api/hooks/groups/useGroupMembers.ts index 6395e4b00..916fb0fb4 100644 --- a/src/api/hooks/groups/useGroupMembers.ts +++ b/src/api/hooks/groups/useGroupMembers.ts @@ -1,16 +1,15 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; +import { useClient } from 'soapbox/hooks'; import { GroupMember, groupMemberSchema } from 'soapbox/schemas'; import { GroupRoles } from 'soapbox/schemas/group-member'; -import { useApi } from '../../../hooks/useApi'; - const useGroupMembers = (groupId: string, role: GroupRoles) => { - const api = useApi(); + const client = useClient(); const { entities, ...result } = useEntities( [Entities.GROUP_MEMBERSHIPS, groupId, role], - () => api(`/api/v1/groups/${groupId}/memberships?role=${role}`), + () => client.request(`/api/v1/groups/${groupId}/memberships?role=${role}`), { schema: groupMemberSchema }, ); diff --git a/src/api/hooks/groups/useGroupMembershipRequests.ts b/src/api/hooks/groups/useGroupMembershipRequests.ts index db54aeb7f..10080a7ee 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 { useClient } from 'soapbox/hooks'; import { accountSchema } from 'soapbox/schemas'; import { GroupRoles } from 'soapbox/schemas/group-member'; @@ -9,14 +9,14 @@ import { useGroupRelationship } from './useGroupRelationship'; import type { ExpandedEntitiesPath } from 'soapbox/entity-store/hooks/types'; const useGroupMembershipRequests = (groupId: string) => { - const api = useApi(); + const client = useClient(); const path: ExpandedEntitiesPath = [Entities.ACCOUNTS, 'membership_requests', groupId]; const { groupRelationship: relationship } = useGroupRelationship(groupId); const { entities, invalidate, fetchEntities, ...rest } = useEntities( path, - () => api(`/api/v1/groups/${groupId}/membership_requests`), + () => client.request(`/api/v1/groups/${groupId}/membership_requests`), { schema: accountSchema, enabled: relationship?.role === GroupRoles.OWNER || relationship?.role === GroupRoles.ADMIN, @@ -24,13 +24,13 @@ const useGroupMembershipRequests = (groupId: string) => { ); const { dismissEntity: authorize } = useDismissEntity(path, async (accountId: string) => { - const response = await api(`/api/v1/groups/${groupId}/membership_requests/${accountId}/authorize`, { method: 'POST' }); + const response = await client.request(`/api/v1/groups/${groupId}/membership_requests/${accountId}/authorize`, { method: 'POST' }); invalidate(); return response; }); const { dismissEntity: reject } = useDismissEntity(path, async (accountId: string) => { - const response = await api(`/api/v1/groups/${groupId}/membership_requests/${accountId}/reject`, { method: 'POST' }); + const response = await client.request(`/api/v1/groups/${groupId}/membership_requests/${accountId}/reject`, { method: 'POST' }); invalidate(); return response; }); diff --git a/src/api/hooks/groups/useGroupRelationship.ts b/src/api/hooks/groups/useGroupRelationship.ts index 28fce9e2c..8eb50964f 100644 --- a/src/api/hooks/groups/useGroupRelationship.ts +++ b/src/api/hooks/groups/useGroupRelationship.ts @@ -2,15 +2,15 @@ import { z } from 'zod'; import { Entities } from 'soapbox/entity-store/entities'; import { useEntity } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks'; +import { useClient } from 'soapbox/hooks'; import { type GroupRelationship, groupRelationshipSchema } from 'soapbox/schemas'; const useGroupRelationship = (groupId: string | undefined) => { - const api = useApi(); + const client = useClient(); const { entity: groupRelationship, ...result } = useEntity( [Entities.GROUP_RELATIONSHIPS, groupId!], - () => api(`/api/v1/groups/relationships?id[]=${groupId}`), + () => client.request(`/api/v1/groups/relationships?id[]=${groupId}`), { enabled: !!groupId, schema: z.array(groupRelationshipSchema).nonempty().transform(arr => arr[0]), diff --git a/src/api/hooks/groups/useGroupRelationships.ts b/src/api/hooks/groups/useGroupRelationships.ts index c5ba4b7e0..b77ab4955 100644 --- a/src/api/hooks/groups/useGroupRelationships.ts +++ b/src/api/hooks/groups/useGroupRelationships.ts @@ -1,14 +1,14 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useBatchedEntities } from 'soapbox/entity-store/hooks/useBatchedEntities'; -import { useApi, useLoggedIn } from 'soapbox/hooks'; +import { useClient, useLoggedIn } from 'soapbox/hooks'; import { type GroupRelationship, groupRelationshipSchema } from 'soapbox/schemas'; const useGroupRelationships = (listKey: string[], ids: string[]) => { - const api = useApi(); + const client = useClient(); const { isLoggedIn } = useLoggedIn(); const fetchGroupRelationships = (ids: string[]) => - api('/api/v1/groups/relationships', { params: { id: ids } }); + client.request('/api/v1/groups/relationships', { params: { id: ids } }); const { entityMap: relationships, ...result } = useBatchedEntities( [Entities.RELATIONSHIPS, ...listKey], diff --git a/src/api/hooks/groups/useGroups.ts b/src/api/hooks/groups/useGroups.ts index 7da4c3039..f44083741 100644 --- a/src/api/hooks/groups/useGroups.ts +++ b/src/api/hooks/groups/useGroups.ts @@ -1,18 +1,18 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks'; +import { useClient } from 'soapbox/hooks'; import { useFeatures } from 'soapbox/hooks/useFeatures'; import { groupSchema, type Group } from 'soapbox/schemas/group'; import { useGroupRelationships } from './useGroupRelationships'; const useGroups = () => { - const api = useApi(); + const client = useClient(); const features = useFeatures(); const { entities, ...result } = useEntities( [Entities.GROUPS, 'search', ''], - () => api('/api/v1/groups'), + () => client.request('/api/v1/groups'), { enabled: features.groups, schema: groupSchema }, ); const { relationships } = useGroupRelationships( diff --git a/src/api/hooks/groups/useUpdateGroup.ts b/src/api/hooks/groups/useUpdateGroup.ts index abd09d784..9902bc2f1 100644 --- a/src/api/hooks/groups/useUpdateGroup.ts +++ b/src/api/hooks/groups/useUpdateGroup.ts @@ -1,8 +1,6 @@ -import { serialize } from 'object-to-formdata'; - import { Entities } from 'soapbox/entity-store/entities'; import { useCreateEntity } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks/useApi'; +import { useClient } from 'soapbox/hooks'; import { groupSchema } from 'soapbox/schemas'; interface UpdateGroupParams { @@ -15,19 +13,14 @@ interface UpdateGroupParams { } const useUpdateGroup = (groupId: string) => { - const api = useApi(); + const client = useClient(); - const { createEntity, ...rest } = useCreateEntity([Entities.GROUPS], (params: UpdateGroupParams) => { - const formData = serialize(params, { indices: true }); - - Object.entries(params).forEach(([key, value]) => formData.append(key, value)); - - return api(`/api/v1/groups/${groupId}`, { + const { createEntity, ...rest } = useCreateEntity([Entities.GROUPS], (params: UpdateGroupParams) => + client.request(`/api/v1/groups/${groupId}`, { method: 'PUT', - headers: { 'Content-Type': '' }, - body: formData, - }); - }, { schema: groupSchema }); + contentType: '', + body: params, + }), { schema: groupSchema }); return { updateGroup: createEntity, diff --git a/src/api/hooks/statuses/useBookmarkFolders.ts b/src/api/hooks/statuses/useBookmarkFolders.ts index c59fe93c9..e7285e4be 100644 --- a/src/api/hooks/statuses/useBookmarkFolders.ts +++ b/src/api/hooks/statuses/useBookmarkFolders.ts @@ -1,16 +1,16 @@ +import { useClient } from 'soapbox/hooks'; import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks'; import { useFeatures } from 'soapbox/hooks/useFeatures'; import { bookmarkFolderSchema, type BookmarkFolder } from 'soapbox/schemas/bookmark-folder'; const useBookmarkFolders = () => { - const api = useApi(); + const client = useClient(); const features = useFeatures(); const { entities, ...result } = useEntities( [Entities.BOOKMARK_FOLDERS], - () => api('/api/v1/pleroma/bookmark_folders'), + () => client.request('/api/v1/pleroma/bookmark_folders'), { enabled: features.bookmarkFolders, schema: bookmarkFolderSchema }, ); diff --git a/src/api/hooks/statuses/useCreateBookmarkFolder.ts b/src/api/hooks/statuses/useCreateBookmarkFolder.ts index 463309b13..200dd9394 100644 --- a/src/api/hooks/statuses/useCreateBookmarkFolder.ts +++ b/src/api/hooks/statuses/useCreateBookmarkFolder.ts @@ -1,6 +1,6 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useCreateEntity } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks'; +import { useClient } from 'soapbox/hooks'; import { bookmarkFolderSchema } from 'soapbox/schemas/bookmark-folder'; interface CreateBookmarkFolderParams { @@ -9,12 +9,12 @@ interface CreateBookmarkFolderParams { } const useCreateBookmarkFolder = () => { - const api = useApi(); + const client = useClient(); const { createEntity, ...rest } = useCreateEntity( [Entities.BOOKMARK_FOLDERS], (params: CreateBookmarkFolderParams) => - api('/api/v1/pleroma/bookmark_folders', { + client.request('/api/v1/pleroma/bookmark_folders', { method: 'POST', body: JSON.stringify(params), }), diff --git a/src/api/hooks/statuses/useUpdateBookmarkFolder.ts b/src/api/hooks/statuses/useUpdateBookmarkFolder.ts index a092cb18b..8dc3f9893 100644 --- a/src/api/hooks/statuses/useUpdateBookmarkFolder.ts +++ b/src/api/hooks/statuses/useUpdateBookmarkFolder.ts @@ -1,6 +1,6 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useCreateEntity } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks'; +import { useClient } from 'soapbox/hooks'; import { bookmarkFolderSchema } from 'soapbox/schemas/bookmark-folder'; interface UpdateBookmarkFolderParams { @@ -9,12 +9,12 @@ interface UpdateBookmarkFolderParams { } const useUpdateBookmarkFolder = (folderId: string) => { - const api = useApi(); + const client = useClient(); const { createEntity, ...rest } = useCreateEntity( [Entities.BOOKMARK_FOLDERS], (params: UpdateBookmarkFolderParams) => - api(`/api/v1/pleroma/bookmark_folders/${folderId}`, { + client.request(`/api/v1/pleroma/bookmark_folders/${folderId}`, { method: 'PATCH', body: JSON.stringify(params), }), diff --git a/src/api/index.ts b/src/api/index.ts index 9dedcdd04..00342fbb4 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -3,12 +3,9 @@ * @module soapbox/api */ import LinkHeader from 'http-link-header'; -import { createSelector } from 'reselect'; import * as BuildConfig from 'soapbox/build-config'; -import { selectAccount } from 'soapbox/selectors'; import { RootState } from 'soapbox/store'; -import { getAccessToken, getAppToken, isURL, parseBaseURL } from 'soapbox/utils/auth'; import { buildFullPath } from 'soapbox/utils/url'; type PlfeResponse = Response & { data: string; json: T }; @@ -27,87 +24,6 @@ const getNextLink = (response: Pick): string | undefined => const getPrevLink = (response: Pick): string | undefined => getLinks(response).refs.find(link => link.rel === 'prev')?.uri; -const getToken = (state: RootState, authType: string) => - authType === 'app' ? getAppToken(state) : getAccessToken(state); - -const getAuthBaseURL = createSelector([ - (state: RootState, me: string | false | null) => me ? selectAccount(state, me)?.url : undefined, - (state: RootState, _me: string | false | null) => state.auth.me, -], (accountUrl, authUserUrl) => { - const baseURL = parseBaseURL(accountUrl) || parseBaseURL(authUserUrl); - return baseURL !== window.location.origin ? baseURL : ''; -}); - -const getFetch = (accessToken?: string | null, baseURL: string = '') => - ( - input: URL | RequestInfo, - init?: RequestInit & { params?: Record; onUploadProgress?: (e: ProgressEvent) => void } | undefined, - ) => { - const fullPath = buildFullPath(input.toString(), isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : baseURL, init?.params); - - const headers = new Headers(init?.headers); - - if (accessToken) { - headers.set('Authorization', `Bearer ${accessToken}`); - } - - if (headers.get('Content-Type') === '') { - headers.delete('Content-Type'); - } else { - headers.set('Content-Type', headers.get('Content-Type') || 'application/json'); - } - - // Fetch API doesn't report upload progress, use XHR - if (init?.onUploadProgress) { - return new Promise>((resolve, reject) => { - const xhr = new XMLHttpRequest(); - - xhr.addEventListener('progress', init.onUploadProgress!); - xhr.addEventListener('loadend', () => { - const data = xhr.response; - let json: T = undefined!; - - try { - json = JSON.parse(data); - } catch (e) { - // - } - - if (xhr.status >= 400) reject({ response: { status: xhr.status, data, json } }); - resolve({ status: xhr.status, data, json } as any as PlfeResponse); - }); - - xhr.open(init?.method || 'GET', fullPath, true); - headers.forEach((value, key) => xhr.setRequestHeader(key, value)); - xhr.responseType = 'text'; - xhr.send(init.body as FormData); - }); - } - - return fetch(fullPath, { - ...init, - headers, - }).then(async (res) => { - const data = await res.text(); - let json: T = undefined!; - - try { - json = JSON.parse(data); - } catch (e) { - // - } - - const { headers, ok, redirected, status, statusText, type, url } = res; - - const response = { headers, ok, redirected, status, statusText, type, url, data, json }; - - if (!ok) { - throw { response }; - } - return response as any as PlfeResponse; - }); - }; - /** * Dumb client for grabbing static files. * It uses FE_SUBDIRECTORY and parses JSON if possible. @@ -132,20 +48,10 @@ const staticFetch = (input: URL | RequestInfo, init?: RequestInit | undefined) = }); }; -/** - * Stateful API client. - * Uses credentials from the Redux store if available. - * @param {function} getState - Must return the Redux state - * @param {string} authType - Either 'user' or 'app' - * @returns {object} Axios instance - */ -const api = (getState: () => RootState, authType: string = 'user') => { - const state = getState(); - const accessToken = getToken(state, authType); - const me = state.me; - const baseURL = me ? getAuthBaseURL(state, me) : ''; +const getClient = (state: RootState | (() => RootState)) => { + if (typeof state === 'function') state = state(); - return getFetch(accessToken, baseURL); + return state.auth.client; }; export { @@ -153,8 +59,6 @@ export { getLinks, getNextLink, getPrevLink, - getFetch, staticFetch, - api, - api as default, + getClient, }; diff --git a/src/components/autosuggest-account-input.tsx b/src/components/autosuggest-account-input.tsx index 955aae065..2f52b0787 100644 --- a/src/components/autosuggest-account-input.tsx +++ b/src/components/autosuggest-account-input.tsx @@ -16,7 +16,6 @@ interface IAutosuggestAccountInput { onSelected: (accountId: string) => void; autoFocus?: boolean; value: string; - limit?: number; className?: string; autoSelect?: boolean; menu?: Menu; @@ -28,7 +27,6 @@ const AutosuggestAccountInput: React.FC = ({ onChange, onSelected, value = '', - limit = 4, ...rest }) => { const dispatch = useAppDispatch(); @@ -45,15 +43,13 @@ const AutosuggestAccountInput: React.FC = ({ }; const handleAccountSearch = useCallback(throttle((q) => { - const params = { q, limit, resolve: false }; - - dispatch(accountSearch(params, controller.current.signal)) + dispatch(accountSearch(q, controller.current.signal)) .then((accounts: { id: string }[]) => { const accountIds = accounts.map(account => account.id); setAccountIds(ImmutableOrderedSet(accountIds)); }) .catch(noOp); - }, 900, { leading: true, trailing: true }), [limit]); + }, 900, { leading: true, trailing: true }), []); const handleChange: React.ChangeEventHandler = e => { refreshCancelToken(); diff --git a/src/components/blurhash.tsx b/src/components/blurhash.tsx index 76a058b69..fc03b3162 100644 --- a/src/components/blurhash.tsx +++ b/src/components/blurhash.tsx @@ -4,7 +4,7 @@ import React, { useRef, useEffect } from 'react'; interface IBlurhash { /** Hash to render */ hash: string | null | undefined; - /** Width of the blurred region in pixels. Defaults to 32. */ + /** Width of the blurred region in pixels. Defaults to 32. */ width?: number; /** Height of the blurred region in pixels. Defaults to width. */ height?: number; diff --git a/src/components/polls/poll-footer.tsx b/src/components/polls/poll-footer.tsx index b015460a2..c61369dbb 100644 --- a/src/components/polls/poll-footer.tsx +++ b/src/components/polls/poll-footer.tsx @@ -25,7 +25,7 @@ const PollFooter: React.FC = ({ poll, showResults, selected }): JSX const dispatch = useAppDispatch(); const intl = useIntl(); - const handleVote = () => dispatch(vote(poll.id, Object.keys(selected))); + const handleVote = () => dispatch(vote(poll.id, Object.keys(selected) as any as number[])); const handleRefresh: React.EventHandler = (e) => { dispatch(fetchPoll(poll.id)); diff --git a/src/components/polls/poll.tsx b/src/components/polls/poll.tsx index 1a7741f13..6e455c59e 100644 --- a/src/components/polls/poll.tsx +++ b/src/components/polls/poll.tsx @@ -38,7 +38,7 @@ const Poll: React.FC = ({ id, status }): JSX.Element | null => { ap_id: status?.url, })); - const handleVote = (selectedId: number) => dispatch(vote(id, [String(selectedId)])); + const handleVote = (selectedId: number) => dispatch(vote(id, [selectedId])); const toggleOption = (value: number) => { if (isLoggedIn) { diff --git a/src/components/sidebar-navigation.tsx b/src/components/sidebar-navigation.tsx index 92933ec0d..6cede6cb1 100644 --- a/src/components/sidebar-navigation.tsx +++ b/src/components/sidebar-navigation.tsx @@ -130,7 +130,7 @@ const SidebarNavigation = () => { ); } - if (features.directTimeline || features.conversations) { + if (features.conversations) { return ( { ); } - if (features.directTimeline || features.conversations) { + if (features.conversations) { return ( = z.ZodType; @@ -35,7 +35,7 @@ interface EntityCallbacks { * Passed into hooks to make requests. * Must return a response. */ -type EntityFn = (value: T) => Promise +type EntityFn = (value: T) => Promise export type { EntitySchema, diff --git a/src/entity-store/hooks/useEntities.ts b/src/entity-store/hooks/useEntities.ts index acb121fe1..58ad1b616 100644 --- a/src/entity-store/hooks/useEntities.ts +++ b/src/entity-store/hooks/useEntities.ts @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import z from 'zod'; import { getNextLink, getPrevLink } from 'soapbox/api'; -import { useApi } from 'soapbox/hooks/useApi'; +import { useClient } from 'soapbox/hooks'; import { useAppDispatch } from 'soapbox/hooks/useAppDispatch'; import { useAppSelector } from 'soapbox/hooks/useAppSelector'; import { useGetState } from 'soapbox/hooks/useGetState'; @@ -39,7 +39,7 @@ const useEntities = ( /** Additional options for the hook. */ opts: UseEntitiesOpts = {}, ) => { - const api = useApi(); + const client = useClient(); const dispatch = useAppDispatch(); const getState = useGetState(); @@ -91,13 +91,13 @@ const useEntities = ( const fetchNextPage = async(): Promise => { if (next) { - await fetchPage(() => api(next), 'end'); + await fetchPage(() => client.request(next), 'end'); } }; const fetchPreviousPage = async(): Promise => { if (prev) { - await fetchPage(() => api(prev), 'start'); + await fetchPage(() => client.request(prev), 'start'); } }; diff --git a/src/entity-store/hooks/useEntityActions.ts b/src/entity-store/hooks/useEntityActions.ts index 8cbc42879..d6d537881 100644 --- a/src/entity-store/hooks/useEntityActions.ts +++ b/src/entity-store/hooks/useEntityActions.ts @@ -1,4 +1,4 @@ -import { useApi } from 'soapbox/hooks/useApi'; +import { useClient } from 'soapbox/hooks'; import { useCreateEntity } from './useCreateEntity'; import { useDeleteEntity } from './useDeleteEntity'; @@ -22,22 +22,20 @@ const useEntityActions = ( endpoints: EntityActionEndpoints, opts: UseEntityActionsOpts = {}, ) => { - const api = useApi(); + const client = useClient(); const { entityType, path } = parseEntitiesPath(expandedPath); const { deleteEntity, isSubmitting: deleteSubmitting } = - useDeleteEntity(entityType, (entityId) => api(endpoints.delete!.replace(/:id/g, entityId), { method: 'DELETE' })); + useDeleteEntity(entityType, (entityId) => client.request(endpoints.delete!.replace(/:id/g, entityId), { method: 'DELETE' })); const { createEntity, isSubmitting: createSubmitting } = - useCreateEntity(path, (data) => api(endpoints.post!, { - method: 'POST', - body: JSON.stringify(data), + useCreateEntity(path, (data) => client.request(endpoints.post!, { + method: 'POST', body: data, }), opts); const { createEntity: updateEntity, isSubmitting: updateSubmitting } = - useCreateEntity(path, (data) => api(endpoints.patch!, { - method: 'PATCH', - body: JSON.stringify(data), + useCreateEntity(path, (data) => client.request(endpoints.patch!, { + method: 'PATCH', body: data, }), opts); return { diff --git a/src/features/admin/announcements.tsx b/src/features/admin/announcements.tsx index 92267ee07..aa1b71f7a 100644 --- a/src/features/admin/announcements.tsx +++ b/src/features/admin/announcements.tsx @@ -91,7 +91,6 @@ const Announcements: React.FC = () => { const { data: announcements, isLoading } = useAnnouncements(); - const handleCreateAnnouncement = () => { dispatch(openModal('EDIT_ANNOUNCEMENT')); }; diff --git a/src/features/bookmark-folders/index.tsx b/src/features/bookmark-folders/index.tsx index 41defaa78..8dbf537e8 100644 --- a/src/features/bookmark-folders/index.tsx +++ b/src/features/bookmark-folders/index.tsx @@ -9,7 +9,6 @@ import { useFeatures } from 'soapbox/hooks'; import NewFolderForm from './components/new-folder-form'; - const messages = defineMessages({ heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, }); diff --git a/src/features/chats/components/chat-composer.tsx b/src/features/chats/components/chat-composer.tsx index ed63f426f..7ba6b1837 100644 --- a/src/features/chats/components/chat-composer.tsx +++ b/src/features/chats/components/chat-composer.tsx @@ -7,7 +7,7 @@ import { Button, Combobox, ComboboxInput, ComboboxList, ComboboxOption, Combobox import { useChatContext } from 'soapbox/contexts/chat-context'; import UploadButton from 'soapbox/features/compose/components/upload-button'; import emojiSearch from 'soapbox/features/emoji/search'; -import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks'; import { Attachment } from 'soapbox/types/entities'; import { textAtCursorMatchesToken } from 'soapbox/utils/suggestions'; @@ -45,9 +45,9 @@ interface IChatComposer extends Pick void; resetFileKey: number | null; resetContentKey: number | null; - attachments?: Attachment[]; - onDeleteAttachment?: (i: number) => void; - uploadCount?: number; + attachment?: Attachment | null; + onDeleteAttachment?: () => void; + uploading?: boolean; uploadProgress?: number; } @@ -63,29 +63,25 @@ const ChatComposer = React.forwardRef resetFileKey, resetContentKey, onPaste, - attachments = [], + attachment, onDeleteAttachment, - uploadCount = 0, + uploading, uploadProgress, }, ref) => { const intl = useIntl(); const dispatch = useAppDispatch(); - const features = useFeatures(); const { chat } = useChatContext(); const isBlocked = useAppSelector((state) => state.getIn(['relationships', chat?.account?.id, 'blocked_by'])); const isBlocking = useAppSelector((state) => state.getIn(['relationships', chat?.account?.id, 'blocking'])); - const maxCharacterCount = useAppSelector((state) => state.instance.configuration.chats.max_characters); - const attachmentLimit = useAppSelector(state => state.instance.configuration.chats.max_media_attachments); + const maxCharacterCount = useInstance().configuration.chats.max_characters; const [suggestions, setSuggestions] = useState(initialSuggestionState); const isSuggestionsAvailable = suggestions.list.length > 0; - const isUploading = uploadCount > 0; - const hasAttachment = attachments.length > 0; const isOverCharacterLimit = maxCharacterCount && value?.length > maxCharacterCount; - const isSubmitDisabled = disabled || isUploading || isOverCharacterLimit || (value.length === 0 && !hasAttachment); + const isSubmitDisabled = disabled || uploading || isOverCharacterLimit || (value.length === 0 && !attachment); const overLimitText = maxCharacterCount ? maxCharacterCount - value?.length : ''; @@ -170,17 +166,15 @@ const ChatComposer = React.forwardRef
- {features.chatsMedia && ( - - = attachmentLimit)} - /> - - )} + + + @@ -198,9 +192,9 @@ const ChatComposer = React.forwardRef autoGrow maxRows={5} disabled={disabled} - attachments={attachments} + attachment={attachment} onDeleteAttachment={onDeleteAttachment} - uploadCount={uploadCount} + uploading={uploading} uploadProgress={uploadProgress} /> {isSuggestionsAvailable ? ( diff --git a/src/features/chats/components/chat-textarea.tsx b/src/features/chats/components/chat-textarea.tsx index f5cbdb037..9249e5f53 100644 --- a/src/features/chats/components/chat-textarea.tsx +++ b/src/features/chats/components/chat-textarea.tsx @@ -7,30 +7,21 @@ import ChatPendingUpload from './chat-pending-upload'; import ChatUpload from './chat-upload'; interface IChatTextarea extends React.ComponentProps { - attachments?: Attachment[]; - onDeleteAttachment?: (i: number) => void; - uploadCount?: number; + attachment?: Attachment | null; + onDeleteAttachment?: () => void; + uploading?: boolean; uploadProgress?: number; } /** Custom textarea for chats. */ const ChatTextarea: React.FC = React.forwardRef(({ - attachments, + attachment, onDeleteAttachment, - uploadCount = 0, + uploading, uploadProgress = 0, ...rest -}, ref) => { - const isUploading = uploadCount > 0; - - const handleDeleteAttachment = (i: number) => () => { - if (onDeleteAttachment) { - onDeleteAttachment(i); - } - }; - - return ( -
( +
= React.forwardRef(({ dark:bg-gray-800 dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:placeholder:text-gray-600 dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500 `} - > - {(!!attachments?.length || isUploading) && ( - - {attachments?.map((attachment, i) => ( -
- -
- ))} + > + {(attachment || uploading) && ( + + {attachment && ( +
+ +
+ )} - {Array.from(Array(uploadCount)).map(() => ( -
- -
- ))} -
- )} + {uploading && ( +
+ +
+ )} +
+ )} -