From 4a59c423955fd889d68132dcdddd4633de421109 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 13 Jun 2026 03:10:00 +0300 Subject: [PATCH 01/86] some initial API refactoring --- .../bookmark_folder_edit.js | 10 +- src/modules/notifications.js | 32 +- src/modules/statuses.js | 23 +- src/modules/users.js | 4 +- src/services/api/admin.js | 515 +++++ src/services/api/api.service.js | 1810 +++++------------ src/services/api/helpers.js | 87 + .../backend_interactor_service.js | 9 +- .../bookmark_folders_fetcher.service.js | 5 +- .../follow_request_fetcher.service.js | 5 +- .../lists_fetcher/lists_fetcher.service.js | 5 +- .../notifications_fetcher.service.js | 5 +- .../status_poster/status_poster.service.js | 61 +- .../timeline_fetcher.service.js | 5 +- src/stores/admin_settings.js | 176 +- src/stores/announcements.js | 90 +- src/stores/bookmark_folders.js | 42 +- src/stores/credentials.js | 19 + src/stores/instance.js | 4 +- src/stores/reports.js | 28 +- 20 files changed, 1368 insertions(+), 1567 deletions(-) create mode 100644 src/services/api/admin.js create mode 100644 src/services/api/helpers.js create mode 100644 src/stores/credentials.js diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.js b/src/components/bookmark_folder_edit/bookmark_folder_edit.js index 43aa239b2..a83825d0b 100644 --- a/src/components/bookmark_folder_edit/bookmark_folder_edit.js +++ b/src/components/bookmark_folder_edit/bookmark_folder_edit.js @@ -1,9 +1,11 @@ import EmojiPicker from 'src/components/emoji_picker/emoji_picker.vue' -import apiService from '../../services/api/api.service' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInterfaceStore } from 'src/stores/interface.js' +import { fetchBookmarkFolders } from 'src/services/api/api.service.js' + const BookmarkFolderEdit = { data() { return { @@ -22,8 +24,10 @@ const BookmarkFolderEdit = { }, created() { if (!this.id) return - const credentials = this.$store.state.users.currentUser.credentials - apiService.fetchBookmarkFolders({ credentials }).then((folders) => { + + fetchBookmarkFolders({ + credentials: useCredentialsStore().current, + }).then((folders) => { const folder = folders.find((folder) => folder.id === this.id) if (!folder) return diff --git a/src/modules/notifications.js b/src/modules/notifications.js index d501b39db..81c8b7fec 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -1,4 +1,4 @@ -import apiService from '../services/api/api.service.js' +import { markNotificationsAsSeen } from '../services/api/api.service.js' import { closeAllDesktopNotifications, closeDesktopNotification, @@ -154,26 +154,22 @@ export const notifications = { }, markNotificationsAsSeen({ rootState, state, commit }) { commit('markNotificationsAsSeen') - apiService - .markNotificationsAsSeen({ - id: state.maxId, - credentials: rootState.users.currentUser.credentials, - }) - .then(() => { - closeAllDesktopNotifications(rootState) - }) + markNotificationsAsSeen({ + id: state.maxId, + credentials: rootState.users.currentUser.credentials, + }).then(() => { + closeAllDesktopNotifications(rootState) + }) }, markSingleNotificationAsSeen({ rootState, commit }, { id }) { commit('markSingleNotificationAsSeen', { id }) - apiService - .markNotificationsAsSeen({ - single: true, - id, - credentials: rootState.users.currentUser.credentials, - }) - .then(() => { - closeDesktopNotification(rootState, { id }) - }) + markNotificationsAsSeen({ + single: true, + id, + credentials: rootState.users.currentUser.credentials, + }).then(() => { + closeDesktopNotification(rootState, { id }) + }) }, dismissNotificationLocal({ commit }, { id }) { commit('dismissNotification', { id }) diff --git a/src/modules/statuses.js b/src/modules/statuses.js index b4f0a93bf..32df8aadf 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -13,7 +13,12 @@ import { slice, } from 'lodash' -import apiService from '../services/api/api.service.js' +import { + deleteStatus, + fetchScrobbles, + fetchStatusHistory, + fetchStatusSource, +} from '../services/api/api.service.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' @@ -131,8 +136,7 @@ const getLatestScrobble = (state, user) => { state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000 if (!scrobblesSupport) return - apiService - .fetchScrobbles({ accountId: user.id }) + fetchScrobbles({ accountId: user.id }) .then((scrobbles) => { if (scrobbles?.error) { useInstanceCapabilitiesStore().set('pleromaScrobblesAvailable', false) @@ -607,20 +611,19 @@ const statuses = { .then((status) => dispatch('addNewStatuses', { statuses: [status] })) }, fetchStatusSource({ rootState }, status) { - return apiService.fetchStatusSource({ + return fetchStatusSource({ id: status.id, credentials: rootState.users.currentUser.credentials, }) }, fetchStatusHistory(_, status) { - return apiService.fetchStatusHistory({ status }) + return fetchStatusHistory({ status }) }, deleteStatus({ rootState, commit }, status) { - apiService - .deleteStatus({ - id: status.id, - credentials: rootState.users.currentUser.credentials, - }) + deleteStatus({ + id: status.id, + credentials: rootState.users.currentUser.credentials, + }) .then(() => { commit('setDeleted', { status }) }) diff --git a/src/modules/users.js b/src/modules/users.js index ec70f8105..fa12c8558 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -9,7 +9,7 @@ import { uniq, } from 'lodash' -import apiService from '../services/api/api.service.js' +import { register } from '../services/api/api.service.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import oauthApi from '../services/new_api/oauth.js' import { @@ -624,7 +624,7 @@ const users = { try { const token = await oauthStore.ensureAppToken() - const data = await apiService.register({ + const data = await register({ credentials: token, params: { ...userInfo }, }) diff --git a/src/services/api/admin.js b/src/services/api/admin.js new file mode 100644 index 000000000..66d4fcb93 --- /dev/null +++ b/src/services/api/admin.js @@ -0,0 +1,515 @@ +import { promisedRequest } from './helpers.js' + +import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' + +const REPORTS = '/api/v1/pleroma/admin/reports' +const CONFIG_URL = '/api/v1/pleroma/admin/config' +const DESCRIPTIONS_URL = '/api/v1/pleroma/admin/config/descriptions' + +const ANNOUNCEMENTS_URL = (id) => '/api/v1/pleroma/admin/announcements/${id}' + +const FRONTENDS_URL = '/api/v1/pleroma/admin/frontends' +const FRONTENDS_INSTALL_URL = '/api/v1/pleroma/admin/frontends/install' + +const USERS_URL = (nickname) => `/api/v1/pleroma/admin/users/${nickname}` +const USERS_URL_LIST = ({ + page, + pageSize, + filters = {}, + query = '', + name = '', + email = '', +}) => { + const { + local = false, + external = false, + active = false, + needApproval = false, + unconfirmed = false, + deactivated = false, + isAdmin = true, + isModerator = true, + } = filters + const filters_str = [ + local && 'local', + external && 'external', + active && 'active', + needApproval && 'need_approval', + unconfirmed && 'unconfirmed', + deactivated && 'deactivated', + isAdmin && 'is_admin', + isModerator && 'is_moderator', + ] + .filter((x) => x) + .join(',') + return `/api/v1/pleroma/admin/users?page=${page}&page_size=${pageSize}&filters=${filters_str}&query=${query}&name=${name}&email=${email}` +} + +const TAG_USER_URL = '/api/pleroma/admin/users/tag' + +const PERMISSION_GROUP_URL = (right) => + `/api/pleroma/admin/users/permission_group/${right}` +const ACTIVATE_USERS_URL = '/api/pleroma/admin/users/activate' +const DEACTIVATE_USERS_URL = '/api/pleroma/admin/users/deactivate' +const SUGGEST_USERS_URL = '/api/pleroma/admin/users/suggest' +const UNSUGGEST_USERS_URL = '/api/pleroma/admin/users/unsuggest' +const APPROVE_USERS_URL = '/api/v1/pleroma/admin/users/approve' +const CONFIRM_USERS_URL = '/api/v1/pleroma/admin/users/confirm_email' +const RESEND_CONFIRMATION_EMAIL_URL = + '/api/v1/pleroma/admin/users/resend_confirmation_email' +const LIST_STATUSES_URL = ({ id, page, pageSize, godmode, withReblogs }) => + `/api/v1/pleroma/admin/users/${id}/statuses?page_size=${pageSize}&page=${page}&godmode=${godmode}&with_reblogs=${withReblogs}` +const CHANGE_STATUS_SCOPE_URL = (id) => `/api/v1/pleroma/admin/statuses/${id}` +const REQUIRE_PASSWORD_CHANGE_URL = + '/api/v1/pleroma/admin/users/force_password_reset' + +const DISABLE_MFA_URL = '/api/v1/pleroma/admin/users/disable_mfa' +const EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji' +const EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import' +const EMOJI_PACKS_URL = (page, pageSize) => + `/api/v1/pleroma/emoji/packs?page=${page}&page_size=${pageSize}` +const EMOJI_PACK_URL = (name) => `/api/v1/pleroma/emoji/pack?name=${name}` +const EMOJI_PACKS_DL_REMOTE_URL = '/api/v1/pleroma/emoji/packs/download' +const EMOJI_PACKS_DL_REMOTE_ZIP_URL = '/api/v1/pleroma/emoji/packs/download_zip' +const EMOJI_PACKS_LS_REMOTE_URL = (url, page, pageSize) => + `/api/v1/pleroma/emoji/packs/remote?url=${url}&page=${page}&page_size=${pageSize}` +const EMOJI_UPDATE_FILE_URL = (name) => + `/api/v1/pleroma/emoji/packs/files?name=${name}` + +// + +export const setUsersTags = ({ + tags, + credentials, + value, + screen_names: nicknames, +}) => + promisedRequest({ + url: TAG_USER_URL, + method: value ? 'PUT' : 'DELETE', + credentials, + payload: { + nicknames, + tags, + }, + }) + +export const setUsersRight = ({ + right, + credentials, + value, + screen_names: nicknames, +}) => + promisedRequest({ + url: PERMISSION_GROUP_URL(right), + method: value ? 'POST' : 'DELETE', + credentials, + payload: { + nicknames, + }, + }) + +export const setUsersActivationStatus = ({ + credentials, + screen_names: nicknames, + value, +}) => + promisedRequest({ + url: value ? ACTIVATE_USERS_URL : DEACTIVATE_USERS_URL, + method: 'PATCH', + credentials, + payload: { + nicknames, + }, + }).then((response) => response.users) + +export const setUsersApprovalStatus = ({ + credentials, + screen_names: nicknames, +}) => + promisedRequest({ + url: APPROVE_USERS_URL, + method: 'PATCH', + credentials, + payload: { + nicknames, + }, + }).then((response) => response.users) + +export const setUsersConfirmationStatus = ({ + credentials, + screen_names: nicknames, +}) => + promisedRequest({ + url: CONFIRM_USERS_URL, + method: 'PATCH', + credentials, + payload: { + nicknames, + }, + }).then((response) => response.users) + +export const setUsersSuggestionStatus = ({ + credentials, + screen_names: nicknames, + value, +}) => + promisedRequest({ + url: value ? SUGGEST_USERS_URL : UNSUGGEST_USERS_URL, + method: 'PATCH', + credentials, + payload: { + nicknames, + }, + }).then((response) => response.users) + +export const getUserData = ({ credentials, screen_name: nickname }) => + promisedRequest({ + url: USERS_URL(nickname), + method: 'GET', + credentials, + }) + +export const deleteAccounts = ({ credentials, screen_names: nicknames }) => + promisedRequest({ + url: USERS_URL(), + method: 'DELETE', + credentials, + payload: { + nicknames, + }, + }) + +export const getAnnouncements = ({ credentials }) => + promisedRequest({ url: ANNOUNCEMENTS_URL(), credentials }) + +// the reported list is hardly useful because standards are for dating i guess, +// so make sure to fetchIfMissing right afterward using this call +export const listUsers = ({ opts, credentials }) => + promisedRequest({ + url: USERS_URL_LIST(opts), + credentials, + method: 'GET', + }) + +export const resendConfirmationEmail = ({ + screen_names: nicknames, + credentials, +}) => + promisedRequest({ + url: RESEND_CONFIRMATION_EMAIL_URL, + credentials, + method: 'PATCH', + payload: { + nicknames, + }, + }) + +export const requirePasswordChange = ({ + screen_names: nicknames, + credentials, +}) => + promisedRequest({ + url: REQUIRE_PASSWORD_CHANGE_URL, + credentials, + method: 'PATCH', + payload: { + nicknames, + }, + }) + +export const disableMFA = ({ screen_name: nickname, credentials }) => + promisedRequest({ + url: DISABLE_MFA_URL, + credentials, + method: 'PUT', + payload: { + nickname, + }, + }) + +export const listStatuses = ({ opts, credentials }) => + promisedRequest({ + url: LIST_STATUSES_URL(opts), + credentials, + method: 'GET', + }) + +export const changeStatusScope = ({ + opts: { id, sensitive, visibility }, + credentials, +}) => { + var payload = {} + if (typeof sensitive !== 'undefined') { + payload['sensitive'] = sensitive + } + if (typeof visibility !== 'undefined') { + payload['visibility'] = visibility + } + + return promisedRequest({ + url: CHANGE_STATUS_SCOPE_URL(id), + credentials, + method: 'PUT', + payload, + }) +} + +export const announcementToPayload = ({ + content, + startsAt, + endsAt, + allDay, +}) => { + const payload = { content } + + if (typeof startsAt !== 'undefined') { + payload.starts_at = startsAt ? new Date(startsAt).toISOString() : null + } + + if (typeof endsAt !== 'undefined') { + payload.ends_at = endsAt ? new Date(endsAt).toISOString() : null + } + + if (typeof allDay !== 'undefined') { + payload.all_day = allDay + } + + return payload +} + +export const postAnnouncement = ({ + credentials, + content, + startsAt, + endsAt, + allDay, +}) => + promisedRequest({ + url: ANNOUNCEMENTS_URL(), + credentials, + method: 'POST', + payload: announcementToPayload({ content, startsAt, endsAt, allDay }), + }) + +export const editAnnouncement = ({ + id, + credentials, + content, + startsAt, + endsAt, + allDay, +}) => + promisedRequest({ + url: ANNOUNCEMENTS_URL(id), + credentials, + method: 'PATCH', + payload: announcementToPayload({ content, startsAt, endsAt, allDay }), + }) + +export const deleteAnnouncement = ({ id, credentials }) => + promisedRequest({ + url: ANNOUNCEMENTS_URL(id), + credentials, + method: 'DELETE', + }) + +export const setReportState = ({ id, state, credentials }) => { + // TODO: Can't use promisedRequest because on OK this does not return json + // See https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1322 + + return promisedRequest({ + url: REPORTS, + credentials, + method: 'PATCH', + payload: { + reports: [ + { + id, + state, + }, + ], + }, + }) + .then((data) => { + if (data.status >= 500) { + throw Error(data.statusText) + } else if (data.status >= 400) { + return data.json() + } + return data + }) + .then((data) => { + if (data.errors) { + throw Error(data.errors[0].message) + } + }) +} + +export const getInstanceDBConfig = ({ credentials }) => + get({ + url: CONFIG_URL, + credentials, + }) + +export const getInstanceConfigDescriptions = ({ credentials }) => + get({ + url: DESCRIPTIONS_URL, + credentials, + }) + +export const getAvailableFrontends = ({ credentials }) => + promisedRequest({ + url: FRONTENDS_URL, + credentials, + }) + +export const pushInstanceDBConfig = ({ credentials, payload }) => + promisedRequest({ + url: CONFIG_URL, + method: 'POST', + credentials, + payload, + }) + +export const installFrontend = ({ credentials, payload }) => + promisedRequest({ + url: FRONTENDS_INSTALL_URL, + credentials, + method: 'POST', + payload, + }) + +// Emoji packs +export const deleteEmojiPack = ({ name }) => + promisedRequest({ + url: EMOJI_PACK_URL(name), + method: 'DELETE', + }) + +export const reloadEmoji = ({ credentials }) => + promisedRequest({ + url: EMOJI_RELOAD_URL, + method: 'POST', + credentials, + }) + +export const importEmojiFromFS = ({ credentials }) => + promisedRequest({ + url: EMOJI_IMPORT_FS_URL, + credentials, + }) + +export const createEmojiPack = ({ name, credentials }) => + promisedRequest({ + url: EMOJI_PACK_URL(name), + method: 'POST', + credentials, + }) + +export const listEmojiPacks = ({ page, pageSize, credentials }) => + promisedRequest({ + url: EMOJI_PACKS_URL(page, pageSize), + }) + +export const listRemoteEmojiPacks = ({ + instance, + page, + pageSize, + credentials, +}) => { + if (!instance.startsWith('http')) { + instance = 'https://' + instance + } + + return promisedRequest({ + url: EMOJI_PACKS_LS_REMOTE_URL(instance, page, pageSize), + credentials, + }) +} + +export const downloadRemoteEmojiPack = ({ + instance, + packName, + as, + credentials, +}) => + promisedRequest({ + url: EMOJI_PACKS_DL_REMOTE_URL, + credentials, + method: 'POST', + payload: { + url: instance, + name: packName, + as, + }, + }) + +export const downloadRemoteEmojiPackZIP = ({ + url, + packName, + file, + credentials, +}) => { + const data = new FormData() + if (file) data.set('file', file) + if (url) data.set('url', url) + data.set('name', packName) + + return promisedRequest({ + url: EMOJI_PACKS_DL_REMOTE_ZIP_URL, + method: 'POST', + payload: data, + }) +} + +export const saveEmojiPackMetadata = ({ name, newData, credentials }) => + promisedRequest({ + url: EMOJI_PACK_URL(name), + credentials, + method: 'PATCH', + payload: { metadata: newData }, + }) + +export const addNewEmojiFile = ({ packName, file, shortcode, filename }) => { + const data = new FormData() + if (filename.trim() !== '') { + data.set('filename', filename) + } + if (shortcode.trim() !== '') { + data.set('shortcode', shortcode) + } + data.set('file', file) + + return promisedRequest({ + url: EMOJI_UPDATE_FILE_URL(packName), + method: 'POST', + payload: data, + }) +} + +export const updateEmojiFile = ({ + packName, + shortcode, + newShortcode, + newFilename, + credentials, + force, +}) => + promisedRequest({ + url: EMOJI_UPDATE_FILE_URL(packName), + credentials, + method: 'PATCH', + payload: { + shortcode, + new_shortcode: newShortcode, + new_filename: newFilename, + force, + }, + }) + +export const deleteEmojiFile = ({ packName, shortcode }) => + promisedRequest({ + url: `${EMOJI_UPDATE_FILE_URL(packName)}&shortcode=${shortcode}`, + method: 'DELETE', + }) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 5ab1fa18e..4ed04d58d 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -9,7 +9,9 @@ import { parseStatus, parseUser, } from '../entity_normalizer/entity_normalizer.service.js' -import { RegistrationError, StatusCodeError } from '../errors/errors' +import { promisedRequest } from './helpers.js' + +import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' /* eslint-env browser */ const MUTES_IMPORT_URL = '/api/pleroma/mutes_import' @@ -116,12 +118,6 @@ const PLEROMA_CHAT_READ_URL = (id) => `/api/v1/pleroma/chats/${id}/read` const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}` const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups' -const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements' -const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements' -const PLEROMA_EDIT_ANNOUNCEMENT_URL = (id) => - `/api/v1/pleroma/admin/announcements/${id}` -const PLEROMA_DELETE_ANNOUNCEMENT_URL = (id) => - `/api/v1/pleroma/admin/announcements/${id}` const PLEROMA_SCROBBLES_URL = (id) => `/api/v1/pleroma/accounts/${id}/scrobbles` const PLEROMA_STATUS_QUOTES_URL = (id) => `/api/v1/pleroma/statuses/${id}/quotes` @@ -130,186 +126,22 @@ const PLEROMA_USER_FAVORITES_TIMELINE_URL = (id) => const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders' const PLEROMA_BOOKMARK_FOLDER_URL = (id) => `/api/v1/pleroma/bookmark_folders/${id}` - -const PLEROMA_ADMIN_REPORTS = '/api/v1/pleroma/admin/reports' -const PLEROMA_ADMIN_CONFIG_URL = '/api/v1/pleroma/admin/config' -const PLEROMA_ADMIN_DESCRIPTIONS_URL = - '/api/v1/pleroma/admin/config/descriptions' -const PLEROMA_ADMIN_FRONTENDS_URL = '/api/v1/pleroma/admin/frontends' -const PLEROMA_ADMIN_FRONTENDS_INSTALL_URL = - '/api/v1/pleroma/admin/frontends/install' - -const PLEROMA_ADMIN_USERS_URL = '/api/v1/pleroma/admin/users' -const PLEROMA_ADMIN_USERS_URL_SHOW = (nickname) => - `/api/v1/pleroma/admin/users/${nickname}` -const PLEROMA_ADMIN_USERS_URL_LIST = ({ - page, - pageSize, - filters = {}, - query = '', - name = '', - email = '', -}) => { - const { - local = false, - external = false, - active = false, - needApproval = false, - unconfirmed = false, - deactivated = false, - isAdmin = true, - isModerator = true, - } = filters - const filters_str = [ - local && 'local', - external && 'external', - active && 'active', - needApproval && 'need_approval', - unconfirmed && 'unconfirmed', - deactivated && 'deactivated', - isAdmin && 'is_admin', - isModerator && 'is_moderator', - ] - .filter((x) => x) - .join(',') - return `/api/v1/pleroma/admin/users?page=${page}&page_size=${pageSize}&filters=${filters_str}&query=${query}&name=${name}&email=${email}` -} -const PLEROMA_ADMIN_TAG_USER_URL = '/api/pleroma/admin/users/tag' -const PLEROMA_ADMIN_PERMISSION_GROUP_URL = (right) => - `/api/pleroma/admin/users/permission_group/${right}` -const PLEROMA_ADMIN_ACTIVATE_USERS_URL = '/api/pleroma/admin/users/activate' -const PLEROMA_ADMIN_DEACTIVATE_USERS_URL = '/api/pleroma/admin/users/deactivate' -const PLEROMA_ADMIN_SUGGEST_USERS_URL = '/api/pleroma/admin/users/suggest' -const PLEROMA_ADMIN_UNSUGGEST_USERS_URL = '/api/pleroma/admin/users/unsuggest' -const PLEROMA_ADMIN_APPROVE_USERS_URL = '/api/v1/pleroma/admin/users/approve' -const PLEROMA_ADMIN_CONFIRM_USERS_URL = - '/api/v1/pleroma/admin/users/confirm_email' -const PLEROMA_ADMIN_RESEND_CONFIRMATION_EMAIL_URL = - '/api/v1/pleroma/admin/users/resend_confirmation_email' -const PLEROMA_ADMIN_LIST_STATUSES_URL = ({ - id, - page, - pageSize, - godmode, - withReblogs, -}) => - `/api/v1/pleroma/admin/users/${id}/statuses?page_size=${pageSize}&page=${page}&godmode=${godmode}&with_reblogs=${withReblogs}` -const PLEROMA_ADMIN_CHANGE_STATUS_SCOPE_URL = (id) => - `/api/v1/pleroma/admin/statuses/${id}` -const PLEROMA_ADMIN_REQUIRE_PASSWORD_CHANGE_URL = - '/api/v1/pleroma/admin/users/force_password_reset' -const PLEROMA_ADMIN_DISABLE_MFA_URL = '/api/v1/pleroma/admin/users/disable_mfa' -const PLEROMA_EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji' -const PLEROMA_EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import' -const PLEROMA_EMOJI_PACKS_URL = (page, pageSize) => - `/api/v1/pleroma/emoji/packs?page=${page}&page_size=${pageSize}` -const PLEROMA_EMOJI_PACK_URL = (name) => - `/api/v1/pleroma/emoji/pack?name=${name}` -const PLEROMA_EMOJI_PACKS_DL_REMOTE_URL = '/api/v1/pleroma/emoji/packs/download' -const PLEROMA_EMOJI_PACKS_DL_REMOTE_ZIP_URL = - '/api/v1/pleroma/emoji/packs/download_zip' -const PLEROMA_EMOJI_PACKS_LS_REMOTE_URL = (url, page, pageSize) => - `/api/v1/pleroma/emoji/packs/remote?url=${url}&page=${page}&page_size=${pageSize}` -const PLEROMA_EMOJI_UPDATE_FILE_URL = (name) => - `/api/v1/pleroma/emoji/packs/files?name=${name}` - -const oldfetch = window.fetch - -const fetch = (url, options) => { - options = options || {} - const baseUrl = '' - const fullUrl = baseUrl + url - options.credentials = 'same-origin' - return oldfetch(fullUrl, options) -} - -const promisedRequest = ({ - method, - url, - params, - payload, - credentials, - headers = {}, -}) => { - const options = { - method, - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - ...headers, - }, - } - if (params) { - url += - '?' + - Object.entries(params) - .map( - ([key, value]) => - encodeURIComponent(key) + '=' + encodeURIComponent(value), - ) - .join('&') - } - if (payload) { - options.body = JSON.stringify(payload) - } - if (credentials) { - options.headers = { - ...options.headers, - ...authHeaders(credentials), - } - } - return fetch(url, options).then((response) => { - return new Promise((resolve, reject) => { - // 204 is "No content", which fails to parse json (as you'd might think) - if (response.ok && response.status === 204) resolve() - - return response - .json() - .then((json) => { - if (!response.ok) { - return reject( - new StatusCodeError( - response.status, - json, - { url, options }, - response, - ), - ) - } - return resolve(json) - }) - .catch((error) => { - return reject( - new StatusCodeError( - response.status, - error, - { url, options }, - response, - ), - ) - }) - }) - }) -} - -const updateNotificationSettings = ({ credentials, settings }) => { +export const updateNotificationSettings = ({ credentials, settings }) => { const form = new FormData() each(settings, (value, key) => { form.append(key, value) }) - return fetch( - `${NOTIFICATION_SETTINGS_URL}?${new URLSearchParams(settings)}`, - { - headers: authHeaders(credentials), - method: 'PUT', - body: form, - }, - ).then((data) => data.json()) + return promisedRequest({ + url: `${NOTIFICATION_SETTINGS_URL}?${new URLSearchParams(settings)}`, + credentials, + method: 'PUT', + formData: form, + }) } -const updateProfileImages = ({ +export const updateProfileImages = ({ credentials, avatar = null, avatarName = null, @@ -326,21 +158,20 @@ const updateProfileImages = ({ } if (banner !== null) form.append('header', banner) if (background !== null) form.append('pleroma_background_image', background) - return fetch(MASTODON_PROFILE_UPDATE_URL, { - headers: authHeaders(credentials), + return promisedRequest({ + url: MASTODON_PROFILE_UPDATE_URL, + credentials, method: 'PATCH', - body: form, + formData: form, + }).then((data) => { + if (data.error) { + throw new Error(data.error) + } + return parseUser(data) }) - .then((data) => data.json()) - .then((data) => { - if (data.error) { - throw new Error(data.error) - } - return parseUser(data) - }) } -const updateProfile = ({ credentials, params }) => { +export const updateProfile = ({ credentials, params }) => { const formData = new FormData() for (const name in params) { @@ -360,23 +191,21 @@ const updateProfile = ({ credentials, params }) => { } } - return fetch(MASTODON_PROFILE_UPDATE_URL, { - headers: authHeaders(credentials), + return promisedRequest({ + url: MASTODON_PROFILE_UPDATE_URL, + credentials, method: 'PATCH', - body: formData, - }) - .then((data) => data.json()) - .then((data) => parseUser(data)) + formData, + }).then((data) => parseUser(data)) } -const updateProfileJSON = ({ credentials, params }) => { - return promisedRequest({ +export const updateProfileJSON = ({ credentials, params }) => + promisedRequest({ url: MASTODON_PROFILE_UPDATE_URL, credentials, payload: params, method: 'PATCH', }).then((data) => parseUser(data)) -} // Params needed: // nickname @@ -391,109 +220,87 @@ const updateProfileJSON = ({ credentials, params }) => { // location // token // language -const register = ({ params, credentials }) => { +export const register = ({ params, credentials }) => { const { nickname, ...rest } = params - return fetch(MASTODON_REGISTRATION_URL, { + return promisedRequest({ + url: MASTODON_REGISTRATION_URL, method: 'POST', - headers: { - ...authHeaders(credentials), - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ + credentials, + payload: { nickname, locale: 'en_US', agreement: true, ...rest, - }), - }).then((response) => { - if (response.ok) { - return response.json() - } else { - return response.json().then((error) => { - throw new RegistrationError(error) - }) - } + }, }) } -const getCaptcha = () => - fetch('/api/pleroma/captcha').then((resp) => resp.json()) +export const getCaptcha = () => + promisedRequest({ + url: '/api/pleroma/captcha', + }) -const authHeaders = (accessToken) => { - if (accessToken) { - return { Authorization: `Bearer ${accessToken}` } - } else { - return {} - } -} - -const followUser = ({ id, credentials, ...options }) => { - const url = MASTODON_FOLLOW_URL(id) +export const followUser = ({ id, credentials, ...options }) => { const form = {} + if (options.reblogs !== undefined) { form.reblogs = options.reblogs } + if (options.notify !== undefined) { form.notify = options.notify } - return fetch(url, { - body: JSON.stringify(form), - headers: { - ...authHeaders(credentials), - 'Content-Type': 'application/json', - }, - method: 'POST', - }).then((data) => data.json()) -} -const unfollowUser = ({ id, credentials }) => { - const url = MASTODON_UNFOLLOW_URL(id) - return fetch(url, { - headers: authHeaders(credentials), - method: 'POST', - }).then((data) => data.json()) -} - -const fetchUserInLists = ({ id, credentials }) => { - const url = MASTODON_USER_IN_LISTS(id) - return fetch(url, { - headers: authHeaders(credentials), - }).then((data) => data.json()) -} - -const pinOwnStatus = ({ id, credentials }) => { return promisedRequest({ + url: MASTODON_FOLLOW_URL(id), + formData: form, + credentials, + method: 'POST', + }) +} + +export const unfollowUser = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_UNFOLLOW_URL(id), + credentials, + method: 'POST', + }) + +export const fetchUserInLists = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_USER_IN_LISTS(id), + credentials, + }) + +export const pinOwnStatus = ({ id, credentials }) => + promisedRequest({ url: MASTODON_PIN_OWN_STATUS(id), credentials, method: 'POST', }).then((data) => parseStatus(data)) -} -const unpinOwnStatus = ({ id, credentials }) => { - return promisedRequest({ +export const unpinOwnStatus = ({ id, credentials }) => + promisedRequest({ url: MASTODON_UNPIN_OWN_STATUS(id), credentials, method: 'POST', }).then((data) => parseStatus(data)) -} -const muteConversation = ({ id, credentials }) => { - return promisedRequest({ +export const muteConversation = ({ id, credentials }) => + promisedRequest({ url: MASTODON_MUTE_CONVERSATION(id), credentials, method: 'POST', }).then((data) => parseStatus(data)) -} -const unmuteConversation = ({ id, credentials }) => { - return promisedRequest({ +export const unmuteConversation = ({ id, credentials }) => + promisedRequest({ url: MASTODON_UNMUTE_CONVERSATION(id), credentials, method: 'POST', }).then((data) => parseStatus(data)) -} -const blockUser = ({ id, expiresIn, credentials }) => { +export const blockUser = ({ id, expiresIn, credentials }) => { const payload = {} if (expiresIn) { payload.duration = expiresIn @@ -507,22 +314,22 @@ const blockUser = ({ id, expiresIn, credentials }) => { }) } -const unblockUser = ({ id, credentials }) => { - return fetch(MASTODON_UNBLOCK_USER_URL(id), { - headers: authHeaders(credentials), +export const unblockUser = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_UNBLOCK_USER_URL(id), + credentials, method: 'POST', - }).then((data) => data.json()) -} + }) -const removeUserFromFollowers = ({ id, credentials }) => { - return fetch(MASTODON_REMOVE_USER_FROM_FOLLOWERS(id), { - headers: authHeaders(credentials), +export const removeUserFromFollowers = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_REMOVE_USER_FROM_FOLLOWERS(id), + credentials, method: 'POST', - }).then((data) => data.json()) -} + }) -const editUserNote = ({ id, credentials, comment }) => { - return promisedRequest({ +export const editUserNote = ({ id, credentials, comment }) => + promisedRequest({ url: MASTODON_USER_NOTE_URL(id), credentials, payload: { @@ -530,31 +337,29 @@ const editUserNote = ({ id, credentials, comment }) => { }, method: 'POST', }) -} -const approveUser = ({ id, credentials }) => { - const url = MASTODON_APPROVE_USER_URL(id) - return fetch(url, { - headers: authHeaders(credentials), +export const approveUser = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_APPROVE_USER_URL(id), + credentials, method: 'POST', - }).then((data) => data.json()) -} + }) -const denyUser = ({ id, credentials }) => { - const url = MASTODON_DENY_USER_URL(id) - return fetch(url, { - headers: authHeaders(credentials), +export const denyUser = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_DENY_USER_URL(id), + credentials, method: 'POST', - }).then((data) => data.json()) -} + }) -const fetchUser = ({ id, credentials }) => { - const url = `${MASTODON_USER_URL}/${id}` - return promisedRequest({ url, credentials }).then((data) => parseUser(data)) -} +export const fetchUser = ({ id, credentials }) => + promisedRequest({ + url: `${MASTODON_USER_URL}/${id}`, + credentials, + }).then((data) => parseUser(data)) -const fetchUserByName = ({ name, credentials }) => { - return promisedRequest({ +export const fetchUserByName = ({ name, credentials }) => + promisedRequest({ url: MASTODON_USER_LOOKUP_URL, credentials, params: { acct: name }, @@ -570,25 +375,20 @@ const fetchUserByName = ({ name, credentials }) => { } }) .then((id) => fetchUser({ id, credentials })) -} -const fetchUserRelationship = ({ id, credentials }) => { - const url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}` - return fetch(url, { headers: authHeaders(credentials) }).then((response) => { - return new Promise((resolve, reject) => - response.json().then((json) => { - if (!response.ok) { - return reject( - new StatusCodeError(response.status, json, { url }, response), - ) - } - return resolve(json) - }), - ) +export const fetchUserRelationship = ({ id, credentials }) => + promisedRequest({ + url: `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}`, + credentials, }) -} -const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => { +export const fetchFriends = ({ + id, + maxId, + sinceId, + limit = 20, + credentials, +}) => { let url = MASTODON_FOLLOWING_URL(id) const args = [ maxId && `max_id=${maxId}`, @@ -600,12 +400,13 @@ const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => { .join('&') url = url + (args ? '?' + args : '') - return fetch(url, { headers: authHeaders(credentials) }) - .then((data) => data.json()) - .then((data) => data.map(parseUser)) + return promisedRequest({ + url, + credentials, + }).then((data) => data.map(parseUser)) } -const exportFriends = ({ id, credentials }) => { +export const exportFriends = ({ id, credentials }) => { // biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO refactor this return new Promise(async (resolve, reject) => { try { @@ -626,7 +427,13 @@ const exportFriends = ({ id, credentials }) => { }) } -const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => { +export const fetchFollowers = ({ + id, + maxId, + sinceId, + limit = 20, + credentials, +}) => { let url = MASTODON_FOLLOWERS_URL(id) const args = [ maxId && `max_id=${maxId}`, @@ -638,263 +445,131 @@ const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => { .join('&') url += args ? '?' + args : '' - return fetch(url, { headers: authHeaders(credentials) }) - .then((data) => data.json()) - .then((data) => data.map(parseUser)) + return promisedRequest({ + url, + credentials, + }).then((data) => data.map(parseUser)) } -const fetchFollowRequests = ({ credentials }) => { - const url = MASTODON_FOLLOW_REQUESTS_URL - return fetch(url, { headers: authHeaders(credentials) }) - .then((data) => data.json()) - .then((data) => data.map(parseUser)) -} +export const fetchFollowRequests = ({ credentials }) => + promisedRequest({ + url: MASTODON_FOLLOW_REQUESTS_URL, + credentials, + }).then((data) => data.map(parseUser)) -const fetchLists = ({ credentials }) => { - const url = MASTODON_LISTS_URL - return fetch(url, { headers: authHeaders(credentials) }).then((data) => - data.json(), - ) -} +export const fetchLists = ({ credentials }) => + promisedRequest({ + url: MASTODON_LISTS_URL, + credentials, + }) -const createList = ({ title, credentials }) => { - const url = MASTODON_LISTS_URL - const headers = authHeaders(credentials) - headers['Content-Type'] = 'application/json' - - return fetch(url, { - headers, +export const createList = ({ title, credentials }) => + promisedRequest({ + url: MASTODON_LISTS_URL, + credentials, method: 'POST', - body: JSON.stringify({ title }), - }).then((data) => data.json()) -} + payload: { title }, + }) -const getList = ({ listId, credentials }) => { - const url = MASTODON_LIST_URL(listId) - return fetch(url, { headers: authHeaders(credentials) }).then((data) => - data.json(), - ) -} +export const getList = ({ listId, credentials }) => + promisedRequest({ + url: MASTODON_LIST_URL(listId), + credentials, + }) -const updateList = ({ listId, title, credentials }) => { - const url = MASTODON_LIST_URL(listId) - const headers = authHeaders(credentials) - headers['Content-Type'] = 'application/json' +export const updateList = ({ listId, title, credentials }) => + promisedRequest({ + url: MASTODON_LIST_URL(listId), - return fetch(url, { - headers, + credentials, method: 'PUT', - body: JSON.stringify({ title }), + payload: { title }, }) -} -const getListAccounts = ({ listId, credentials }) => { - const url = MASTODON_LIST_ACCOUNTS_URL(listId) - return fetch(url, { headers: authHeaders(credentials) }) - .then((data) => data.json()) - .then((data) => data.map(({ id }) => id)) -} +export const getListAccounts = ({ listId, credentials }) => + promisedRequest({ + url: MASTODON_LIST_ACCOUNTS_URL(listId), + credentials, + }).then((data) => data.map(({ id }) => id)) -const addAccountsToList = ({ listId, accountIds, credentials }) => { - const url = MASTODON_LIST_ACCOUNTS_URL(listId) - const headers = authHeaders(credentials) - headers['Content-Type'] = 'application/json' - - return fetch(url, { - headers, +export const addAccountsToList = ({ listId, accountIds, credentials }) => + promisedRequest({ + url: MASTODON_LIST_ACCOUNTS_URL(listId), + credentials, method: 'POST', - body: JSON.stringify({ account_ids: accountIds }), + payload: { account_ids: accountIds }, }) -} -const removeAccountsFromList = ({ listId, accountIds, credentials }) => { - const url = MASTODON_LIST_ACCOUNTS_URL(listId) - const headers = authHeaders(credentials) - headers['Content-Type'] = 'application/json' - - return fetch(url, { - headers, +export const removeAccountsFromList = ({ listId, accountIds, credentials }) => + promisedRequest({ + url: MASTODON_LIST_ACCOUNTS_URL(listId), + credentials, method: 'DELETE', - body: JSON.stringify({ account_ids: accountIds }), + payload: { account_ids: accountIds }, }) -} -const deleteList = ({ listId, credentials }) => { - const url = MASTODON_LIST_URL(listId) - return fetch(url, { +export const deleteList = ({ listId, credentials }) => + promisedRequest({ + url: MASTODON_LIST_URL(listId), method: 'DELETE', - headers: authHeaders(credentials), + credentials, }) -} -const fetchConversation = ({ id, credentials }) => { - const urlContext = MASTODON_STATUS_CONTEXT_URL(id) - return fetch(urlContext, { headers: authHeaders(credentials) }) +export const fetchConversation = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_STATUS_CONTEXT_URL(id), + credentials, + }) .then((data) => { if (data.ok) { return data } throw new Error('Error fetching timeline', data) }) - .then((data) => data.json()) .then(({ ancestors, descendants }) => ({ ancestors: ancestors.map(parseStatus), descendants: descendants.map(parseStatus), })) -} -const fetchStatus = ({ id, credentials }) => { - const url = MASTODON_STATUS_URL(id) - return fetch(url, { headers: authHeaders(credentials) }) +export const fetchStatus = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_STATUS_URL(id), + credentials, + }) .then((data) => { if (data.ok) { return data } throw new Error('Error fetching timeline', { cause: data }) }) - .then((data) => data.json()) .then((data) => parseStatus(data)) -} -const fetchStatusSource = ({ id, credentials }) => { - const url = MASTODON_STATUS_SOURCE_URL(id) - return fetch(url, { headers: authHeaders(credentials) }) +export const fetchStatusSource = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_STATUS_SOURCE_URL(id), + credentials, + }) .then((data) => { if (data.ok) { return data } throw new Error('Error fetching source', { cause: data }) }) - .then((data) => data.json()) .then((data) => parseSource(data)) -} -const fetchStatusHistory = ({ status, credentials }) => { - const url = MASTODON_STATUS_HISTORY_URL(status.id) - return promisedRequest({ url, credentials }).then((data) => { +export const fetchStatusHistory = ({ status, credentials }) => + promisedRequest({ + url: MASTODON_STATUS_HISTORY_URL(status.id), + credentials, + }).then((data) => { data.reverse() return data.map((item) => { item.originalStatus = status return parseStatus(item) }) }) -} -const adminSetUsersTags = ({ - tags, - credentials, - value, - screen_names: nicknames, -}) => { - return promisedRequest({ - url: PLEROMA_ADMIN_TAG_USER_URL, - method: value ? 'PUT' : 'DELETE', - credentials, - payload: { - nicknames, - tags, - }, - }) -} - -const adminSetUsersRight = ({ - right, - credentials, - value, - screen_names: nicknames, -}) => { - return promisedRequest({ - url: PLEROMA_ADMIN_PERMISSION_GROUP_URL(right), - method: value ? 'POST' : 'DELETE', - credentials, - payload: { - nicknames, - }, - }) -} - -const adminSetUsersActivationStatus = ({ - credentials, - screen_names: nicknames, - value, -}) => { - return promisedRequest({ - url: value - ? PLEROMA_ADMIN_ACTIVATE_USERS_URL - : PLEROMA_ADMIN_DEACTIVATE_USERS_URL, - method: 'PATCH', - credentials, - payload: { - nicknames, - }, - }).then((response) => response.users) -} - -const adminSetUsersApprovalStatus = ({ - credentials, - screen_names: nicknames, -}) => { - return promisedRequest({ - url: PLEROMA_ADMIN_APPROVE_USERS_URL, - method: 'PATCH', - credentials, - payload: { - nicknames, - }, - }).then((response) => response.users) -} - -const adminSetUsersConfirmationStatus = ({ - credentials, - screen_names: nicknames, -}) => { - return promisedRequest({ - url: PLEROMA_ADMIN_CONFIRM_USERS_URL, - method: 'PATCH', - credentials, - payload: { - nicknames, - }, - }).then((response) => response.users) -} - -const adminSetUsersSuggestionStatus = ({ - credentials, - screen_names: nicknames, - value, -}) => { - return promisedRequest({ - url: value - ? PLEROMA_ADMIN_SUGGEST_USERS_URL - : PLEROMA_ADMIN_UNSUGGEST_USERS_URL, - method: 'PATCH', - credentials, - payload: { - nicknames, - }, - }).then((response) => response.users) -} - -const adminGetUserData = ({ credentials, screen_name: nickname }) => { - return promisedRequest({ - url: PLEROMA_ADMIN_USERS_URL_SHOW(nickname), - method: 'GET', - credentials, - }) -} - -const adminDeleteAccounts = ({ credentials, screen_names: nicknames }) => { - return promisedRequest({ - url: PLEROMA_ADMIN_USERS_URL, - method: 'DELETE', - credentials, - payload: { - nicknames, - }, - }) -} - -const fetchTimeline = ({ +export const fetchTimeline = ({ timeline, credentials, since = false, @@ -989,109 +664,82 @@ const fetchTimeline = ({ ) url += `?${queryString}` - return fetch(url, { headers: authHeaders(credentials) }).then( - async (response) => { - const success = response.ok - - const data = await response.json() - - if (success && !data.errors) { - const pagination = parseLinkHeaderPagination( - response.headers.get('Link'), - { - flakeId: timeline !== 'bookmarks' && timeline !== 'notifications', - }, - ) - - return { - data: data.map(isNotifications ? parseNotification : parseStatus), - pagination, - } - } else { - data.errors ||= [] - data.status = response.status - data.statusText = response.statusText - return data - } - }, - ) -} - -const fetchPinnedStatuses = ({ id, credentials }) => { - const url = MASTODON_USER_TIMELINE_URL(id) + '?pinned=true' - return promisedRequest({ url, credentials }).then((data) => - data.map(parseStatus), - ) -} - -const verifyCredentials = (user) => { - return fetch(MASTODON_LOGIN_URL, { - headers: authHeaders(user), - }) - .then((response) => { - if (response.ok) { - return response.json() - } else { - return { - error: response, - } - } - }) - .then((data) => (data.error ? data : parseUser(data))) -} - -const favorite = ({ id, credentials }) => { return promisedRequest({ + url, + credentials, + }).then(async (data) => { + const pagination = parseLinkHeaderPagination( + data._response.headers.get('Link'), + { + flakeId: timeline !== 'bookmarks' && timeline !== 'notifications', + }, + ) + + return { + data: data.map(isNotifications ? parseNotification : parseStatus), + pagination, + } + }) +} + +export const fetchPinnedStatuses = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_USER_TIMELINE_URL(id) + '?pinned=true', + credentials, + }).then((data) => data.map(parseStatus)) + +export const verifyCredentials = ({ credentials }) => + promisedRequest({ + url: MASTODON_LOGIN_URL, + credentials, + }).then((data) => (data.error ? data : parseUser(data))) + +export const favorite = ({ id, credentials }) => + promisedRequest({ url: MASTODON_FAVORITE_URL(id), method: 'POST', credentials, }).then((data) => parseStatus(data)) -} -const unfavorite = ({ id, credentials }) => { - return promisedRequest({ +export const unfavorite = ({ id, credentials }) => + promisedRequest({ url: MASTODON_UNFAVORITE_URL(id), method: 'POST', credentials, }).then((data) => parseStatus(data)) -} -const retweet = ({ id, credentials }) => { - return promisedRequest({ +export const retweet = ({ id, credentials }) => + promisedRequest({ url: MASTODON_RETWEET_URL(id), method: 'POST', credentials, }).then((data) => parseStatus(data)) -} -const unretweet = ({ id, credentials }) => { - return promisedRequest({ +export const unretweet = ({ id, credentials }) => + promisedRequest({ url: MASTODON_UNRETWEET_URL(id), method: 'POST', credentials, }).then((data) => parseStatus(data)) -} -const bookmarkStatus = ({ id, credentials, ...options }) => { - return promisedRequest({ +export const bookmarkStatus = ({ id, credentials, ...options }) => + promisedRequest({ url: MASTODON_BOOKMARK_STATUS_URL(id), - headers: authHeaders(credentials), + credentials, method: 'POST', payload: { folder_id: options.folder_id, }, }) -} -const unbookmarkStatus = ({ id, credentials }) => { - return promisedRequest({ +export const unbookmarkStatus = ({ id, credentials }) => + promisedRequest({ url: MASTODON_UNBOOKMARK_STATUS_URL(id), - headers: authHeaders(credentials), + credentials, method: 'POST', }) -} -const postStatus = ({ +export const postStatus = ({ credentials, status, spoilerText, @@ -1140,23 +788,21 @@ const postStatus = ({ form.append('preview', 'true') } - const postHeaders = authHeaders(credentials) + const headers = {} if (idempotencyKey) { - postHeaders['idempotency-key'] = idempotencyKey + headers['idempotency-key'] = idempotencyKey } - return fetch(MASTODON_POST_STATUS_URL, { - body: form, + return promisedRequest({ + url: MASTODON_POST_STATUS_URL, + formData: form, method: 'POST', - headers: postHeaders, - }) - .then((response) => { - return response.json() - }) - .then((data) => (data.error ? data : parseStatus(data))) + credentials, + headers, + }).then((data) => (data.error ? data : parseStatus(data))) } -const editStatus = ({ +export const editStatus = ({ id, credentials, status, @@ -1191,136 +837,131 @@ const editStatus = ({ }) } - const putHeaders = authHeaders(credentials) - - return fetch(MASTODON_STATUS_URL(id), { - body: form, + return promisedRequest({ + url: MASTODON_STATUS_URL(id), + formData: form, method: 'PUT', - headers: putHeaders, - }) - .then((response) => { - return response.json() - }) - .then((data) => (data.error ? data : parseStatus(data))) + credentials, + }).then((data) => (data.error ? data : parseStatus(data))) } -const deleteStatus = ({ id, credentials }) => { - return promisedRequest({ +export const deleteStatus = ({ id, credentials }) => + promisedRequest({ url: MASTODON_DELETE_URL(id), credentials, method: 'DELETE', }) -} -const uploadMedia = ({ formData, credentials }) => { - return fetch(MASTODON_MEDIA_UPLOAD_URL, { - body: formData, +export const uploadMedia = ({ formData, credentials }) => + promisedRequest({ + url: MASTODON_MEDIA_UPLOAD_URL, + formData, method: 'POST', - headers: authHeaders(credentials), - }) - .then((data) => data.json()) - .then((data) => parseAttachment(data)) -} + credentials, + }).then((data) => parseAttachment(data)) -const setMediaDescription = ({ id, description, credentials }) => { - return promisedRequest({ +export const setMediaDescription = ({ id, description, credentials }) => + promisedRequest({ url: `${MASTODON_MEDIA_UPLOAD_URL}/${id}`, method: 'PUT', - headers: authHeaders(credentials), + credentials, payload: { description, }, }).then((data) => parseAttachment(data)) -} -const importMutes = ({ file, credentials }) => { +export const importMutes = ({ file, credentials }) => { const formData = new FormData() formData.append('list', file) - return fetch(MUTES_IMPORT_URL, { - body: formData, + return promisedRequest({ + url: MUTES_IMPORT_URL, + formData, method: 'POST', - headers: authHeaders(credentials), + credentials, }).then((response) => response.ok) } -const importBlocks = ({ file, credentials }) => { +export const importBlocks = ({ file, credentials }) => { const formData = new FormData() formData.append('list', file) - return fetch(BLOCKS_IMPORT_URL, { - body: formData, + return promisedRequest({ + url: BLOCKS_IMPORT_URL, + formData, method: 'POST', - headers: authHeaders(credentials), + credentials, }).then((response) => response.ok) } -const importFollows = ({ file, credentials }) => { +export const importFollows = ({ file, credentials }) => { const formData = new FormData() formData.append('list', file) - return fetch(FOLLOW_IMPORT_URL, { - body: formData, + return promisedRequest({ + url: FOLLOW_IMPORT_URL, + formData, method: 'POST', - headers: authHeaders(credentials), + credentials, }).then((response) => response.ok) } -const deleteAccount = ({ credentials, password }) => { - const form = new FormData() +export const deleteAccount = ({ credentials, password }) => { + const formData = new FormData() - form.append('password', password) + formData.append('password', password) - return fetch(DELETE_ACCOUNT_URL, { - body: form, + return promisedRequest({ + url: DELETE_ACCOUNT_URL, + formData, method: 'POST', - headers: authHeaders(credentials), - }).then((response) => response.json()) + credentials, + }) } -const changeEmail = ({ credentials, email, password }) => { +export const changeEmail = ({ credentials, email, password }) => { const form = new FormData() form.append('email', email) form.append('password', password) - return fetch(CHANGE_EMAIL_URL, { - body: form, + return promisedRequest({ + url: CHANGE_EMAIL_URL, + formData: form, method: 'POST', - headers: authHeaders(credentials), - }).then((response) => response.json()) + credentials, + }) } -const moveAccount = ({ credentials, password, targetAccount }) => { +export const moveAccount = ({ credentials, password, targetAccount }) => { const form = new FormData() form.append('password', password) form.append('target_account', targetAccount) - return fetch(MOVE_ACCOUNT_URL, { - body: form, + return promisedRequest({ + url: MOVE_ACCOUNT_URL, + formData: form, method: 'POST', - headers: authHeaders(credentials), - }).then((response) => response.json()) + credentials, + }) } -const addAlias = ({ credentials, alias }) => { - return promisedRequest({ +export const addAlias = ({ credentials, alias }) => + promisedRequest({ url: ALIASES_URL, method: 'PUT', credentials, payload: { alias }, }) -} -const deleteAlias = ({ credentials, alias }) => { - return promisedRequest({ +export const deleteAlias = ({ credentials, alias }) => + promisedRequest({ url: ALIASES_URL, method: 'DELETE', credentials, payload: { alias }, }) -} -const listAliases = ({ credentials }) => { - return promisedRequest({ +export const listAliases = ({ credentials }) => + promisedRequest({ url: ALIASES_URL, method: 'GET', credentials, @@ -1328,9 +969,8 @@ const listAliases = ({ credentials }) => { _cacheBooster: new Date().getTime(), }, }) -} -const changePassword = ({ +export const changePassword = ({ credentials, password, newPassword, @@ -1342,58 +982,61 @@ const changePassword = ({ form.append('new_password', newPassword) form.append('new_password_confirmation', newPasswordConfirmation) - return fetch(CHANGE_PASSWORD_URL, { - body: form, + return promisedRequest({ + url: CHANGE_PASSWORD_URL, + formData: form, method: 'POST', - headers: authHeaders(credentials), - }).then((response) => response.json()) + credentials, + }) } -const settingsMFA = ({ credentials }) => { - return fetch(MFA_SETTINGS_URL, { - headers: authHeaders(credentials), +export const settingsMFA = ({ credentials }) => + promisedRequest({ + url: MFA_SETTINGS_URL, + credentials, method: 'GET', - }).then((data) => data.json()) -} + }) -const mfaDisableOTP = ({ credentials, password }) => { +export const mfaDisableOTP = ({ credentials, password }) => { const form = new FormData() form.append('password', password) - return fetch(MFA_DISABLE_OTP_URL, { - body: form, + return promisedRequest({ + url: MFA_DISABLE_OTP_URL, + formData: form, method: 'DELETE', - headers: authHeaders(credentials), - }).then((response) => response.json()) + credentials, + }) } -const mfaConfirmOTP = ({ credentials, password, token }) => { +export const mfaConfirmOTP = ({ credentials, password, token }) => { const form = new FormData() form.append('password', password) form.append('code', token) - return fetch(MFA_CONFIRM_OTP_URL, { - body: form, - headers: authHeaders(credentials), + return promisedRequest({ + url: MFA_CONFIRM_OTP_URL, + formData: form, + credentials, method: 'POST', - }).then((data) => data.json()) + }) } -const mfaSetupOTP = ({ credentials }) => { - return fetch(MFA_SETUP_OTP_URL, { - headers: authHeaders(credentials), +export const mfaSetupOTP = ({ credentials }) => + promisedRequest({ + url: MFA_SETUP_OTP_URL, + credentials, method: 'GET', - }).then((data) => data.json()) -} -const generateMfaBackupCodes = ({ credentials }) => { - return fetch(MFA_BACKUP_CODES_URL, { - headers: authHeaders(credentials), + }) +export const generateMfaBackupCodes = ({ credentials }) => + promisedRequest({ + url: MFA_BACKUP_CODES_URL, + credentials, method: 'GET', - }).then((data) => data.json()) -} + }) -const fetchMutes = ({ maxId, credentials }) => { +export const fetchMutes = ({ maxId, credentials }) => { const query = new URLSearchParams({ with_relationships: true }) if (maxId) { query.append('max_id', maxId) @@ -1404,7 +1047,7 @@ const fetchMutes = ({ maxId, credentials }) => { }).then((users) => users.map(parseUser)) } -const muteUser = ({ id, expiresIn, credentials }) => { +export const muteUser = ({ id, expiresIn, credentials }) => { const payload = {} if (expiresIn) { payload.expires_in = expiresIn @@ -1418,15 +1061,14 @@ const muteUser = ({ id, expiresIn, credentials }) => { }) } -const unmuteUser = ({ id, credentials }) => { - return promisedRequest({ +export const unmuteUser = ({ id, credentials }) => + promisedRequest({ url: MASTODON_UNMUTE_USER_URL(id), credentials, method: 'POST', }) -} -const fetchBlocks = ({ maxId, credentials }) => { +export const fetchBlocks = ({ maxId, credentials }) => { const query = new URLSearchParams({ with_relationships: true }) if (maxId) { query.append('max_id', maxId) @@ -1437,16 +1079,15 @@ const fetchBlocks = ({ maxId, credentials }) => { }).then((users) => users.map(parseUser)) } -const addBackup = ({ credentials }) => { - return promisedRequest({ +export const addBackup = ({ credentials }) => + promisedRequest({ url: PLEROMA_BACKUP_URL, method: 'POST', credentials, }) -} -const listBackups = ({ credentials }) => { - return promisedRequest({ +export const listBackups = ({ credentials }) => + promisedRequest({ url: PLEROMA_BACKUP_URL, method: 'GET', credentials, @@ -1454,53 +1095,48 @@ const listBackups = ({ credentials }) => { _cacheBooster: new Date().getTime(), }, }) -} -const fetchOAuthTokens = ({ credentials }) => { - const url = '/api/oauth_tokens.json' - - return fetch(url, { - headers: authHeaders(credentials), - }).then((data) => { - if (data.ok) { - return data.json() - } - throw new Error('Error fetching auth tokens', data) +export const fetchOAuthTokens = ({ credentials }) => + promisedRequest({ + url: '/api/oauth_tokens.json', + credentials, }) -} -const revokeOAuthToken = ({ id, credentials }) => { - const url = `/api/oauth_tokens/${id}` - - return fetch(url, { - headers: authHeaders(credentials), +export const revokeOAuthToken = ({ id, credentials }) => + promisedRequest({ + url: `/api/oauth_tokens/${id}`, + credentials, method: 'DELETE', }) -} -const suggestions = ({ credentials }) => { - return fetch(SUGGESTIONS_URL, { - headers: authHeaders(credentials), - }).then((data) => data.json()) -} +export const suggestions = ({ credentials }) => + promisedRequest({ + url: SUGGESTIONS_URL, + credentials, + }) -const markNotificationsAsSeen = ({ id, credentials, single = false }) => { - const body = new FormData() +export const markNotificationsAsSeen = ({ + id, + credentials, + single = false, +}) => { + const formData = new FormData() if (single) { - body.append('id', id) + formData.append('id', id) } else { - body.append('max_id', id) + formData.append('max_id', id) } - return fetch(NOTIFICATION_READ_URL, { - body, - headers: authHeaders(credentials), + return promisedRequest({ + url: NOTIFICATION_READ_URL, + formData, + credentials, method: 'POST', - }).then((data) => data.json()) + }) } -const vote = ({ pollId, choices, credentials }) => { +export const vote = ({ pollId, choices, credentials }) => { const form = new FormData() form.append('choices', choices) @@ -1514,32 +1150,29 @@ const vote = ({ pollId, choices, credentials }) => { }) } -const fetchPoll = ({ pollId, credentials }) => { - return promisedRequest({ +export const fetchPoll = ({ pollId, credentials }) => + promisedRequest({ url: MASTODON_POLL_URL(encodeURIComponent(pollId)), method: 'GET', credentials, }) -} -const fetchFavoritedByUsers = ({ id, credentials }) => { - return promisedRequest({ +export const fetchFavoritedByUsers = ({ id, credentials }) => + promisedRequest({ url: MASTODON_STATUS_FAVORITEDBY_URL(id), method: 'GET', credentials, }).then((users) => users.map(parseUser)) -} -const fetchRebloggedByUsers = ({ id, credentials }) => { - return promisedRequest({ +export const fetchRebloggedByUsers = ({ id, credentials }) => + promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id), method: 'GET', credentials, }).then((users) => users.map(parseUser)) -} -const fetchEmojiReactions = ({ id, credentials }) => { - return promisedRequest({ +export const fetchEmojiReactions = ({ id, credentials }) => + promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id), credentials, }).then((reactions) => @@ -1548,26 +1181,29 @@ const fetchEmojiReactions = ({ id, credentials }) => { return r }), ) -} -const reactWithEmoji = ({ id, emoji, credentials }) => { - return promisedRequest({ +export const reactWithEmoji = ({ id, emoji, credentials }) => + promisedRequest({ url: PLEROMA_EMOJI_REACT_URL(id, emoji), method: 'PUT', credentials, }).then(parseStatus) -} -const unreactWithEmoji = ({ id, emoji, credentials }) => { - return promisedRequest({ +export const unreactWithEmoji = ({ id, emoji, credentials }) => + promisedRequest({ url: PLEROMA_EMOJI_UNREACT_URL(id, emoji), method: 'DELETE', credentials, }).then(parseStatus) -} -const reportUser = ({ credentials, userId, statusIds, comment, forward }) => { - return promisedRequest({ +export const reportUser = ({ + credentials, + userId, + statusIds, + comment, + forward, +}) => + promisedRequest({ url: MASTODON_REPORT_USER_URL, method: 'POST', payload: { @@ -1578,10 +1214,9 @@ const reportUser = ({ credentials, userId, statusIds, comment, forward }) => { }, credentials, }) -} -const searchUsers = ({ credentials, query }) => { - return promisedRequest({ +export const searchUsers = ({ credentials, query }) => + promisedRequest({ url: MASTODON_USER_SEARCH_URL, params: { q: query, @@ -1589,9 +1224,8 @@ const searchUsers = ({ credentials, query }) => { }, credentials, }).then((data) => data.map(parseUser)) -} -const search2 = ({ +export const search2 = ({ credentials, q, resolve, @@ -1634,16 +1268,16 @@ const search2 = ({ ) url += `?${queryString}` - return fetch(url, { headers: authHeaders(credentials) }) + return promisedRequest({ + url, + credentials, + }) .then((data) => { if (data.ok) { return data } throw new Error('Error fetching search result', data) }) - .then((data) => { - return data.json() - }) .then((data) => { data.accounts = data.accounts.slice(0, limit).map((u) => parseUser(u)) data.statuses = data.statuses.slice(0, limit).map((s) => parseStatus(s)) @@ -1651,197 +1285,45 @@ const search2 = ({ }) } -const fetchKnownDomains = ({ credentials }) => { - return promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials }) -} +export const fetchKnownDomains = ({ credentials }) => + promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials }) -const fetchDomainMutes = ({ credentials }) => { - return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials }) -} +export const fetchDomainMutes = ({ credentials }) => + promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials }) -const muteDomain = ({ domain, credentials }) => { - return promisedRequest({ +export const muteDomain = ({ domain, credentials }) => + promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, method: 'POST', payload: { domain }, credentials, }) -} -const unmuteDomain = ({ domain, credentials }) => { - return promisedRequest({ +export const unmuteDomain = ({ domain, credentials }) => + promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, method: 'DELETE', payload: { domain }, credentials, }) -} -const dismissNotification = ({ credentials, id }) => { - return promisedRequest({ +export const dismissNotification = ({ credentials, id }) => + promisedRequest({ url: MASTODON_DISMISS_NOTIFICATION_URL(id), method: 'POST', payload: { id }, credentials, }) -} -const adminFetchAnnouncements = ({ credentials }) => { - return promisedRequest({ url: PLEROMA_ANNOUNCEMENTS_URL, credentials }) -} +export const getAnnouncements = ({ credentials }) => + promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials }) -const fetchAnnouncements = ({ credentials }) => { - return promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials }) -} - -const dismissAnnouncement = ({ id, credentials }) => { - return promisedRequest({ +export const dismissAnnouncement = ({ id, credentials }) => + promisedRequest({ url: MASTODON_ANNOUNCEMENTS_DISMISS_URL(id), credentials, method: 'POST', }) -} - -const adminListUsers = ({ opts, credentials }) => { - // the reported list is hardly useful because standards are for dating i guess, - // so make sure to fetchIfMissing right afterward using this call - const url = PLEROMA_ADMIN_USERS_URL_LIST(opts) - - return promisedRequest({ - url, - credentials, - method: 'GET', - }) -} - -const adminResendConfirmationEmail = ({ - screen_names: nicknames, - credentials, -}) => { - const url = PLEROMA_ADMIN_RESEND_CONFIRMATION_EMAIL_URL - return promisedRequest({ - url, - credentials, - method: 'PATCH', - payload: { - nicknames, - }, - }) -} - -const adminRequirePasswordChange = ({ - screen_names: nicknames, - credentials, -}) => { - const url = PLEROMA_ADMIN_REQUIRE_PASSWORD_CHANGE_URL - return promisedRequest({ - url, - credentials, - method: 'PATCH', - payload: { - nicknames, - }, - }) -} - -const adminDisableMFA = ({ screen_name: nickname, credentials }) => { - const url = PLEROMA_ADMIN_DISABLE_MFA_URL - return promisedRequest({ - url, - credentials, - method: 'PUT', - payload: { - nickname, - }, - }) -} - -const adminListStatuses = ({ opts, credentials }) => { - const url = PLEROMA_ADMIN_LIST_STATUSES_URL(opts) - - return promisedRequest({ - url, - credentials, - method: 'GET', - }) -} - -const adminChangeStatusScope = ({ - opts: { id, sensitive, visibility }, - credentials, -}) => { - const url = PLEROMA_ADMIN_CHANGE_STATUS_SCOPE_URL(id) - var payload = {} - if (typeof sensitive !== 'undefined') { - payload['sensitive'] = sensitive - } - if (typeof visibility !== 'undefined') { - payload['visibility'] = visibility - } - return promisedRequest({ - url, - credentials, - method: 'PUT', - payload, - }) -} - -const announcementToPayload = ({ content, startsAt, endsAt, allDay }) => { - const payload = { content } - - if (typeof startsAt !== 'undefined') { - payload.starts_at = startsAt ? new Date(startsAt).toISOString() : null - } - - if (typeof endsAt !== 'undefined') { - payload.ends_at = endsAt ? new Date(endsAt).toISOString() : null - } - - if (typeof allDay !== 'undefined') { - payload.all_day = allDay - } - - return payload -} - -const postAnnouncement = ({ - credentials, - content, - startsAt, - endsAt, - allDay, -}) => { - return promisedRequest({ - url: PLEROMA_POST_ANNOUNCEMENT_URL, - credentials, - method: 'POST', - payload: announcementToPayload({ content, startsAt, endsAt, allDay }), - }) -} - -const editAnnouncement = ({ - id, - credentials, - content, - startsAt, - endsAt, - allDay, -}) => { - return promisedRequest({ - url: PLEROMA_EDIT_ANNOUNCEMENT_URL(id), - credentials, - method: 'PATCH', - payload: announcementToPayload({ content, startsAt, endsAt, allDay }), - }) -} - -const deleteAnnouncement = ({ id, credentials }) => { - return promisedRequest({ - url: PLEROMA_DELETE_ANNOUNCEMENT_URL(id), - credentials, - method: 'DELETE', - }) -} export const getMastodonSocketURI = ( { credentials, stream, args = {} }, @@ -2018,23 +1500,28 @@ export const WSConnectionStatus = Object.freeze({ STARTING_INITIAL: 6, }) -const chats = ({ credentials }) => { - return fetch(PLEROMA_CHATS_URL, { headers: authHeaders(credentials) }) - .then((data) => data.json()) - .then((data) => { - return { chats: data.map(parseChat).filter((c) => c) } - }) -} +export const chats = ({ credentials }) => + promisedRequest({ + url: PLEROMA_CHATS_URL, + credentials, + }).then((data) => { + chats: data.map(parseChat).filter((c) => c) + }) -const getOrCreateChat = ({ accountId, credentials }) => { - return promisedRequest({ +export const getOrCreateChat = ({ accountId, credentials }) => + promisedRequest({ url: PLEROMA_CHAT_URL(accountId), method: 'POST', credentials, }) -} -const chatMessages = ({ id, credentials, maxId, sinceId, limit = 20 }) => { +export const chatMessages = ({ + id, + credentials, + maxId, + sinceId, + limit = 20, +}) => { let url = PLEROMA_CHAT_MESSAGES_URL(id) const args = [ maxId && `max_id=${maxId}`, @@ -2053,7 +1540,7 @@ const chatMessages = ({ id, credentials, maxId, sinceId, limit = 20 }) => { }) } -const sendChatMessage = ({ +export const sendChatMessage = ({ id, content, mediaId = null, @@ -2083,8 +1570,8 @@ const sendChatMessage = ({ }) } -const readChat = ({ id, lastReadId, credentials }) => { - return promisedRequest({ +export const readChat = ({ id, lastReadId, credentials }) => + promisedRequest({ url: PLEROMA_CHAT_READ_URL(id), method: 'POST', payload: { @@ -2092,436 +1579,49 @@ const readChat = ({ id, lastReadId, credentials }) => { }, credentials, }) -} -const deleteChatMessage = ({ chatId, messageId, credentials }) => { - return promisedRequest({ +export const deleteChatMessage = ({ chatId, messageId, credentials }) => + promisedRequest({ url: PLEROMA_DELETE_CHAT_MESSAGE_URL(chatId, messageId), method: 'DELETE', credentials, }) -} -const setReportState = ({ id, state, credentials }) => { - // TODO: Can't use promisedRequest because on OK this does not return json - // See https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1322 - return fetch(PLEROMA_ADMIN_REPORTS, { - headers: { - ...authHeaders(credentials), - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - method: 'PATCH', - body: JSON.stringify({ - reports: [ - { - id, - state, - }, - ], - }), - }) - .then((data) => { - if (data.status >= 500) { - throw Error(data.statusText) - } else if (data.status >= 400) { - return data.json() - } - return data - }) - .then((data) => { - if (data.errors) { - throw Error(data.errors[0].message) - } - }) -} - -// ADMIN STUFF // EXPERIMENTAL -const fetchInstanceDBConfig = ({ credentials }) => { - return fetch(PLEROMA_ADMIN_CONFIG_URL, { - headers: authHeaders(credentials), - }).then((response) => { - if (response.ok) { - return response.json() - } else { - return { - error: response, - } - } - }) -} - -const fetchInstanceConfigDescriptions = ({ credentials }) => { - return fetch(PLEROMA_ADMIN_DESCRIPTIONS_URL, { - headers: authHeaders(credentials), - }).then((response) => { - if (response.ok) { - return response.json() - } else { - return { - error: response, - } - } - }) -} - -const fetchAvailableFrontends = ({ credentials }) => { - return fetch(PLEROMA_ADMIN_FRONTENDS_URL, { - headers: authHeaders(credentials), - }).then((response) => { - if (response.ok) { - return response.json() - } else { - return { - error: response, - } - } - }) -} - -const pushInstanceDBConfig = ({ credentials, payload }) => { - return fetch(PLEROMA_ADMIN_CONFIG_URL, { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - ...authHeaders(credentials), - }, - method: 'POST', - body: JSON.stringify(payload), - }).then((response) => { - if (response.ok) { - return response.json() - } else { - return { - error: response, - } - } - }) -} - -const installFrontend = ({ credentials, payload }) => { - return fetch(PLEROMA_ADMIN_FRONTENDS_INSTALL_URL, { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - ...authHeaders(credentials), - }, - method: 'POST', - body: JSON.stringify(payload), - }).then((response) => { - if (response.ok) { - return response.json() - } else { - return { - error: response, - } - } - }) -} - -const fetchScrobbles = ({ accountId, limit = 1 }) => { +export const fetchScrobbles = ({ accountId, limit = 1 }) => { let url = PLEROMA_SCROBBLES_URL(accountId) const params = [['limit', limit]] const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join( '&', ) url += `?${queryString}` - return fetch(url, {}).then((response) => { - if (response.ok) { - return response.json() - } else { - return { - error: response, - } - } + return promisedRequest({ url }) +} + +export const fetchBookmarkFolders = ({ credentials }) => + promisedRequest({ + url: PLEROMA_BOOKMARK_FOLDERS_URL, + credentials, }) -} -const deleteEmojiPack = ({ name }) => { - return fetch(PLEROMA_EMOJI_PACK_URL(name), { method: 'DELETE' }) -} - -const reloadEmoji = () => { - return fetch(PLEROMA_EMOJI_RELOAD_URL, { method: 'POST' }) -} - -const importEmojiFromFS = () => { - return fetch(PLEROMA_EMOJI_IMPORT_FS_URL) -} - -const createEmojiPack = ({ name }) => { - return fetch(PLEROMA_EMOJI_PACK_URL(name), { method: 'POST' }) -} - -const listEmojiPacks = ({ page, pageSize }) => { - return fetch(PLEROMA_EMOJI_PACKS_URL(page, pageSize)) -} - -const listRemoteEmojiPacks = ({ instance, page, pageSize }) => { - if (!instance.startsWith('http')) { - instance = 'https://' + instance - } - - return fetch(PLEROMA_EMOJI_PACKS_LS_REMOTE_URL(instance, page, pageSize), { - headers: { 'Content-Type': 'application/json' }, - }) -} - -const downloadRemoteEmojiPack = ({ instance, packName, as }) => { - return fetch(PLEROMA_EMOJI_PACKS_DL_REMOTE_URL, { +export const createBookmarkFolder = ({ name, emoji, credentials }) => + promisedRequest({ + url: PLEROMA_BOOKMARK_FOLDERS_URL, + credentials, method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - url: instance, - name: packName, - as, - }), + payload: { name, emoji }, }) -} -const downloadRemoteEmojiPackZIP = ({ url, packName, file }) => { - const data = new FormData() - if (file) data.set('file', file) - if (url) data.set('url', url) - data.set('name', packName) - - return fetch(PLEROMA_EMOJI_PACKS_DL_REMOTE_ZIP_URL, { - method: 'POST', - body: data, - }) -} - -const saveEmojiPackMetadata = ({ name, newData }) => { - return fetch(PLEROMA_EMOJI_PACK_URL(name), { +export const updateBookmarkFolder = ({ folderId, name, emoji, credentials }) => + promisedRequest({ + url: PLEROMA_BOOKMARK_FOLDER_URL(folderId), + credentials, method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ metadata: newData }), + payload: { name, emoji }, }) -} -const addNewEmojiFile = ({ packName, file, shortcode, filename }) => { - const data = new FormData() - if (filename.trim() !== '') { - data.set('filename', filename) - } - if (shortcode.trim() !== '') { - data.set('shortcode', shortcode) - } - data.set('file', file) - - return fetch(PLEROMA_EMOJI_UPDATE_FILE_URL(packName), { - method: 'POST', - body: data, - }) -} - -const updateEmojiFile = ({ - packName, - shortcode, - newShortcode, - newFilename, - force, -}) => { - return fetch(PLEROMA_EMOJI_UPDATE_FILE_URL(packName), { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - shortcode, - new_shortcode: newShortcode, - new_filename: newFilename, - force, - }), - }) -} - -const deleteEmojiFile = ({ packName, shortcode }) => { - return fetch( - `${PLEROMA_EMOJI_UPDATE_FILE_URL(packName)}&shortcode=${shortcode}`, - { method: 'DELETE' }, - ) -} - -const fetchBookmarkFolders = ({ credentials }) => { - const url = PLEROMA_BOOKMARK_FOLDERS_URL - return fetch(url, { headers: authHeaders(credentials) }).then((data) => - data.json(), - ) -} - -const createBookmarkFolder = ({ name, emoji, credentials }) => { - const url = PLEROMA_BOOKMARK_FOLDERS_URL - const headers = authHeaders(credentials) - headers['Content-Type'] = 'application/json' - - return fetch(url, { - headers, - method: 'POST', - body: JSON.stringify({ name, emoji }), - }).then((data) => data.json()) -} - -const updateBookmarkFolder = ({ folderId, name, emoji, credentials }) => { - const url = PLEROMA_BOOKMARK_FOLDER_URL(folderId) - const headers = authHeaders(credentials) - headers['Content-Type'] = 'application/json' - - return fetch(url, { - headers, - method: 'PATCH', - body: JSON.stringify({ name, emoji }), - }).then((data) => data.json()) -} - -const deleteBookmarkFolder = ({ folderId, credentials }) => { - const url = PLEROMA_BOOKMARK_FOLDER_URL(folderId) - return fetch(url, { +export const deleteBookmarkFolder = ({ folderId, credentials }) => + promisedRequest({ + url: PLEROMA_BOOKMARK_FOLDER_URL(folderId), method: 'DELETE', - headers: authHeaders(credentials), + credentials, }) -} - -const apiService = { - verifyCredentials, - fetchTimeline, - fetchPinnedStatuses, - fetchConversation, - fetchStatus, - fetchStatusSource, - fetchStatusHistory, - fetchFriends, - exportFriends, - fetchFollowers, - followUser, - unfollowUser, - pinOwnStatus, - unpinOwnStatus, - muteConversation, - unmuteConversation, - blockUser, - unblockUser, - removeUserFromFollowers, - editUserNote, - fetchUser, - fetchUserByName, - fetchUserRelationship, - favorite, - unfavorite, - retweet, - unretweet, - bookmarkStatus, - unbookmarkStatus, - postStatus, - editStatus, - deleteStatus, - uploadMedia, - setMediaDescription, - fetchMutes, - muteUser, - unmuteUser, - fetchBlocks, - fetchOAuthTokens, - revokeOAuthToken, - register, - getCaptcha, - updateProfileImages, - updateProfile, - updateProfileJSON, - importMutes, - importBlocks, - importFollows, - deleteAccount, - changeEmail, - moveAccount, - addAlias, - deleteAlias, - listAliases, - changePassword, - settingsMFA, - mfaDisableOTP, - generateMfaBackupCodes, - mfaSetupOTP, - mfaConfirmOTP, - addBackup, - listBackups, - fetchFollowRequests, - fetchLists, - createList, - getList, - updateList, - getListAccounts, - addAccountsToList, - removeAccountsFromList, - deleteList, - approveUser, - denyUser, - suggestions, - markNotificationsAsSeen, - dismissNotification, - vote, - fetchPoll, - fetchFavoritedByUsers, - fetchRebloggedByUsers, - fetchEmojiReactions, - reactWithEmoji, - unreactWithEmoji, - reportUser, - updateNotificationSettings, - search2, - searchUsers, - fetchKnownDomains, - fetchDomainMutes, - muteDomain, - unmuteDomain, - chats, - getOrCreateChat, - chatMessages, - sendChatMessage, - readChat, - deleteChatMessage, - setReportState, - fetchUserInLists, - fetchAnnouncements, - dismissAnnouncement, - postAnnouncement, - editAnnouncement, - deleteAnnouncement, - fetchScrobbles, - adminFetchAnnouncements, - fetchInstanceDBConfig, - fetchInstanceConfigDescriptions, - fetchAvailableFrontends, - pushInstanceDBConfig, - installFrontend, - importEmojiFromFS, - reloadEmoji, - listEmojiPacks, - createEmojiPack, - deleteEmojiPack, - saveEmojiPackMetadata, - addNewEmojiFile, - updateEmojiFile, - deleteEmojiFile, - listRemoteEmojiPacks, - downloadRemoteEmojiPack, - downloadRemoteEmojiPackZIP, - fetchBookmarkFolders, - createBookmarkFolder, - updateBookmarkFolder, - deleteBookmarkFolder, - adminListUsers, - adminGetUserData, - adminResendConfirmationEmail, - adminDeleteAccounts, - adminSetUsersRight, - adminSetUsersTags, - adminSetUsersApprovalStatus, - adminSetUsersConfirmationStatus, - adminSetUsersActivationStatus, - adminSetUsersSuggestionStatus, - adminListStatuses, - adminChangeStatusScope, - adminRequirePasswordChange, - adminDisableMFA, -} - -export default apiService diff --git a/src/services/api/helpers.js b/src/services/api/helpers.js new file mode 100644 index 000000000..80012cca3 --- /dev/null +++ b/src/services/api/helpers.js @@ -0,0 +1,87 @@ +import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' + +export const promisedRequest = ({ + method, + url, + params, + payload, + formData, + credentials, + headers = {}, +}) => { + const options = { + method, + credentials: 'same-origin', + headers: { + Accept: 'application/json', + ...headers, + }, + } + if (!formData) { + options.headers['Content-Type'] = 'application/json' + } + if (params) { + url += + '?' + + Object.entries(params) + .map( + ([key, value]) => + encodeURIComponent(key) + '=' + encodeURIComponent(value), + ) + .join('&') + } + if (formData || payload) { + options.body = formData || JSON.stringify(payload) + } + + if (credentials) { + options.headers = { + ...options.headers, + ...authHeaders(credentials), + } + } + + return fetch(url, options).then((response) => { + return new Promise((resolve, reject) => { + // 204 is "No content", which fails to parse json (as you'd might think) + if (response.ok && response.status === 204) resolve() + + return response + .json() + .then((json) => { + if (!response.ok) { + return reject( + new StatusCodeError( + response.status, + json, + { url, options }, + response, + ), + ) + } + + json._response = response + + return resolve(json) + }) + .catch((error) => { + return reject( + new StatusCodeError( + response.status, + error, + { url, options }, + response, + ), + ) + }) + }) + }) +} + +const authHeaders = (accessToken) => { + if (accessToken) { + return { Authorization: `Bearer ${accessToken}` } + } else { + return {} + } +} diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index adc18ef7f..c9ef67799 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -1,10 +1,7 @@ import bookmarkFoldersFetcher from '../../services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js' import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service' import listsFetcher from '../../services/lists_fetcher/lists_fetcher.service.js' -import apiService, { - getMastodonSocketURI, - ProcessedWS, -} from '../api/api.service.js' +import * as apiService from '../api/api.service.js' import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js' import timelineFetcher from '../timeline_fetcher/timeline_fetcher.service.js' @@ -58,8 +55,8 @@ const backendInteractorService = (credentials) => ({ startUserSocket({ store }) { const serv = useInstanceStore().server.replace('http', 'ws') - const url = getMastodonSocketURI({}, serv) - return ProcessedWS({ url, id: 'Unified', credentials }) + const url = apiService.getMastodonSocketURI({}, serv) + return apiService.ProcessedWS({ url, id: 'Unified', credentials }) }, ...Object.entries(apiService).reduce((acc, [key, func]) => { diff --git a/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js b/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js index 7b81c19dc..987b0786a 100644 --- a/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js +++ b/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js @@ -1,11 +1,10 @@ -import apiService from '../api/api.service.js' +import { fetchBookmarkFolders } from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' const fetchAndUpdate = ({ credentials }) => { - return apiService - .fetchBookmarkFolders({ credentials }) + return fetchBookmarkFolders({ credentials }) .then( (bookmarkFolders) => { useBookmarkFoldersStore().setBookmarkFolders(bookmarkFolders) diff --git a/src/services/follow_request_fetcher/follow_request_fetcher.service.js b/src/services/follow_request_fetcher/follow_request_fetcher.service.js index 530c98aa7..015eb55ed 100644 --- a/src/services/follow_request_fetcher/follow_request_fetcher.service.js +++ b/src/services/follow_request_fetcher/follow_request_fetcher.service.js @@ -1,9 +1,8 @@ -import apiService from '../api/api.service.js' +import { fetchFollowRequests } from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' const fetchAndUpdate = ({ store, credentials }) => { - return apiService - .fetchFollowRequests({ credentials }) + return fetchFollowRequests({ credentials }) .then( (requests) => { store.commit('setFollowRequests', requests) diff --git a/src/services/lists_fetcher/lists_fetcher.service.js b/src/services/lists_fetcher/lists_fetcher.service.js index c395ef93b..f647f8271 100644 --- a/src/services/lists_fetcher/lists_fetcher.service.js +++ b/src/services/lists_fetcher/lists_fetcher.service.js @@ -1,11 +1,10 @@ -import apiService from '../api/api.service.js' +import { fetchLists } from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useListsStore } from 'src/stores/lists.js' const fetchAndUpdate = ({ credentials }) => { - return apiService - .fetchLists({ credentials }) + return fetchLists({ credentials }) .then( (lists) => { useListsStore().setLists(lists) diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index c1a9e1a2f..ec6c47a5d 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -1,4 +1,4 @@ -import apiService from '../api/api.service.js' +import { fetchTimeline } from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -80,8 +80,7 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => { } const fetchNotifications = ({ store, args, older }) => { - return apiService - .fetchTimeline(args) + return fetchTimeline(args) .then((response) => { if (response.errors) { if ( diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 021c31ef8..1a814ec7c 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -1,6 +1,11 @@ import { map } from 'lodash' -import apiService from '../api/api.service.js' +import { + editStatus as apiEditStatus, + postStatus as apiPostStatus, + setMediaDescription as apiSetMediaDescription, + uploadMedia as apiUploadMedia, +} from '../api/api.service.js' const postStatus = ({ store, @@ -18,21 +23,20 @@ const postStatus = ({ }) => { const mediaIds = map(media, 'id') - return apiService - .postStatus({ - credentials: store.state.users.currentUser.credentials, - status, - spoilerText, - visibility, - sensitive, - mediaIds, - inReplyToStatusId, - quoteId, - contentType, - poll, - preview, - idempotencyKey, - }) + return apiPostStatus({ + credentials: store.state.users.currentUser.credentials, + status, + spoilerText, + visibility, + sensitive, + mediaIds, + inReplyToStatusId, + quoteId, + contentType, + poll, + preview, + idempotencyKey, + }) .then((data) => { if (!data.error && !preview) { store.dispatch('addNewStatuses', { @@ -63,17 +67,16 @@ const editStatus = ({ }) => { const mediaIds = map(media, 'id') - return apiService - .editStatus({ - id: statusId, - credentials: store.state.users.currentUser.credentials, - status, - spoilerText, - sensitive, - poll, - mediaIds, - contentType, - }) + return editStatus({ + id: statusId, + credentials: store.state.users.currentUser.credentials, + status, + spoilerText, + sensitive, + poll, + mediaIds, + contentType, + }) .then((data) => { if (!data.error) { store.dispatch('addNewStatuses', { @@ -95,12 +98,12 @@ const editStatus = ({ const uploadMedia = ({ store, formData }) => { const credentials = store.state.users.currentUser.credentials - return apiService.uploadMedia({ credentials, formData }) + return apiUploadMedia({ credentials, formData }) } const setMediaDescription = ({ store, id, description }) => { const credentials = store.state.users.currentUser.credentials - return apiService.setMediaDescription({ credentials, id, description }) + return apiSetMediaDescription({ credentials, id, description }) } const statusPosterService = { diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 68991addf..8e9b8a2e9 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -1,6 +1,6 @@ import { camelCase } from 'lodash' -import apiService from '../api/api.service.js' +import { fetchTimeline } from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -75,8 +75,7 @@ const fetchAndUpdate = ({ const numStatusesBeforeFetch = timelineData.statuses.length - return apiService - .fetchTimeline(args) + return fetchTimeline(args) .then((response) => { if (response.errors) { if (timeline === 'favorites') { diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index b07315034..04031b672 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -1,6 +1,26 @@ import { cloneDeep, differenceWith, flatten, get, isEqual, set } from 'lodash' import { defineStore } from 'pinia' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { + changeStatusScope, + deleteAccounts, + disableMFA, + getAvailableFrontends, + getInstanceDBConfig, + getUserData, + listStatuses, + listUsers, + requirePasswordChange, + resendConfirmationEmail, + setUsersActivationStatus, + setUsersApprovalStatus, + setUsersConfirmationStatus, + setUsersRight, + setUsersSuggestionStatus, + setUsersTags, +} from 'src/services/api/admin.js' import { parseStatus } from 'src/services/entity_normalizer/entity_normalizer.service.js' export const defaultState = { @@ -21,7 +41,6 @@ export const newUserFlags = { export const useAdminSettingsStore = defineStore('adminSettings', { state: () => ({ ...cloneDeep(defaultState), - backendInteractor: window.vuex.state.api.backendInteractor, }), actions: { // Configuration Stuff @@ -54,7 +73,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, loadAdminStuff() { - this.backendInteractor.fetchInstanceDBConfig().then((backendDbConfig) => { + getInstanceDBConfig({ + credentials: useCredentialsStore().current, + }).then((backendDbConfig) => { if (backendDbConfig.error) { if (backendDbConfig.error.status === 400) { backendDbConfig.error.json().then((errorJson) => { @@ -68,11 +89,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } }) if (this.descriptions === null) { - this.backendInteractor - .fetchInstanceConfigDescriptions() - .then((backendDescriptions) => - this.setInstanceAdminDescriptions({ backendDescriptions }), - ) + fetchInstanceConfigDescriptions().then((backendDescriptions) => + this.setInstanceAdminDescriptions({ backendDescriptions }), + ) } }, setInstanceAdminSettings({ backendDbConfig }) { @@ -203,15 +222,12 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } }) - window.vuex.state.api.backendInteractor - .pushInstanceDBConfig({ - payload: { - configs: changed, - }, - }) - .then(() => - window.vuex.state.api.backendInteractor.fetchInstanceDBConfig(), - ) + pushInstanceDBConfig({ + payload: { + configs: changed, + }, + }) + .then(() => fetchInstanceDBConfig()) .then((backendDbConfig) => this.setInstanceAdminSettings({ backendDbConfig }), ) @@ -234,21 +250,18 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } } - window.vuex.state.api.backendInteractor - .pushInstanceDBConfig({ - payload: { - configs: [ - { - group, - key, - value: convert(clone), - }, - ], - }, - }) - .then(() => - window.vuex.state.api.backendInteractor.fetchInstanceDBConfig(), - ) + pushInstanceDBConfig({ + payload: { + configs: [ + { + group, + key, + value: convert(clone), + }, + ], + }, + }) + .then(() => fetchInstanceDBConfig()) .then((backendDbConfig) => this.setInstanceAdminSettings({ backendDbConfig }), ) @@ -260,22 +273,19 @@ export const useAdminSettingsStore = defineStore('adminSettings', { this.modifiedPaths.delete(path) - return window.vuex.state.api.backendInteractor - .pushInstanceDBConfig({ - payload: { - configs: [ - { - group, - key, - delete: true, - subkeys: [subkey], - }, - ], - }, - }) - .then(() => - window.vuex.state.api.backendInteractor.fetchInstanceDBConfig(), - ) + return pushInstanceDBConfig({ + payload: { + configs: [ + { + group, + key, + delete: true, + subkeys: [subkey], + }, + ], + }, + }) + .then(() => fetchInstanceDBConfig()) .then((backendDbConfig) => this.setInstanceAdminSettings({ backendDbConfig }), ) @@ -283,9 +293,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { // Frontends Stuff loadFrontendsStuff() { - this.backendInteractor - .fetchAvailableFrontends() - .then((frontends) => this.setAvailableFrontends({ frontends })) + getAvailableFrontends({ + credentials: useCredentialsStore().current, + }).then((frontends) => this.setAvailableFrontends({ frontends })) }, setAvailableFrontends({ frontends }) { @@ -302,10 +312,10 @@ export const useAdminSettingsStore = defineStore('adminSettings', { // Statuses stuff async fetchStatuses(opts) { - const { total, activities } = - await this.backendInteractor.adminListStatuses({ - opts, - }) + const { total, activities } = await listStatuses({ + credentials: useCredentialsStore().current, + opts, + }) const statuses = activities.map(parseStatus) @@ -317,7 +327,8 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } }, async changeStatusScope(opts) { - const raw = await this.backendInteractor.adminChangeStatusScope({ + const raw = await changeStatusScope({ + credentials: useCredentialsStore().current, opts, }) const status = parseStatus(raw) @@ -327,7 +338,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { // Users stuff async fetchUsers(opts) { - const { users, count } = await this.backendInteractor.adminListUsers({ + const { users, count } = await listUsers({ + credentials: useCredentialsStore().current, + opts, }) @@ -344,17 +357,23 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } }, async getUserData({ user }) { - const api = this.backendInteractor.adminGetUserData + const api = getUserData const { screen_name } = user - const result = await api({ screen_name }) + const result = await api({ + credentials: useCredentialsStore().current, + screen_name, + }) window.vuex.commit('updateUserAdminData', { user: result }) }, async deleteUsers({ users }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminDeleteAccounts + const api = deleteAccounts - const resultUserIds = await api({ screen_names }) + const resultUserIds = await api({ + credentials: useCredentialsStore().current, + screen_names, + }) resultUserIds.forEach((userId) => { window.vuex.dispatch( @@ -369,28 +388,34 @@ export const useAdminSettingsStore = defineStore('adminSettings', { resendConfirmationEmail({ users }) { const screen_names = users.map((u) => u.screen_name) - return this.backendInteractor.adminResendConfirmationEmail({ - screen_names, + return resendConfirmationEmail({ + credentials: useCredentialsStore().current, + screen_names, }) }, requirePasswordChange({ users }) { const screen_names = users.map((u) => u.screen_name) - return this.backendInteractor.adminRequirePasswordChange({ - screen_names, + return requirePasswordChange({ + credentials: useCredentialsStore().current, + screen_names, }) }, // Singular only! disableMFA({ user }) { const { screen_name } = user - return this.backendInteractor.adminDisableMFA({ screen_name }) + return disableMFA({ + credentials: useCredentialsStore().current, + screen_name, + }) }, async setUsersTags({ users, tags, value }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminSetUsersTags + const api = setUsersTags await api({ + credentials: useCredentialsStore().current, screen_names, tags, value, @@ -402,9 +427,10 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersRight({ users, right, value }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminSetUsersRight + const api = setUsersRight await api({ + credentials: useCredentialsStore().current, screen_names, right, value, @@ -416,9 +442,10 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersActivationStatus({ users, value }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminSetUsersActivationStatus + const api = setUsersActivationStatus const resultUsers = await api({ + credentials: useCredentialsStore().current, screen_names, value, }) @@ -429,9 +456,10 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersSuggestionStatus({ users, value }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminSetUsersSuggestionStatus + const api = setUsersSuggestionStatus const resultUsers = await api({ + credentials: useCredentialsStore().current, screen_names, value, }) @@ -442,9 +470,12 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersConfirmationStatus({ users }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminSetUsersConfirmationStatus + const api = setUsersConfirmationStatus - await api({ screen_names }) + await api({ + credentials: useCredentialsStore().current, + screen_names, + }) users.forEach((user) => { this.getUserData({ user }) @@ -452,9 +483,10 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersApprovalStatus({ users }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminSetUsersApprovalStatus + const api = setUsersApprovalStatus const resultUsers = await api({ + credentials: useCredentialsStore().current, screen_names, }) diff --git a/src/stores/announcements.js b/src/stores/announcements.js index cb325dadd..a40d4d1e7 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -1,5 +1,18 @@ import { defineStore } from 'pinia' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { + getAnnouncements as adminGetAnnouncements, + deleteAnnouncement, + editAnnouncement, + postAnnouncement, +} from 'src/services/api/admin.js' +import { + dismissAnnouncement, + getAnnouncements, +} from 'src/services/api/api.service.js' + const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5 export const useAnnouncementsStore = defineStore('announcements', { @@ -31,15 +44,19 @@ export const useAnnouncementsStore = defineStore('announcements', { currentUser && currentUser.privileges.has('announcements_manage_announcements') - const getAnnouncements = async () => { + const fetchAnnouncements = async () => { if (!isAdmin) { - return window.vuex.state.api.backendInteractor.fetchAnnouncements() + return fetchAnnouncements({ + credentials: useCredentialsStore().current, + }) } - const all = - await window.vuex.state.api.backendInteractor.adminFetchAnnouncements() - const visible = - await window.vuex.state.api.backendInteractor.fetchAnnouncements() + const all = await adminGetAnnouncements({ + credentials: useCredentialsStore().current, + }) + const visible = await getAnnouncements({ + credentials: useCredentialsStore().current, + }) const visibleObject = visible.reduce((a, c) => { a[c.id] = c return a @@ -59,7 +76,7 @@ export const useAnnouncementsStore = defineStore('announcements', { return all } - return getAnnouncements() + return fetchAnnouncements() .then((announcements) => { this.announcements = announcements }) @@ -74,17 +91,18 @@ export const useAnnouncementsStore = defineStore('announcements', { }) }, markAnnouncementAsRead(id) { - return window.vuex.state.api.backendInteractor - .dismissAnnouncement({ id }) - .then(() => { - const index = this.announcements.findIndex((a) => a.id === id) + return dismissAnnouncement({ + id, + credentials: useCredentialsStore().current, + }).then(() => { + const index = this.announcements.findIndex((a) => a.id === id) - if (index < 0) { - return - } + if (index < 0) { + return + } - this.announcements[index].read = true - }) + this.announcements[index].read = true + }) }, startFetchingAnnouncements() { if (this.fetchAnnouncementsTimer) { @@ -105,25 +123,35 @@ export const useAnnouncementsStore = defineStore('announcements', { clearInterval(interval) }, postAnnouncement({ content, startsAt, endsAt, allDay }) { - return window.vuex.state.api.backendInteractor - .postAnnouncement({ content, startsAt, endsAt, allDay }) - .then(() => { - return this.fetchAnnouncements() - }) + return postAnnouncement({ + credentials: useCredentialsStore().current, + content, + startsAt, + endsAt, + allDay, + }).then(() => { + return this.fetchAnnouncements() + }) }, editAnnouncement({ id, content, startsAt, endsAt, allDay }) { - return window.vuex.state.api.backendInteractor - .editAnnouncement({ id, content, startsAt, endsAt, allDay }) - .then(() => { - return this.fetchAnnouncements() - }) + return editAnnouncement({ + id, + content, + startsAt, + endsAt, + allDay, + credentials: useCredentialsStore().current, + }).then(() => { + return this.fetchAnnouncements() + }) }, deleteAnnouncement(id) { - return window.vuex.state.api.backendInteractor - .deleteAnnouncement({ id }) - .then(() => { - return this.fetchAnnouncements() - }) + return deleteAnnouncement({ + id, + credentials: useCredentialsStore().current, + }).then(() => { + return this.fetchAnnouncements() + }) }, }, }) diff --git a/src/stores/bookmark_folders.js b/src/stores/bookmark_folders.js index 028322f9d..54e38f701 100644 --- a/src/stores/bookmark_folders.js +++ b/src/stores/bookmark_folders.js @@ -1,6 +1,14 @@ import { find, remove } from 'lodash' import { defineStore } from 'pinia' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { + createBookmarkFolder, + deleteBookmarkFolder, + updateBookmarkFolder, +} from 'src/services/api/api.service.js' + export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { state: () => ({ allFolders: [], @@ -30,23 +38,31 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { } }, createBookmarkFolder({ name, emoji }) { - return window.vuex.state.api.backendInteractor - .createBookmarkFolder({ name, emoji }) - .then((folder) => { - this.setBookmarkFolder(folder) - return folder - }) + return createBookmarkFolder({ + name, + emoji, + credentials: useCredentialsStore().current, + }).then((folder) => { + this.setBookmarkFolder(folder) + return folder + }) }, updateBookmarkFolder({ folderId, name, emoji }) { - return window.vuex.state.api.backendInteractor - .updateBookmarkFolder({ folderId, name, emoji }) - .then((folder) => { - this.setBookmarkFolder(folder) - return folder - }) + return updateBookmarkFolder({ + credentials: useCredentialsStore().current, + folderId, + name, + emoji, + }).then((folder) => { + this.setBookmarkFolder(folder) + return folder + }) }, deleteBookmarkFolder({ folderId }) { - window.vuex.state.api.backendInteractor.deleteBookmarkFolder({ folderId }) + deleteBookmarkFolder({ + folderId, + credentials: useCredentialsStore().current, + }) remove(this.allFolders, (folder) => folder.id === folderId) }, }, diff --git a/src/stores/credentials.js b/src/stores/credentials.js new file mode 100644 index 000000000..2070162c4 --- /dev/null +++ b/src/stores/credentials.js @@ -0,0 +1,19 @@ +import { defineStore } from 'pinia' + +const defaultState = { + credentials: null, +} + +export const useCredentialsStore = defineStore('credentials', { + state: () => ({ ...defaultState }), + actions: { + setCredentials(credentials) { + this.credentials = credentials + }, + }, + getters: { + current() { + return window.vuex.state.users.currentUser.credentials + }, + }, +}) diff --git a/src/stores/instance.js b/src/stores/instance.js index 54b3cf43c..2f9bf3c44 100644 --- a/src/stores/instance.js +++ b/src/stores/instance.js @@ -11,7 +11,7 @@ import { LOCAL_DEFAULT_CONFIG_DEFINITIONS, validateSetting, } from '../modules/default_config_state.js' -import apiService from '../services/api/api.service.js' +import { fetchKnownDomains } from '../services/api/api.service.js' import { useInterfaceStore } from 'src/stores/interface.js' @@ -210,7 +210,7 @@ export const useInstanceStore = defineStore('instance', { }, async getKnownDomains() { try { - this.knownDomains = await apiService.fetchKnownDomains({ + this.knownDomains = await fetchKnownDomains({ credentials: window.vuex.state.users.currentUser.credentials, }) } catch (e) { diff --git a/src/stores/reports.js b/src/stores/reports.js index b5e2307c4..be4ada5e1 100644 --- a/src/stores/reports.js +++ b/src/stores/reports.js @@ -1,8 +1,11 @@ import { filter } from 'lodash' import { defineStore } from 'pinia' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInterfaceStore } from 'src/stores/interface.js' +import { setReportState } from 'src/services/api/admin.js' + export const useReportsStore = defineStore('reports', { state: () => ({ reportModal: { @@ -40,18 +43,21 @@ export const useReportsStore = defineStore('reports', { setReportState({ id, state }) { const oldState = this.reports[id].state this.reports[id].state = state - window.vuex.state.api.backendInteractor - .setReportState({ id, state }) - .catch((e) => { - console.error('Failed to set report state', e) - useInterfaceStore().pushGlobalNotice({ - level: 'error', - messageKey: 'general.generic_error_message', - messageArgs: [e.message], - timeout: 5000, - }) - this.reports[id].state = oldState + + setReportState({ + id, + state, + credentials: useCredentialsStore().current, + }).catch((e) => { + console.error('Failed to set report state', e) + useInterfaceStore().pushGlobalNotice({ + level: 'error', + messageKey: 'general.generic_error_message', + messageArgs: [e.message], + timeout: 5000, }) + this.reports[id].state = oldState + }) }, addReport(report) { this.reports[report.id] = report From 28efd7ebd25488db72c79c9bcfd93614d3e2487c Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 13 Jun 2026 23:32:08 +0300 Subject: [PATCH 02/86] fix chats api --- .../status_action_buttons.js | 2 +- src/modules/chats.js | 14 +++-- src/modules/statuses.js | 5 +- src/services/api/admin.js | 4 +- src/services/api/api.service.js | 6 +- src/stores/admin_settings.js | 56 +++++++++++++++---- 6 files changed, 63 insertions(+), 24 deletions(-) diff --git a/src/components/status_action_buttons/status_action_buttons.js b/src/components/status_action_buttons/status_action_buttons.js index 2d0de25ef..e02b71b0c 100644 --- a/src/components/status_action_buttons/status_action_buttons.js +++ b/src/components/status_action_buttons/status_action_buttons.js @@ -107,7 +107,7 @@ const StatusActionButtons = { button .action?.(this.funcArg) .then(() => this.$emit('onSuccess')) - .catch((err) => this.$emit('onError', err.error.error)) + .catch((err) => this.$emit('onError', err)) }, onExtraClose() { this.showPin = false diff --git a/src/modules/chats.js b/src/modules/chats.js index 308b2cb27..3c0ed7d3e 100644 --- a/src/modules/chats.js +++ b/src/modules/chats.js @@ -9,6 +9,10 @@ import { } from '../services/entity_normalizer/entity_normalizer.service.js' import { promiseInterval } from '../services/promise_interval/promise_interval.js' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { chats } from 'src/services/api/api.service.js' + const emptyChatList = () => ({ data: [], idStore: {}, @@ -36,7 +40,7 @@ const unreadChatCount = (state) => { return sumBy(state.chatList.data, 'unread') } -const chats = { +const chatsModule = { state: { ...defaultState }, getters: { currentChat: (state) => state.openedChats[state.currentChatId], @@ -60,8 +64,10 @@ const chats = { commit('setChatListFetcher', { fetcher: undefined }) }, fetchChats({ dispatch, rootState }) { - return rootState.api.backendInteractor.chats().then(({ chats }) => { - dispatch('addNewChats', { chats }) + return chats({ + credentials: useCredentialsStore().current, + }).then(({ chatList }) => { + dispatch('addNewChats', { chats: chatList }) return chats }) }, @@ -262,4 +268,4 @@ const chats = { }, } -export default chats +export default chatsModule diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 32df8aadf..6c8caafdd 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -20,6 +20,7 @@ import { fetchStatusSource, } from '../services/api/api.service.js' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' @@ -613,7 +614,7 @@ const statuses = { fetchStatusSource({ rootState }, status) { return fetchStatusSource({ id: status.id, - credentials: rootState.users.currentUser.credentials, + credentials: useCredentialsStore().current, }) }, fetchStatusHistory(_, status) { @@ -622,7 +623,7 @@ const statuses = { deleteStatus({ rootState, commit }, status) { deleteStatus({ id: status.id, - credentials: rootState.users.currentUser.credentials, + credentials: useCredentialsStore().current, }) .then(() => { commit('setDeleted', { status }) diff --git a/src/services/api/admin.js b/src/services/api/admin.js index 66d4fcb93..debc61575 100644 --- a/src/services/api/admin.js +++ b/src/services/api/admin.js @@ -347,13 +347,13 @@ export const setReportState = ({ id, state, credentials }) => { } export const getInstanceDBConfig = ({ credentials }) => - get({ + promisedRequest({ url: CONFIG_URL, credentials, }) export const getInstanceConfigDescriptions = ({ credentials }) => - get({ + promisedRequest({ url: DESCRIPTIONS_URL, credentials, }) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 4ed04d58d..9a7d266ae 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1504,9 +1504,9 @@ export const chats = ({ credentials }) => promisedRequest({ url: PLEROMA_CHATS_URL, credentials, - }).then((data) => { - chats: data.map(parseChat).filter((c) => c) - }) + }).then((data) => ({ + chatList: data.map(parseChat).filter((c) => c), + })) export const getOrCreateChat = ({ accountId, credentials }) => promisedRequest({ diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index 04031b672..8f4ce5352 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -8,10 +8,12 @@ import { deleteAccounts, disableMFA, getAvailableFrontends, + getInstanceConfigDescriptions, getInstanceDBConfig, getUserData, listStatuses, listUsers, + pushInstanceDBConfig, requirePasswordChange, resendConfirmationEmail, setUsersActivationStatus, @@ -85,12 +87,20 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) } } else { - this.setInstanceAdminSettings({ backendDbConfig }) + this.setInstanceAdminSettings({ + credentials: useCredentialsStore().current, + backendDbConfig, + }) } }) if (this.descriptions === null) { - fetchInstanceConfigDescriptions().then((backendDescriptions) => - this.setInstanceAdminDescriptions({ backendDescriptions }), + getInstanceConfigDescriptions({ + credentials: useCredentialsStore().current, + }).then((backendDescriptions) => + this.setInstanceAdminDescriptions({ + credentials: useCredentialsStore().current, + backendDescriptions, + }), ) } }, @@ -223,13 +233,22 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) pushInstanceDBConfig({ + credentials: useCredentialsStore().current, payload: { configs: changed, }, }) - .then(() => fetchInstanceDBConfig()) + .then(() => + getInstanceDBConfig({ + credentials: useCredentialsStore().current, + }), + ) .then((backendDbConfig) => - this.setInstanceAdminSettings({ backendDbConfig }), + this.setInstanceAdminSettings({ + credentials: useCredentialsStore().current, + + backendDbConfig, + }), ) }, pushAdminSetting({ path, value }) { @@ -251,6 +270,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } pushInstanceDBConfig({ + credentials: useCredentialsStore().current, payload: { configs: [ { @@ -261,9 +281,16 @@ export const useAdminSettingsStore = defineStore('adminSettings', { ], }, }) - .then(() => fetchInstanceDBConfig()) + .then(() => + getInstanceDBConfig({ + credentials: useCredentialsStore().current, + }), + ) .then((backendDbConfig) => - this.setInstanceAdminSettings({ backendDbConfig }), + this.setInstanceAdminSettings({ + credentials: useCredentialsStore().current, + backendDbConfig, + }), ) }, resetAdminSetting({ path }) { @@ -274,6 +301,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { this.modifiedPaths.delete(path) return pushInstanceDBConfig({ + credentials: useCredentialsStore().current, payload: { configs: [ { @@ -285,7 +313,11 @@ export const useAdminSettingsStore = defineStore('adminSettings', { ], }, }) - .then(() => fetchInstanceDBConfig()) + .then(() => + getInstanceDBConfig({ + credentials: useCredentialsStore().current, + }), + ) .then((backendDbConfig) => this.setInstanceAdminSettings({ backendDbConfig }), ) @@ -389,16 +421,16 @@ export const useAdminSettingsStore = defineStore('adminSettings', { const screen_names = users.map((u) => u.screen_name) return resendConfirmationEmail({ - credentials: useCredentialsStore().current, - screen_names, + credentials: useCredentialsStore().current, + screen_names, }) }, requirePasswordChange({ users }) { const screen_names = users.map((u) => u.screen_name) return requirePasswordChange({ - credentials: useCredentialsStore().current, - screen_names, + credentials: useCredentialsStore().current, + screen_names, }) }, // Singular only! From 0d9709825f561e3d6fb0d55bc8a100d99d9ebaf0 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 15 Jun 2026 20:02:22 +0300 Subject: [PATCH 03/86] remaining backend interactor removals --- src/boot/after_store.js | 7 +- src/components/chat/chat.js | 72 ++-- src/components/chat_new/chat_new.js | 9 +- src/components/conversation/conversation.js | 24 +- .../follow_request_card.js | 22 +- src/components/notification/notification.js | 22 +- .../remote_user_resolver.js | 13 +- .../settings_modal/admin_tabs/emoji_tab.js | 21 +- .../admin_tabs/frontends_tab.js | 2 +- .../helpers/emoji_editing_popover.vue | 55 +-- .../settings_modal/tabs/appearance_tab.js | 8 +- .../settings_modal/tabs/composing_tab.js | 15 +- .../tabs/data_import_export_tab.js | 57 ++- .../settings_modal/tabs/general_tab.js | 15 +- .../tabs/mutes_and_blocks_tab.js | 33 +- .../settings_modal/tabs/notifications_tab.js | 7 +- .../settings_modal/tabs/profile_tab.js | 11 +- .../settings_modal/tabs/security_tab/mfa.js | 47 ++- .../tabs/security_tab/mfa_totp.js | 32 +- .../tabs/security_tab/security_tab.js | 114 +++--- .../still-image/still-image-emoji-popover.js | 3 +- src/components/user_card/user_card.js | 5 +- .../user_reporting_modal.js | 7 +- src/components/who_to_follow/who_to_follow.js | 26 +- .../who_to_follow_panel.js | 23 +- src/modules/api.js | 84 ++--- src/modules/chats.js | 17 +- src/modules/notifications.js | 8 +- src/modules/profileConfig.js | 39 +- src/modules/statuses.js | 279 ++++++++------ src/modules/users.js | 351 ++++++++++-------- src/services/api/api.service.js | 9 + .../backend_interactor_service.js | 72 ---- .../bookmark_folders_fetcher.service.js | 31 -- .../follow_manipulate/follow_manipulate.js | 66 ++-- .../lists_fetcher/lists_fetcher.service.js | 31 -- src/stores/admin_settings.js | 73 ++++ src/stores/bookmark_folders.js | 19 + src/stores/emoji.js | 10 +- src/stores/lists.js | 132 ++++--- src/stores/oauth_tokens.js | 32 +- src/stores/polls.js | 39 +- src/stores/sync_config.js | 7 +- src/stores/user_highlight.js | 16 +- .../specs/components/user_profile.spec.js | 9 - 45 files changed, 1118 insertions(+), 856 deletions(-) delete mode 100644 src/services/backend_interactor_service/backend_interactor_service.js delete mode 100644 src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js delete mode 100644 src/services/lists_fetcher/lists_fetcher.service.js diff --git a/src/boot/after_store.js b/src/boot/after_store.js index fe65a7387..2bab043ad 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -19,7 +19,6 @@ import { config.autoAddCss = false import App from '../App.vue' -import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import FaviconService from '../services/favicon_service/favicon_service.js' import { applyStyleConfig } from '../services/style_setter/style_setter.js' import { initServiceWorker, updateFocus } from '../services/sw/sw.js' @@ -31,6 +30,7 @@ import routes from './routes' import { useAnnouncementsStore } from 'src/stores/announcements' import { useAuthFlowStore } from 'src/stores/auth_flow' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useEmojiStore } from 'src/stores/emoji.js' import { useI18nStore } from 'src/stores/i18n' import { useInstanceStore } from 'src/stores/instance.js' @@ -264,10 +264,7 @@ const getStickers = async ({ store }) => { const getAppSecret = async ({ store }) => { const oauth = useOAuthStore() if (oauth.userToken) { - store.commit( - 'setBackendInteractor', - backendInteractorService(oauth.getToken), - ) + useCredentialsStore().setCredentials(oauth.getToken) } } diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index 7428c0dc7..8bcc57d76 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -16,8 +16,15 @@ import { isScrollable, } from './chat_layout_utils.js' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInterfaceStore } from 'src/stores/interface.js' +import { + chatMessages, + getOrCreateChat, + sendChatMessage, +} from 'src/services/api/api.service.js' + import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons' @@ -115,7 +122,6 @@ const Chat = { mobileLayout: (store) => store.layoutType === 'mobile', }), ...mapState({ - backendInteractor: (state) => state.api.backendInteractor, mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus, currentUser: (state) => state.users.currentUser, }), @@ -267,42 +273,46 @@ const Chat = { const fetchOlderMessages = !!maxId const sinceId = fetchLatest && chatMessageService.maxId - return this.backendInteractor - .chatMessages({ id: chatId, maxId, sinceId }) - .then((messages) => { - // Clear the current chat in case we're recovering from a ws connection loss. - if (isFirstFetch) { - chatService.clear(chatMessageService) - } + return chatMessages({ + id: chatId, + maxId, + sinceId, + credentials: useCredentialsStore().current, + }).then((messages) => { + // Clear the current chat in case we're recovering from a ws connection loss. + if (isFirstFetch) { + chatService.clear(chatMessageService) + } - const positionBeforeUpdate = getScrollPosition() - this.$store - .dispatch('addChatMessages', { chatId, messages }) - .then(() => { - this.$nextTick(() => { - if (fetchOlderMessages) { - this.handleScrollUp(positionBeforeUpdate) - } + const positionBeforeUpdate = getScrollPosition() + this.$store + .dispatch('addChatMessages', { chatId, messages }) + .then(() => { + this.$nextTick(() => { + if (fetchOlderMessages) { + this.handleScrollUp(positionBeforeUpdate) + } - // In vertical screens, the first batch of fetched messages may not always take the - // full height of the scrollable container. - // If this is the case, we want to fetch the messages until the scrollable container - // is fully populated so that the user has the ability to scroll up and load the history. - if (!isScrollable() && messages.length > 0) { - this.fetchChat({ - maxId: this.currentChatMessageService.minId, - }) - } - }) + // In vertical screens, the first batch of fetched messages may not always take the + // full height of the scrollable container. + // If this is the case, we want to fetch the messages until the scrollable container + // is fully populated so that the user has the ability to scroll up and load the history. + if (!isScrollable() && messages.length > 0) { + this.fetchChat({ + maxId: this.currentChatMessageService.minId, + }) + } }) - }) + }) + }) }, async startFetching() { let chat = this.findOpenedChatByRecipientId(this.recipientId) if (!chat) { try { - chat = await this.backendInteractor.getOrCreateChat({ + chat = await getOrCreateChat({ accountId: this.recipientId, + credentials: useCredentialsStore().current, }) } catch (e) { console.error('Error creating or getting a chat', e) @@ -369,8 +379,10 @@ const Chat = { doSendMessage({ params, fakeMessage, retriesLeft = MAX_RETRIES }) { if (retriesLeft <= 0) return - this.backendInteractor - .sendChatMessage(params) + sendChatMessage({ + params, + credentials: useCredentialsStore().current, + }) .then((data) => { this.$store.dispatch('addChatMessages', { chatId: this.currentChat.id, diff --git a/src/components/chat_new/chat_new.js b/src/components/chat_new/chat_new.js index 5ee2b610d..2861e23c3 100644 --- a/src/components/chat_new/chat_new.js +++ b/src/components/chat_new/chat_new.js @@ -3,6 +3,10 @@ import { mapGetters, mapState } from 'vuex' import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue' import UserAvatar from 'src/components/user_avatar/user_avatar.vue' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { chats } from 'src/services/api/api.service.js' + import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons' @@ -22,7 +26,9 @@ const chatNew = { } }, async created() { - const { chats } = await this.backendInteractor.chats() + const { chats } = await chats({ + credentials: useCredentialsStore().current, + }) chats.forEach((chat) => this.suggestions.push(chat.account)) }, computed: { @@ -38,7 +44,6 @@ const chatNew = { }, ...mapState({ currentUser: (state) => state.users.currentUser, - backendInteractor: (state) => state.api.backendInteractor, }), ...mapGetters(['findUser']), }, diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index da15c79d6..0e3a3f729 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -7,9 +7,12 @@ import QuickViewSettings from 'src/components/quick_view_settings/quick_view_set import ThreadTree from 'src/components/thread_tree/thread_tree.vue' import { WSConnectionStatus } from '../../services/api/api.service.js' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInterfaceStore } from 'src/stores/interface' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { fetchConversation, fetchStatus } from 'src/services/api/api.service.js' + import { library } from '@fortawesome/fontawesome-svg-core' import { faAngleDoubleDown, @@ -436,17 +439,20 @@ const conversation = { methods: { fetchConversation() { if (this.status) { - this.$store.state.api.backendInteractor - .fetchConversation({ id: this.statusId }) - .then(({ ancestors, descendants }) => { - this.$store.dispatch('addNewStatuses', { statuses: ancestors }) - this.$store.dispatch('addNewStatuses', { statuses: descendants }) - this.setHighlight(this.originalStatusId) - }) + fetchConversation({ + id: this.statusId, + credentials: useCredentialsStore().current, + }).then(({ ancestors, descendants }) => { + this.$store.dispatch('addNewStatuses', { statuses: ancestors }) + this.$store.dispatch('addNewStatuses', { statuses: descendants }) + this.setHighlight(this.originalStatusId) + }) } else { this.loadStatusError = null - this.$store.state.api.backendInteractor - .fetchStatus({ id: this.statusId }) + fetchStatus({ + id: this.statusId, + credentials: useCredentialsStore().current, + }) .then((status) => { this.$store.dispatch('addNewStatuses', { statuses: [status] }) this.fetchConversation() diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js index 44658e985..7e983f60b 100644 --- a/src/components/follow_request_card/follow_request_card.js +++ b/src/components/follow_request_card/follow_request_card.js @@ -3,8 +3,11 @@ import { defineAsyncComponent } from 'vue' import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js' import BasicUserCard from '../basic_user_card/basic_user_card.vue' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { approveUser, denyUser } from 'src/services/api/api.service.js' + const FollowRequestCard = { props: ['user'], components: { @@ -48,7 +51,10 @@ const FollowRequestCard = { } }, doApprove() { - this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) + approveUser({ + id: this.user.id, + credentials: useCredentialsStore().current, + }) this.$store.dispatch('removeFollowRequest', this.user) const notifId = this.findFollowRequestNotificationId() @@ -70,12 +76,14 @@ const FollowRequestCard = { }, doDeny() { const notifId = this.findFollowRequestNotificationId() - this.$store.state.api.backendInteractor - .denyUser({ id: this.user.id }) - .then(() => { - this.$store.dispatch('dismissNotificationLocal', { id: notifId }) - this.$store.dispatch('removeFollowRequest', this.user) - }) + + denyUser({ + id: this.user.id, + credentials: useCredentialsStore().current, + }).then(() => { + this.$store.dispatch('dismissNotificationLocal', { id: notifId }) + this.$store.dispatch('removeFollowRequest', this.user) + }) this.hideDenyConfirmDialog() }, }, diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 856850920..c651d667e 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -13,10 +13,12 @@ import { highlightStyle, } from '../../services/user_highlighter/user_highlighter.js' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' +import { approveUser, denyUser } from 'src/services/api/api.service.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import { library } from '@fortawesome/fontawesome-svg-core' @@ -142,7 +144,10 @@ const Notification = { } }, doApprove() { - this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) + approveUser({ + id: this.user.id, + credentials: useCredentialsStore().current, + }) this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id, @@ -163,14 +168,15 @@ const Notification = { } }, doDeny() { - this.$store.state.api.backendInteractor - .denyUser({ id: this.user.id }) - .then(() => { - this.$store.dispatch('dismissNotificationLocal', { - id: this.notification.id, - }) - this.$store.dispatch('removeFollowRequest', this.user) + denyUser({ + id: this.user.id, + credentials: useCredentialsStore().current, + }).then(() => { + this.$store.dispatch('dismissNotificationLocal', { + id: this.notification.id, }) + this.$store.dispatch('removeFollowRequest', this.user) + }) this.hideDenyConfirmDialog() }, }, diff --git a/src/components/remote_user_resolver/remote_user_resolver.js b/src/components/remote_user_resolver/remote_user_resolver.js index 430f56c84..3f3b72ba9 100644 --- a/src/components/remote_user_resolver/remote_user_resolver.js +++ b/src/components/remote_user_resolver/remote_user_resolver.js @@ -1,3 +1,7 @@ +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { fetchUser } from 'src/services/api/api.service.js' + const RemoteUserResolver = { data: () => ({ error: false, @@ -7,10 +11,11 @@ const RemoteUserResolver = { }, methods: { redirect() { - const acct = - this.$route.params.username + '@' + this.$route.params.hostname - this.$store.state.api.backendInteractor - .fetchUser({ id: acct }) + const id = this.$route.params.username + '@' + this.$route.params.hostname + fetchUser({ + id, + credentials: useCredentialsStore().current, + }) .then((externalUser) => { if (externalUser.error) { this.error = true diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js index 98c0cb467..f64128c57 100644 --- a/src/components/settings_modal/admin_tabs/emoji_tab.js +++ b/src/components/settings_modal/admin_tabs/emoji_tab.js @@ -11,6 +11,7 @@ import ModifiedIndicator from '../helpers/modified_indicator.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' import StringSetting from '../helpers/string_setting.vue' +import { useAdminSettingsStore } from 'src/stores/admin_settings.js' import { useEmojiStore } from 'src/stores/emoji.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInterfaceStore } from 'src/stores/interface.js' @@ -98,10 +99,10 @@ const EmojiTab = { methods: { reloadEmoji() { - this.$store.state.api.backendInteractor.reloadEmoji() + useAdminSettingsStore().reloadEmoji() }, importFromFS() { - this.$store.state.api.backendInteractor.importEmojiFromFS() + useAdminSettingsStore().importEmojiFromFS() }, emojiAddr(name) { if (this.pack.remote !== undefined) { @@ -113,7 +114,7 @@ const EmojiTab = { }, createEmojiPack() { - this.$store.state.api.backendInteractor + useAdminSettingsStore() .createEmojiPack({ name: this.newPackName }) .then((resp) => resp.json()) .then((resp) => { @@ -130,7 +131,7 @@ const EmojiTab = { }) }, deleteEmojiPack() { - this.$store.state.api.backendInteractor + useAdminSettingsStore() .deleteEmojiPack({ name: this.packName }) .then((resp) => resp.json()) .then((resp) => { @@ -157,7 +158,7 @@ const EmojiTab = { return edited !== def }, savePackMetadata() { - this.$store.state.api.backendInteractor + useAdminSettingsStore() .saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta }) .then((resp) => resp.json()) .then((resp) => { @@ -182,7 +183,7 @@ const EmojiTab = { useEmojiStore() .getAdminPacks( this.remotePackInstance, - this.$store.state.api.backendInteractor.listEmojiPacks, + useAdminSettingsStore().listEmojiPacks, ) .then((allPacks) => { this.knownLocalPacks = allPacks @@ -195,7 +196,7 @@ const EmojiTab = { useEmojiStore() .getAdminPacks( this.remotePackInstance, - this.$store.state.api.backendInteractor.listRemoteEmojiPacks, + useAdminSettingsStore().listRemoteEmojiPacks, ) .then((allPacks) => { let inst = this.remotePackInstance @@ -226,7 +227,7 @@ const EmojiTab = { this.remotePackDownloadAs = this.pack.remote.baseName } - this.$store.state.api.backendInteractor + useAdminSettingsStore() .downloadRemoteEmojiPack({ instance: this.pack.remote.instance, packName: this.pack.remote.baseName, @@ -247,7 +248,7 @@ const EmojiTab = { }) }, downloadRemoteURLPack() { - this.$store.state.api.backendInteractor + useAdminSettingsStore() .downloadRemoteEmojiPackZIP({ url: this.remotePackURL, packName: this.newPackName, @@ -268,7 +269,7 @@ const EmojiTab = { }) }, downloadRemoteFilePack() { - this.$store.state.api.backendInteractor + useAdminSettingsStore() .downloadRemoteEmojiPackZIP({ file: this.remotePackFile[0], packName: this.newPackName, diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.js b/src/components/settings_modal/admin_tabs/frontends_tab.js index 9bec3d763..b2e6c10d0 100644 --- a/src/components/settings_modal/admin_tabs/frontends_tab.js +++ b/src/components/settings_modal/admin_tabs/frontends_tab.js @@ -71,7 +71,7 @@ const FrontendsTab = { const payload = { name, ref } this.working = true - this.$store.state.api.backendInteractor + useAdminSettingsStore() .installFrontend({ payload }) .finally(() => { this.working = false diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue index 7078d2488..2c7b151be 100644 --- a/src/components/settings_modal/helpers/emoji_editing_popover.vue +++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue @@ -153,6 +153,14 @@ import Popover from 'components/popover/popover.vue' import SelectComponent from 'components/select/select.vue' import { defineAsyncComponent } from 'vue' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { + addNewEmojiFile, + deleteEmojiFile, + updateEmojiFile, +} from 'src/services/api/api.service.js' + export default { components: { Popover, @@ -243,14 +251,14 @@ export default { saveEditedEmoji() { if (!this.isEdited) return - this.$store.state.api.backendInteractor - .updateEmojiFile({ - packName: this.packName, - shortcode: this.shortcode, - newShortcode: this.editedShortcode, - newFilename: this.editedFile, - force: false, - }) + updateEmojiFile({ + packName: this.packName, + shortcode: this.shortcode, + newShortcode: this.editedShortcode, + newFilename: this.editedFile, + force: false, + credentials: useCredentialsStore().current, + }) .then((resp) => { if (resp.error !== undefined) { this.$emit('displayError', resp.error) @@ -263,18 +271,18 @@ export default { }, uploadEmoji() { let packName = this.remote === undefined ? this.packName : this.copyToPack - this.$store.state.api.backendInteractor - .addNewEmojiFile({ - packName: packName, - file: - this.remote === undefined - ? this.uploadURL !== '' - ? this.uploadURL - : this.uploadFile[0] - : this.emojiAddr(this.file), - shortcode: this.editedShortcode, - filename: this.editedFile, - }) + addNewEmojiFile({ + packName: packName, + file: + this.remote === undefined + ? this.uploadURL !== '' + ? this.uploadURL + : this.uploadFile[0] + : this.emojiAddr(this.file), + shortcode: this.editedShortcode, + filename: this.editedFile, + credentials: useCredentialsStore().current, + }) .then((resp) => resp.json()) .then((resp) => { if (resp.error !== undefined) { @@ -297,8 +305,11 @@ export default { deleteEmoji() { this.deleteModalVisible = false - this.$store.state.api.backendInteractor - .deleteEmojiFile({ packName: this.packName, shortcode: this.shortcode }) + deleteEmojiFile({ + packName: this.packName, + shortcode: this.shortcode, + credentials: useCredentialsStore().current, + }) .then((resp) => resp.json()) .then((resp) => { if (resp.error !== undefined) { diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js index 68d2b923a..595153e61 100644 --- a/src/components/settings_modal/tabs/appearance_tab.js +++ b/src/components/settings_modal/tabs/appearance_tab.js @@ -10,9 +10,11 @@ import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' import Preview from './old_theme_tab/theme_preview.vue' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { normalizeThemeData, useInterfaceStore } from 'src/stores/interface.js' +import { updateProfileImages } from 'src/services/api/api.service.js' import { newImporter } from 'src/services/export_import/export_import.js' import { adoptStyleSheets, @@ -484,8 +486,10 @@ const AppearanceTab = { } this.backgroundUploading = true - this.$store.state.api.backendInteractor - .updateProfileImages({ background }) + updateProfileImages({ + background, + credentials: useCredentialsStore().current, + }) .then((data) => { this.$store.commit('addNewUsers', [data]) this.$store.commit('setCurrentUser', data) diff --git a/src/components/settings_modal/tabs/composing_tab.js b/src/components/settings_modal/tabs/composing_tab.js index ab8d0101d..cf7aa7e3e 100644 --- a/src/components/settings_modal/tabs/composing_tab.js +++ b/src/components/settings_modal/tabs/composing_tab.js @@ -11,11 +11,13 @@ import IntegerSetting from '../helpers/integer_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' +import { updateProfile } from 'src/services/api/api.service.js' import localeService from 'src/services/locale/locale.service.js' import { cacheKey, clearCache, emojiCacheKey } from 'src/services/sw/sw.js' @@ -164,12 +166,13 @@ const ComposingTab = { ), } - this.$store.state.api.backendInteractor - .updateProfile({ params }) - .then((user) => { - this.$store.commit('addNewUsers', [user]) - this.$store.commit('setCurrentUser', user) - }) + updateProfile({ + params, + credentials: useCredentialsStore().current, + }).then((user) => { + this.$store.commit('addNewUsers', [user]) + this.$store.commit('setCurrentUser', user) + }) }, updateFont(key, value) { useSyncConfigStore().setSimplePrefAndSave({ diff --git a/src/components/settings_modal/tabs/data_import_export_tab.js b/src/components/settings_modal/tabs/data_import_export_tab.js index cc41302c7..630cd85e6 100644 --- a/src/components/settings_modal/tabs/data_import_export_tab.js +++ b/src/components/settings_modal/tabs/data_import_export_tab.js @@ -4,8 +4,20 @@ import Checkbox from 'src/components/checkbox/checkbox.vue' import Exporter from 'src/components/exporter/exporter.vue' import Importer from 'src/components/importer/importer.vue' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useOAuthTokensStore } from 'src/stores/oauth_tokens.js' +import { + addBackup, + exportFriends, + fetchBlocks, + fetchMutes, + importBlocks, + importFollows, + importMutes, + listBackups, +} from 'src/services/api/api.service.js' + const DataImportExportTab = { data() { return { @@ -28,42 +40,51 @@ const DataImportExportTab = { }, computed: { ...mapState({ - backendInteractor: (state) => state.api.backendInteractor, user: (state) => state.users.currentUser, }), }, methods: { getFollowsContent() { - return this.backendInteractor - .exportFriends({ id: this.user.id }) - .then(this.generateExportableUsersContent) + return exportFriends({ + id: this.user.id, + credentials: useCredentialsStore().current, + }).then(this.generateExportableUsersContent) }, getBlocksContent() { - return this.backendInteractor - .fetchBlocks() - .then(this.generateExportableUsersContent) + return fetchBlocks({ + credentials: useCredentialsStore().current, + }).then(this.generateExportableUsersContent) }, getMutesContent() { - return this.backendInteractor - .fetchMutes() - .then(this.generateExportableUsersContent) + return fetchMutes({ + credentials: useCredentialsStore().current, + }).then(this.generateExportableUsersContent) }, importFollows(file) { - return this.backendInteractor.importFollows({ file }).then((status) => { + return importFollows({ + file, + credentials: useCredentialsStore().current, + }).then((status) => { if (!status) { throw new Error('failed') } }) }, importBlocks(file) { - return this.backendInteractor.importBlocks({ file }).then((status) => { + return importBlocks({ + file, + credentials: useCredentialsStore().current, + }).then((status) => { if (!status) { throw new Error('failed') } }) }, importMutes(file) { - return this.backendInteractor.importMutes({ file }).then((status) => { + return importMutes({ + file, + credentials: useCredentialsStore().current, + }).then((status) => { if (!status) { throw new Error('failed') } @@ -83,8 +104,9 @@ const DataImportExportTab = { .join('\n') }, addBackup() { - this.$store.state.api.backendInteractor - .addBackup() + addBackup({ + credentials: useCredentialsStore().current, + }) .then(() => { this.addedBackup = true this.addBackupError = false @@ -96,8 +118,9 @@ const DataImportExportTab = { .then(() => this.fetchBackups()) }, fetchBackups() { - this.$store.state.api.backendInteractor - .listBackups() + listBackups({ + credentials: useCredentialsStore().current, + }) .then((res) => { this.backups = res this.listBackupsError = false diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index db1177cb2..207fda663 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -8,12 +8,14 @@ import FloatSetting from '../helpers/float_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useLocalConfigStore } from 'src/stores/local_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' +import { updateProfile } from 'src/services/api/api.service.js' import localeService from 'src/services/locale/locale.service.js' const GeneralTab = { @@ -58,12 +60,13 @@ const GeneralTab = { ), } - this.$store.state.api.backendInteractor - .updateProfile({ params }) - .then((user) => { - this.$store.commit('addNewUsers', [user]) - this.$store.commit('setCurrentUser', user) - }) + updateProfile({ + params, + credentials: useCredentialsStore().current, + }).then((user) => { + this.$store.commit('addNewUsers', [user]) + this.$store.commit('setCurrentUser', user) + }) }, updateFont(path, value) { useLocalConfigStore().set({ path, value }) diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js index 0d889ce54..43af4a048 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js @@ -9,9 +9,12 @@ import MuteCard from 'src/components/mute_card/mute_card.vue' import ProgressButton from 'src/components/progress_button/progress_button.vue' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthTokensStore } from 'src/stores/oauth_tokens.js' +import { importBlocks, importFollows } from 'src/services/api/api.service.js' + const MutesAndBlocks = { data() { return { @@ -54,22 +57,24 @@ const MutesAndBlocks = { return () => this.$store.dispatch('fetch' + group, this.userId) }, importFollows(file) { - return this.$store.state.api.backendInteractor - .importFollows({ file }) - .then((status) => { - if (!status) { - throw new Error('failed') - } - }) + return importFollows({ + file, + credentials: useCredentialsStore().current, + }).then((status) => { + if (!status) { + throw new Error('failed') + } + }) }, importBlocks(file) { - return this.$store.state.api.backendInteractor - .importBlocks({ file }) - .then((status) => { - if (!status) { - throw new Error('failed') - } - }) + return importBlocks({ + file, + credentials: useCredentialsStore().current, + }).then((status) => { + if (!status) { + throw new Error('failed') + } + }) }, generateExportableUsersContent(users) { // Get addresses diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js index 5114012a8..f3328a20a 100644 --- a/src/components/settings_modal/tabs/notifications_tab.js +++ b/src/components/settings_modal/tabs/notifications_tab.js @@ -1,6 +1,10 @@ import BooleanSetting from '../helpers/boolean_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { updateNotificationSettings } from 'src/services/api/api.service.js' + const NotificationsTab = { data() { return { @@ -27,7 +31,8 @@ const NotificationsTab = { }, methods: { updateNotificationSettings() { - this.$store.state.api.backendInteractor.updateNotificationSettings({ + updateNotificationSettings({ + credentials: useCredentialsStore().current, settings: this.notificationSettings, }) }, diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index 3e12f265f..afdeffb34 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -3,6 +3,10 @@ import UserCard from 'src/components/user_card/user_card.vue' import BooleanSetting from '../helpers/boolean_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { updateProfile } from 'src/services/api/api.service.js' + import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch, @@ -35,9 +39,10 @@ const ProfileTab = { const params = { locked: this.locked, } - - this.$store.state.api.backendInteractor - .updateProfile({ params }) + updateProfile({ + params, + credentials: useCredentialsStore().current, + }) .then((user) => { this.$store.commit('addNewUsers', [user]) this.$store.commit('setCurrentUser', user) diff --git a/src/components/settings_modal/tabs/security_tab/mfa.js b/src/components/settings_modal/tabs/security_tab/mfa.js index a998fdb4b..f8d0d8e3b 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa.js +++ b/src/components/settings_modal/tabs/security_tab/mfa.js @@ -5,6 +5,15 @@ import Confirm from './confirm.vue' import RecoveryCodes from './mfa_backup_codes.vue' import TOTP from './mfa_totp.vue' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { + generateMfaBackupCodes, + mfaConfirmOTP, + mfaSetupOTP, + settingsMFA, +} from 'src/services/api/api.service.js' + const Mfa = { data: () => ({ settings: { @@ -71,9 +80,6 @@ const Mfa = { confirmNewBackupCodes() { return this.backupCodes.getNewCodes }, - ...mapState({ - backendInteractor: (state) => state.api.backendInteractor, - }), }, methods: { @@ -87,7 +93,9 @@ const Mfa = { this.backupCodes.inProgress = true this.backupCodes.codes = [] - return this.backendInteractor.generateMfaBackupCodes().then((res) => { + return generateMfaBackupCodes({ + credentials: useCredentialsStore().current, + }).then((res) => { this.backupCodes.codes = res.codes this.backupCodes.inProgress = false }) @@ -112,7 +120,9 @@ const Mfa = { // prepare setup OTP this.setupState.state = 'setupOTP' this.setupState.setupOTPState = 'prepare' - this.backendInteractor.mfaSetupOTP().then((res) => { + mfaSetupOTP({ + credentials: useCredentialsStore().current, + }).then((res) => { this.otpSettings = res this.setupState.setupOTPState = 'confirm' }) @@ -120,18 +130,17 @@ const Mfa = { doConfirmOTP() { // handler confirm enable OTP this.error = null - this.backendInteractor - .mfaConfirmOTP({ - token: this.otpConfirmToken, - password: this.currentPassword, - }) - .then((res) => { - if (res.error) { - this.error = res.error - return - } - this.completeSetup() - }) + mfaConfirmOTP({ + token: this.otpConfirmToken, + password: this.currentPassword, + credentials: useCredentialsStore().current, + }).then((res) => { + if (res.error) { + this.error = res.error + return + } + this.completeSetup() + }) }, completeSetup() { @@ -152,7 +161,9 @@ const Mfa = { // fetch settings from server async fetchSettings() { - const result = await this.backendInteractor.settingsMFA() + const result = await settingsMFA({ + credentials: useCredentialsStore().current, + }) if (result.error) return this.settings = result.settings this.settings.available = true diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.js b/src/components/settings_modal/tabs/security_tab/mfa_totp.js index e011fd638..c03168d5b 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa_totp.js +++ b/src/components/settings_modal/tabs/security_tab/mfa_totp.js @@ -2,6 +2,10 @@ import { mapState } from 'vuex' import Confirm from './confirm.vue' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { mfaDisableOTP } from 'src/services/api/api.service.js' + export default { props: ['settings'], data: () => ({ @@ -17,9 +21,6 @@ export default { isActivated() { return this.settings.totp }, - ...mapState({ - backendInteractor: (state) => state.api.backendInteractor, - }), }, methods: { doActivate() { @@ -36,19 +37,18 @@ export default { // confirm deactivate TOTP method this.error = null this.inProgress = true - this.backendInteractor - .mfaDisableOTP({ - password: this.currentPassword, - }) - .then((res) => { - this.inProgress = false - if (res.error) { - this.error = res.error - return - } - this.deactivate = false - this.$emit('deactivate') - }) + mfaDisableOTP({ + password: this.currentPassword, + credentials: useCredentialsStore().current, + }).then((res) => { + this.inProgress = false + if (res.error) { + this.error = res.error + return + } + this.deactivate = false + this.$emit('deactivate') + }) }, }, } diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js index 96510edcf..272cdecff 100644 --- a/src/components/settings_modal/tabs/security_tab/security_tab.js +++ b/src/components/settings_modal/tabs/security_tab/security_tab.js @@ -2,10 +2,20 @@ import Checkbox from 'src/components/checkbox/checkbox.vue' import ProgressButton from 'src/components/progress_button/progress_button.vue' import Mfa from './mfa.vue' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useOAuthTokensStore } from 'src/stores/oauth_tokens' +import { + addAlias, + changeEmail, + changePassword, + deleteAccount, + deleteAlias, + listAliases, + moveAccount, +} from 'src/services/api/api.service.js' import localeService from 'src/services/locale/locale.service.js' const SecurityTab = { @@ -65,78 +75,79 @@ const SecurityTab = { this.deletingAccount = true }, deleteAccount() { - this.$store.state.api.backendInteractor - .deleteAccount({ password: this.deleteAccountConfirmPasswordInput }) - .then((res) => { - if (res.status === 'success') { - this.$store.dispatch('logout') - this.$router.push({ name: 'root' }) - } else { - this.deleteAccountError = res.error - } - }) + deleteAccount({ + credentials: useCredentialsStore().current, + password: this.deleteAccountConfirmPasswordInput, + }).then((res) => { + if (res.status === 'success') { + this.$store.dispatch('logout') + this.$router.push({ name: 'root' }) + } else { + this.deleteAccountError = res.error + } + }) }, changePassword() { const params = { password: this.changePasswordInputs[0], newPassword: this.changePasswordInputs[1], newPasswordConfirmation: this.changePasswordInputs[2], + credentials: useCredentialsStore().current, } - this.$store.state.api.backendInteractor - .changePassword(params) - .then((res) => { - if (res.status === 'success') { - this.changedPassword = true - this.changePasswordError = false - this.logout() - } else { - this.changedPassword = false - this.changePasswordError = res.error - } - }) + changePassword(params).then((res) => { + if (res.status === 'success') { + this.changedPassword = true + this.changePasswordError = false + this.logout() + } else { + this.changedPassword = false + this.changePasswordError = res.error + } + }) }, changeEmail() { const params = { email: this.newEmail, password: this.changeEmailPassword, + credentials: useCredentialsStore().current, } - this.$store.state.api.backendInteractor - .changeEmail(params) - .then((res) => { - if (res.status === 'success') { - this.changedEmail = true - this.changeEmailError = false - } else { - this.changedEmail = false - this.changeEmailError = res.error - } - }) + changeEmail(params).then((res) => { + if (res.status === 'success') { + this.changedEmail = true + this.changeEmailError = false + } else { + this.changedEmail = false + this.changeEmailError = res.error + } + }) }, moveAccount() { const params = { targetAccount: this.moveAccountTarget, password: this.moveAccountPassword, + credentials: useCredentialsStore().current, } - this.$store.state.api.backendInteractor - .moveAccount(params) - .then((res) => { - if (res.status === 'success') { - this.movedAccount = true - this.moveAccountError = false - } else { - this.movedAccount = false - this.moveAccountError = res.error - } - }) + moveAccount(params).then((res) => { + if (res.status === 'success') { + this.movedAccount = true + this.moveAccountError = false + } else { + this.movedAccount = false + this.moveAccountError = res.error + } + }) }, removeAlias(alias) { - this.$store.state.api.backendInteractor - .deleteAlias({ alias }) - .then(() => this.fetchAliases()) + deleteAlias({ + alias, + credentials: useCredentialsStore().current, + }).then(() => this.fetchAliases()) }, addAlias() { - this.$store.state.api.backendInteractor - .addAlias({ alias: this.addAliasTarget }) + addAlias({ + alias: this.addAliasTarget, + credentials: useCredentialsStore().current, + }) .then(() => { this.addedAlias = true this.addAliasError = false @@ -149,8 +160,9 @@ const SecurityTab = { .then(() => this.fetchAliases()) }, fetchAliases() { - this.$store.state.api.backendInteractor - .listAliases() + listAliases({ + credentials: useCredentialsStore().current, + }) .then((res) => { this.aliases = res.aliases this.listAliasesError = false diff --git a/src/components/still-image/still-image-emoji-popover.js b/src/components/still-image/still-image-emoji-popover.js index 02f036a52..92cc20904 100644 --- a/src/components/still-image/still-image-emoji-popover.js +++ b/src/components/still-image/still-image-emoji-popover.js @@ -2,6 +2,7 @@ import Popover from 'components/popover/popover.vue' import SelectComponent from 'components/select/select.vue' import { mapState } from 'pinia' +import { useAdminSettingsStore } from 'src/stores/admin_settings' import { useEmojiStore } from 'src/stores/emoji' import { useInterfaceStore } from 'src/stores/interface' @@ -37,7 +38,7 @@ export default { }) }, copyToLocalPack() { - this.$store.state.api.backendInteractor + useAdminSettingsStore() .addNewEmojiFile({ packName: this.packName, file: this.$attrs.src, diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index acb2c4ea1..b026c2347 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -16,6 +16,7 @@ import Select from 'src/components/select/select.vue' import UserAvatar from 'src/components/user_avatar/user_avatar.vue' import UserLink from 'src/components/user_link/user_link.vue' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useEmojiStore } from 'src/stores/emoji.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' @@ -25,6 +26,7 @@ import { useMergedConfigStore } from 'src/stores/merged_config.js' import { usePostStatusStore } from 'src/stores/post_status' import { useUserHighlightStore } from 'src/stores/user_highlight.js' +import { updateProfile } from 'src/services/api/api.service.js' import { propsToNative } from 'src/services/attributes_helper/attributes_helper.service.js' import localeService from 'src/services/locale/locale.service.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -597,8 +599,7 @@ export default { params.header = this.newBannerFile } - this.$store.state.api.backendInteractor - .updateProfile({ params }) + updateProfile({ params }) .then((user) => { this.newFields.splice(this.newFields.length) merge(this.newFields, user.fields) diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js index 9aed47399..d5d91e349 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.js +++ b/src/components/user_reporting_modal/user_reporting_modal.js @@ -5,8 +5,11 @@ import List from 'src/components/list/list.vue' import Modal from 'src/components/modal/modal.vue' import UserLink from 'src/components/user_link/user_link.vue' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useReportsStore } from 'src/stores/reports.js' +import { reportUser } from 'src/services/api/api.service.js' + const UserReportingModal = { components: { List, @@ -71,9 +74,9 @@ const UserReportingModal = { comment: this.comment, forward: this.forward, statusIds: [...this.statusIdsToReport], + credentials: useCredentialsStore().current, } - this.$store.state.api.backendInteractor - .reportUser({ ...params }) + reportUser({ ...params }) .then(() => { this.processing = false this.resetState() diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js index 8ca2d8c1a..a6edbf508 100644 --- a/src/components/who_to_follow/who_to_follow.js +++ b/src/components/who_to_follow/who_to_follow.js @@ -1,8 +1,11 @@ import FollowCard from 'src/components/follow_card/follow_card.vue' import apiService from '../../services/api/api.service.js' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' +import { fetchUser, suggestions } from 'src/services/api/api.service.js' + const WhoToFollow = { components: { FollowCard, @@ -17,21 +20,22 @@ const WhoToFollow = { }, methods: { showWhoToFollow(reply) { - reply.forEach((i) => { - this.$store.state.api.backendInteractor - .fetchUser({ id: i.acct }) - .then((externalUser) => { - if (!externalUser.error) { - this.$store.commit('addNewUsers', [externalUser]) - this.users.push(externalUser) - } - }) + reply.forEach(({ id }) => { + fetchUser({ + id, + credentials: useCredentialsStore().current, + }).then((externalUser) => { + if (!externalUser.error) { + this.$store.commit('addNewUsers', [externalUser]) + this.users.push(externalUser) + } + }) }) }, getWhoToFollow() { - const credentials = this.$store.state.users.currentUser.credentials + const credentials = useCredentialsStore().current if (credentials) { - apiService.suggestions({ credentials }).then((reply) => { + suggestions({ credentials }).then((reply) => { this.showWhoToFollow(reply) }) } diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js index cd0524ecf..c6de34af3 100644 --- a/src/components/who_to_follow_panel/who_to_follow_panel.js +++ b/src/components/who_to_follow_panel/who_to_follow_panel.js @@ -1,10 +1,10 @@ import { shuffle } from 'lodash' -import apiService from '../../services/api/api.service.js' - +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' +import { fetchUser, suggestions } from 'src/services/api/api.service.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' function showWhoToFollow(panel, reply) { @@ -18,14 +18,15 @@ function showWhoToFollow(panel, reply) { toFollow.img = img toFollow.name = name - panel.$store.state.api.backendInteractor - .fetchUser({ id: name }) - .then((externalUser) => { - if (!externalUser.error) { - panel.$store.commit('addNewUsers', [externalUser]) - toFollow.id = externalUser.id - } - }) + fetchUser({ + id: name, + credentials: useCredentialsStore().current, + }).then((externalUser) => { + if (!externalUser.error) { + panel.$store.commit('addNewUsers', [externalUser]) + toFollow.id = externalUser.id + } + }) }) } @@ -35,7 +36,7 @@ function getWhoToFollow(panel) { panel.usersToFollow.forEach((toFollow) => { toFollow.name = 'Loading...' }) - apiService.suggestions({ credentials }).then((reply) => { + suggestions({ credentials }).then((reply) => { showWhoToFollow(panel, reply) }) } diff --git a/src/modules/api.js b/src/modules/api.js index 6f4b8b15f..bc0844f61 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -1,20 +1,28 @@ import { Socket } from 'phoenix' import { WSConnectionStatus } from '../services/api/api.service.js' -import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useShoutStore } from 'src/stores/shout.js' +import { + fetchTimeline, + getMastodonSocketURI, + ProcessedWS, +} from 'src/services/api/api.service.js' +import followRequestFetcher from 'src/services/follow_request_fetcher/follow_request_fetcher.service' +import notificationsFetcher from 'src/services/notifications_fetcher/notifications_fetcher.service.js' +import timelineFetcher from 'src/services/timeline_fetcher/timeline_fetcher.service.js' + const retryTimeout = (multiplier) => 1000 * multiplier const api = { state: { retryMultiplier: 1, - backendInteractor: backendInteractorService(), fetchers: {}, socket: null, mastoUserSocket: null, @@ -25,9 +33,6 @@ const api = { followRequestCount: (state) => state.followRequests.length, }, mutations: { - setBackendInteractor(state, backendInteractor) { - state.backendInteractor = backendInteractor - }, addFetcher(state, { fetcherName, fetcher }) { state.fetchers[fetcherName] = fetcher }, @@ -91,9 +96,17 @@ const api = { try { const { state, commit, dispatch, rootState } = store const timelineData = rootState.statuses.timelines.friends - state.mastoUserSocket = state.backendInteractor.startUserSocket({ - store, + + const serv = useInstanceStore().server.replace('http', 'ws') + const credentials = useCredentialsStore().current + const url = getMastodonSocketURI({ credentials }, serv) + + state.mastoUserSocket = ProcessedWS({ + url, + id: 'Unified', + credentials, }) + state.mastoUserSocket.addEventListener( 'pleroma:authenticated', () => { @@ -245,7 +258,7 @@ const api = { return if (store.state.fetchers[timeline]) return - const fetcher = store.state.backendInteractor.startFetchingTimeline({ + const fetcher = timelineFetcher.startFetching({ timeline, store, userId, @@ -253,7 +266,9 @@ const api = { statusId, bookmarkFolderId, tag, + credentials: useCredentialsStore().current, }) + store.commit('addFetcher', { fetcherName: timeline, fetcher }) }, stopFetchingTimeline(store, timeline) { @@ -261,19 +276,22 @@ const api = { if (!fetcher) return store.commit('removeFetcher', { fetcherName: timeline, fetcher }) }, + fetchTimeline(store, { timeline, ...rest }) { - store.state.backendInteractor.fetchTimeline({ + fetchTimeline({ store, timeline, ...rest, + credentials: useCredentialsStore().current, }) }, // Notifications startFetchingNotifications(store) { if (store.state.fetchers.notifications) return - const fetcher = store.state.backendInteractor.startFetchingNotifications({ + const fetcher = notificationsFetcher.startFetching({ store, + credentials: useCredentialsStore().current, }) store.commit('addFetcher', { fetcherName: 'notifications', fetcher }) }, @@ -282,19 +300,14 @@ const api = { if (!fetcher) return store.commit('removeFetcher', { fetcherName: 'notifications', fetcher }) }, - fetchNotifications(store, { ...rest }) { - store.state.backendInteractor.fetchNotifications({ - store, - ...rest, - }) - }, // Follow requests startFetchingFollowRequests(store) { if (store.state.fetchers.followRequests) return - const fetcher = store.state.backendInteractor.startFetchingFollowRequests( - { store }, - ) + const fetcher = followRequestFetcher.startFetchingFollowRequests({ + store, + credentials: useCredentialsStore().current, + }) store.commit('addFetcher', { fetcherName: 'followRequests', fetcher }) }, @@ -303,39 +316,6 @@ const api = { if (!fetcher) return store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher }) }, - removeFollowRequest(store, request) { - const requests = store.state.followRequests.filter((it) => it !== request) - store.commit('setFollowRequests', requests) - }, - - // Lists - startFetchingLists(store) { - if (store.state.fetchers.lists) return - const fetcher = store.state.backendInteractor.startFetchingLists({ - store, - }) - store.commit('addFetcher', { fetcherName: 'lists', fetcher }) - }, - stopFetchingLists(store) { - const fetcher = store.state.fetchers.lists - if (!fetcher) return - store.commit('removeFetcher', { fetcherName: 'lists', fetcher }) - }, - - // Bookmark folders - startFetchingBookmarkFolders(store) { - if (store.state.fetchers.bookmarkFolders) return - if (!useInstanceCapabilitiesStore().pleromaBookmarkFoldersAvailable) - return - const fetcher = - store.state.backendInteractor.startFetchingBookmarkFolders({ store }) - store.commit('addFetcher', { fetcherName: 'bookmarkFolders', fetcher }) - }, - stopFetchingBookmarkFolders(store) { - const fetcher = store.state.fetchers.bookmarkFolders - if (!fetcher) return - store.commit('removeFetcher', { fetcherName: 'bookmarkFolders', fetcher }) - }, // Pleroma websocket setWsToken(store, token) { diff --git a/src/modules/chats.js b/src/modules/chats.js index 3c0ed7d3e..4d56782a0 100644 --- a/src/modules/chats.js +++ b/src/modules/chats.js @@ -11,7 +11,11 @@ import { promiseInterval } from '../services/promise_interval/promise_interval.j import { useCredentialsStore } from 'src/stores/credentials.js' -import { chats } from 'src/services/api/api.service.js' +import { + chats, + deleteChatMessage, + readChat, +} from 'src/services/api/api.service.js' const emptyChatList = () => ({ data: [], @@ -119,11 +123,18 @@ const chatsModule = { commit('readChat', { id, lastReadId }) if (isNewMessage) { - rootState.api.backendInteractor.readChat({ id, lastReadId }) + readChat({ + id, + lastReadId, + credentials: useCredentialsStore().current, + }) } }, deleteChatMessage({ rootState, commit }, value) { - rootState.api.backendInteractor.deleteChatMessage(value) + deleteChatMessage({ + ...value, + credentials: useCredentialsStore().current, + }) commit('deleteChatMessage', { commit, ...value }) }, resetChats({ commit, dispatch }) { diff --git a/src/modules/notifications.js b/src/modules/notifications.js index 81c8b7fec..2e55477b7 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -9,11 +9,14 @@ import { maybeShowNotification, } from '../services/notification_utils/notification_utils.js' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useI18nStore } from 'src/stores/i18n.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useReportsStore } from 'src/stores/reports.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' +import { dismissNotification } from 'src/services/api/api.service.js' + const emptyNotifications = () => ({ desktopNotificationSilence: true, maxId: 0, @@ -176,7 +179,10 @@ export const notifications = { }, dismissNotification({ rootState, commit }, { id }) { commit('dismissNotification', { id }) - rootState.api.backendInteractor.dismissNotification({ id }) + dismissNotification({ + id, + credentials: useCredentialsStore().current, + }) }, updateNotification({ commit }, { id, updater }) { commit('updateNotification', { id, updater }) diff --git a/src/modules/profileConfig.js b/src/modules/profileConfig.js index 90571e21d..c6374e142 100644 --- a/src/modules/profileConfig.js +++ b/src/modules/profileConfig.js @@ -1,28 +1,37 @@ import { get, set } from 'lodash' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { + updateNotificationSettings, + updateProfile, +} from 'src/services/api/api.service.js' + const defaultApi = ({ rootState, commit }, { path, value }) => { const params = {} set(params, path, value) - return rootState.api.backendInteractor - .updateProfile({ params }) - .then((result) => { - commit('addNewUsers', [result]) - commit('setCurrentUser', result) - }) + return updateProfile({ + params, + credentials: useCredentialsStore().current, + }).then((result) => { + commit('addNewUsers', [result]) + commit('setCurrentUser', result) + }) } const notificationsApi = ({ rootState, commit }, { path, value, oldValue }) => { const settings = {} set(settings, path, value) - return rootState.api.backendInteractor - .updateNotificationSettings({ settings }) - .then((result) => { - if (result.status === 'success') { - commit('confirmProfileOption', { name, value }) - } else { - commit('confirmProfileOption', { name, value: oldValue }) - } - }) + return updateNotificationSettings({ + settings, + credentials: useCredentialsStore().current, + }).then((result) => { + if (result.status === 'success') { + commit('confirmProfileOption', { name, value }) + } else { + commit('confirmProfileOption', { name, value: oldValue }) + } + }) } /** diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 6c8caafdd..ff81a12ba 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -14,10 +14,28 @@ import { } from 'lodash' import { + bookmarkStatus, deleteStatus, + favorite, + fetchEmojiReactions, + fetchFavoritedByUsers, + fetchPinnedStatuses, + fetchRebloggedByUsers, fetchScrobbles, + fetchStatus, fetchStatusHistory, fetchStatusSource, + muteConversation, + pinOwnStatus, + reactWithEmoji, + retweet, + search2, + unbookmarkStatus, + unfavorite, + unmuteConversation, + unpinOwnStatus, + unreactWithEmoji, + unretweet, } from '../services/api/api.service.js' import { useCredentialsStore } from 'src/stores/credentials.js' @@ -607,9 +625,9 @@ const statuses = { }) }, fetchStatus({ rootState, dispatch }, id) { - return rootState.api.backendInteractor - .fetchStatus({ id }) - .then((status) => dispatch('addNewStatuses', { statuses: [status] })) + return fetchStatus({ id }).then((status) => + dispatch('addNewStatuses', { statuses: [status] }), + ) }, fetchStatusSource({ rootState }, status) { return fetchStatusSource({ @@ -647,99 +665,111 @@ const statuses = { favorite({ rootState, commit }, status) { // Optimistic favoriting... commit('setFavorited', { status, value: true }) - rootState.api.backendInteractor - .favorite({ id: status.id }) - .then((status) => - commit('setFavoritedConfirm', { - status, - user: rootState.users.currentUser, - }), - ) + favorite({ + id: status.id, + credentials: useCredentialsStore().current, + }).then((status) => + commit('setFavoritedConfirm', { + status, + user: rootState.users.currentUser, + }), + ) }, unfavorite({ rootState, commit }, status) { // Optimistic unfavoriting... commit('setFavorited', { status, value: false }) - rootState.api.backendInteractor - .unfavorite({ id: status.id }) - .then((status) => - commit('setFavoritedConfirm', { - status, - user: rootState.users.currentUser, - }), - ) + unfavorite({ + id: status.id, + credentials: useCredentialsStore().current, + }).then((status) => + commit('setFavoritedConfirm', { + status, + user: rootState.users.currentUser, + }), + ) }, fetchPinnedStatuses({ rootState, dispatch }, userId) { - rootState.api.backendInteractor - .fetchPinnedStatuses({ id: userId }) - .then((statuses) => - dispatch('addNewStatuses', { - statuses, - timeline: 'user', - userId, - showImmediately: true, - noIdUpdate: true, - }), - ) + fetchPinnedStatuses({ + id: userId, + credentials: useCredentialsStore().current, + }).then((statuses) => + dispatch('addNewStatuses', { + statuses, + timeline: 'user', + userId, + showImmediately: true, + noIdUpdate: true, + }), + ) }, pinStatus({ rootState, dispatch }, statusId) { - return rootState.api.backendInteractor - .pinOwnStatus({ id: statusId }) - .then((status) => dispatch('addNewStatuses', { statuses: [status] })) + return pinOwnStatus({ + id: statusId, + credentials: useCredentialsStore().current, + }).then((status) => dispatch('addNewStatuses', { statuses: [status] })) }, unpinStatus({ rootState, dispatch }, statusId) { - return rootState.api.backendInteractor - .unpinOwnStatus({ id: statusId }) - .then((status) => dispatch('addNewStatuses', { statuses: [status] })) + return unpinOwnStatus({ + id: statusId, + credentials: useCredentialsStore().current, + }).then((status) => dispatch('addNewStatuses', { statuses: [status] })) }, muteConversation({ rootState, commit }, { id: statusId }) { - return rootState.api.backendInteractor - .muteConversation({ id: statusId }) - .then((status) => commit('setMutedStatus', status)) + return muteConversation({ + id: statusId, + credentials: useCredentialsStore().current, + }).then((status) => commit('setMutedStatus', status)) }, unmuteConversation({ rootState, commit }, { id: statusId }) { - return rootState.api.backendInteractor - .unmuteConversation({ id: statusId }) - .then((status) => commit('setMutedStatus', status)) + return unmuteConversation({ + id: statusId, + credentials: useCredentialsStore().current, + }).then((status) => commit('setMutedStatus', status)) }, retweet({ rootState, commit }, status) { // Optimistic retweeting... commit('setRetweeted', { status, value: true }) - rootState.api.backendInteractor - .retweet({ id: status.id }) - .then((status) => - commit('setRetweetedConfirm', { - status: status.retweeted_status, - user: rootState.users.currentUser, - }), - ) + retweet({ + id: status.id, + credentials: useCredentialsStore().current, + }).then((status) => + commit('setRetweetedConfirm', { + status: status.retweeted_status, + user: rootState.users.currentUser, + }), + ) }, unretweet({ rootState, commit }, status) { // Optimistic unretweeting... commit('setRetweeted', { status, value: false }) - rootState.api.backendInteractor - .unretweet({ id: status.id }) - .then((status) => - commit('setRetweetedConfirm', { - status, - user: rootState.users.currentUser, - }), - ) + unretweet({ + id: status.id, + credentials: useCredentialsStore().current, + }).then((status) => + commit('setRetweetedConfirm', { + status, + user: rootState.users.currentUser, + }), + ) }, bookmark({ rootState, commit }, status) { commit('setBookmarked', { status, value: true }) - rootState.api.backendInteractor - .bookmarkStatus({ id: status.id, folder_id: status.bookmark_folder_id }) - .then((status) => { - commit('setBookmarkedConfirm', { status }) - }) + bookmarkStatus({ + id: status.id, + folder_id: status.bookmark_folder_id, + credentials: useCredentialsStore().current, + }).then((status) => { + commit('setBookmarkedConfirm', { status }) + }) }, unbookmark({ rootState, commit }, status) { commit('setBookmarked', { status, value: false }) - rootState.api.backendInteractor - .unbookmarkStatus({ id: status.id }) - .then((status) => { - commit('setBookmarkedConfirm', { status }) - }) + unbookmarkStatus({ + id: status.id, + credentials: useCredentialsStore().current, + }).then((status) => { + commit('setBookmarkedConfirm', { status }) + }) }, queueFlush({ commit }, { timeline, id }) { commit('queueFlush', { timeline, id }) @@ -749,8 +779,14 @@ const statuses = { }, fetchFavsAndRepeats({ rootState, commit }, id) { Promise.all([ - rootState.api.backendInteractor.fetchFavoritedByUsers({ id }), - rootState.api.backendInteractor.fetchRebloggedByUsers({ id }), + fetchFavoritedByUsers({ + id, + credentials: useCredentialsStore().current, + }), + fetchRebloggedByUsers({ + id, + credentials: useCredentialsStore().current, + }), ]).then(([favoritedByUsers, rebloggedByUsers]) => { commit('addFavs', { id, @@ -769,7 +805,11 @@ const statuses = { if (!currentUser) return commit('addOwnReaction', { id, emoji, currentUser }) - rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then(() => { + reactWithEmoji({ + id, + emoji, + credentials: useCredentialsStore().current, + }).then(() => { dispatch('fetchEmojiReactionsBy', id) }) }, @@ -778,59 +818,70 @@ const statuses = { if (!currentUser) return commit('removeOwnReaction', { id, emoji, currentUser }) - rootState.api.backendInteractor - .unreactWithEmoji({ id, emoji }) - .then(() => { - dispatch('fetchEmojiReactionsBy', id) - }) + unreactWithEmoji({ + id, + emoji, + currentUser: rootState.users.currentUser, + }).then(() => { + dispatch('fetchEmojiReactionsBy', id) + }) }, fetchEmojiReactionsBy({ rootState, commit }, id) { - return rootState.api.backendInteractor - .fetchEmojiReactions({ id }) - .then((emojiReactions) => { - commit('addEmojiReactionsBy', { - id, - emojiReactions, - currentUser: rootState.users.currentUser, - }) + return fetchEmojiReactions({ + id, + credentials: useCredentialsStore().current, + }).then((emojiReactions) => { + commit('addEmojiReactionsBy', { + id, + emojiReactions, + currentUser: rootState.users.currentUser, }) + }) }, fetchFavs({ rootState, commit }, id) { - rootState.api.backendInteractor - .fetchFavoritedByUsers({ id }) - .then((favoritedByUsers) => - commit('addFavs', { - id, - favoritedByUsers, - currentUser: rootState.users.currentUser, - }), - ) + fetchFavoritedByUsers({ + id, + credentials: useCredentialsStore().current, + }).then((favoritedByUsers) => + commit('addFavs', { + id, + favoritedByUsers, + currentUser: rootState.users.currentUser, + }), + ) }, fetchRepeats({ rootState, commit }, id) { - rootState.api.backendInteractor - .fetchRebloggedByUsers({ id }) - .then((rebloggedByUsers) => - commit('addRepeats', { - id, - rebloggedByUsers, - currentUser: rootState.users.currentUser, - }), - ) + fetchRebloggedByUsers({ + id, + credentials: useCredentialsStore().current, + }).then((rebloggedByUsers) => + commit('addRepeats', { + id, + rebloggedByUsers, + currentUser: rootState.users.currentUser, + }), + ) }, search(store, { q, resolve, limit, offset, following, type }) { - return store.rootState.api.backendInteractor - .search2({ q, resolve, limit, offset, following, type }) - .then((data) => { - store.commit('addNewUsers', data.accounts) - store.commit( - 'addNewUsers', - data.statuses.map((s) => s.user).filter((u) => u), - ) - data.statuses = store.commit('addNewStatuses', { - statuses: data.statuses, - }) - return data + return search2({ + q, + resolve, + limit, + offset, + following, + type, + credentials: useCredentialsStore().current, + }).then((data) => { + store.commit('addNewUsers', data.accounts) + store.commit( + 'addNewUsers', + data.statuses.map((s) => s.user).filter((u) => u), + ) + data.statuses = store.commit('addNewStatuses', { + statuses: data.statuses, }) + return data + }) }, setVirtualHeight({ commit }, { statusId, height }) { commit('setVirtualHeight', { statusId, height }) diff --git a/src/modules/users.js b/src/modules/users.js index fa12c8558..c7f457a47 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -10,7 +10,6 @@ import { } from 'lodash' import { register } from '../services/api/api.service.js' -import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import oauthApi from '../services/new_api/oauth.js' import { registerPushNotifications, @@ -21,15 +20,35 @@ import { windowWidth, } from '../services/window_utils/window_utils' +import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useEmojiStore } from 'src/stores/emoji.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' +import { useListsStore } from 'src/stores/lists.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' +import { + fetchBlocks, + fetchDomainMutes, + fetchFollowers, + fetchFriends, + fetchMutes, + fetchUser, + fetchUserByName, + fetchUserInLists, + fetchUserRelationship, + followUser, + getCaptcha, + muteUser, + searchUsers, + verifyCredentials, +} from 'src/services/api/api.service.js' + // TODO: Unify with mergeOrAdd in statuses.js export const mergeOrAdd = (arr, obj, item) => { if (!item) { @@ -72,46 +91,38 @@ const blockUser = (store, args) => { store.commit('updateUserRelationship', [predictedRelationship]) store.commit('addBlockId', id) - return store.rootState.api.backendInteractor - .blockUser({ id, expiresIn }) - .then((relationship) => { - store.commit('updateUserRelationship', [relationship]) - store.commit('addBlockId', id) + return blockUser({ id, expiresIn }).then((relationship) => { + store.commit('updateUserRelationship', [relationship]) + store.commit('addBlockId', id) - store.commit('removeStatus', { timeline: 'friends', userId: id }) - store.commit('removeStatus', { timeline: 'public', userId: id }) - store.commit('removeStatus', { - timeline: 'publicAndExternal', - userId: id, - }) + store.commit('removeStatus', { timeline: 'friends', userId: id }) + store.commit('removeStatus', { timeline: 'public', userId: id }) + store.commit('removeStatus', { + timeline: 'publicAndExternal', + userId: id, }) + }) } const unblockUser = (store, id) => { - return store.rootState.api.backendInteractor - .unblockUser({ id }) - .then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return unblockUser({ id }).then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } const removeUserFromFollowers = (store, id) => { - return store.rootState.api.backendInteractor - .removeUserFromFollowers({ id }) - .then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return removeUserFromFollowers({ id }).then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } const editUserNote = (store, { id, comment }) => { - return store.rootState.api.backendInteractor - .editUserNote({ id, comment }) - .then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return editUserNote({ id, comment }).then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } -const muteUser = (store, args) => { +const localMuteUser = (store, args) => { const id = typeof args === 'object' ? args.id : args const expiresIn = typeof args === 'object' ? args.expiresIn : 0 @@ -119,12 +130,14 @@ const muteUser = (store, args) => { store.commit('updateUserRelationship', [predictedRelationship]) store.commit('addMuteId', id) - return store.rootState.api.backendInteractor - .muteUser({ id, expiresIn }) - .then((relationship) => { - store.commit('updateUserRelationship', [relationship]) - store.commit('addMuteId', id) - }) + return muteUser({ + id, + expiresIn, + credentials: useCredentialsStore().current, + }).then((relationship) => { + store.commit('updateUserRelationship', [relationship]) + store.commit('addMuteId', id) + }) } const unmuteUser = (store, id) => { @@ -132,39 +145,43 @@ const unmuteUser = (store, id) => { predictedRelationship.muting = false store.commit('updateUserRelationship', [predictedRelationship]) - return store.rootState.api.backendInteractor - .unmuteUser({ id }) - .then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return unmuteUser({ id }).then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } const hideReblogs = (store, userId) => { - return store.rootState.api.backendInteractor - .followUser({ id: userId, reblogs: false }) - .then((relationship) => { - store.commit('updateUserRelationship', [relationship]) - }) + return followUser({ + id: userId, + reblogs: false, + credentials: useCredentialsStore().current, + }).then((relationship) => { + store.commit('updateUserRelationship', [relationship]) + }) } const showReblogs = (store, userId) => { - return store.rootState.api.backendInteractor - .followUser({ id: userId, reblogs: true }) - .then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return followUser({ + id: userId, + reblogs: true, + credentials: useCredentialsStore().current, + }).then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } const muteDomain = (store, domain) => { - return store.rootState.api.backendInteractor - .muteDomain({ domain }) - .then(() => store.commit('addDomainMute', domain)) + return muteDomain({ + domain, + credentials: useCredentialsStore().current, + }).then(() => store.commit('addDomainMute', domain)) } const unmuteDomain = (store, domain) => { - return store.rootState.api.backendInteractor - .unmuteDomain({ domain }) - .then(() => store.commit('removeDomainMute', domain)) + return unmuteDomain({ + domain, + credentials: useCredentialsStore().current, + }).then(() => store.commit('removeDomainMute', domain)) } export const mutations = { @@ -385,55 +402,60 @@ const users = { }) }, fetchUser(store, id) { - return store.rootState.api.backendInteractor - .fetchUser({ id }) - .then((user) => { - store.commit('addNewUsers', [user]) - return user - }) + return fetchUser({ + id, + credentials: useCredentialsStore().current, + }).then((user) => { + store.commit('addNewUsers', [user]) + return user + }) }, fetchUserByName(store, name) { - return store.rootState.api.backendInteractor - .fetchUserByName({ name }) - .then((user) => { - store.commit('addNewUsers', [user]) - return user - }) + return fetchUserByName({ + name, + credentials: useCredentialsStore().current, + }).then((user) => { + store.commit('addNewUsers', [user]) + return user + }) }, fetchUserRelationship(store, id) { if (store.state.currentUser) { - store.rootState.api.backendInteractor - .fetchUserRelationship({ id }) - .then((relationships) => - store.commit('updateUserRelationship', relationships), - ) + fetchUserRelationship({ + id, + credentials: useCredentialsStore().current, + }).then((relationships) => + store.commit('updateUserRelationship', relationships), + ) } }, fetchUserInLists(store, id) { if (store.state.currentUser) { - store.rootState.api.backendInteractor - .fetchUserInLists({ id }) - .then((inLists) => store.commit('updateUserInLists', { id, inLists })) + fetchUserInLists({ + id, + credentials: useCredentialsStore().current, + }).then((inLists) => store.commit('updateUserInLists', { id, inLists })) } }, fetchBlocks(store, args) { const { reset } = args || {} const maxId = store.state.currentUser.blockIdsMaxId - return store.rootState.api.backendInteractor - .fetchBlocks({ maxId }) - .then((blocks) => { - if (reset) { - store.commit('saveBlockIds', map(blocks, 'id')) - } else { - map(blocks, 'id').map((id) => store.commit('addBlockId', id)) - } - if (blocks.length) { - store.commit('setBlockIdsMaxId', last(blocks).id) - } - store.commit('addNewUsers', blocks) - return blocks - }) + return fetchBlocks({ + maxId, + credentials: useCredentialsStore().current, + }).then((blocks) => { + if (reset) { + store.commit('saveBlockIds', map(blocks, 'id')) + } else { + map(blocks, 'id').map((id) => store.commit('addBlockId', id)) + } + if (blocks.length) { + store.commit('setBlockIdsMaxId', last(blocks).id) + } + store.commit('addNewUsers', blocks) + return blocks + }) }, blockUser(store, data) { return blockUser(store, data) @@ -457,23 +479,24 @@ const users = { const { reset } = args || {} const maxId = store.state.currentUser.muteIdsMaxId - return store.rootState.api.backendInteractor - .fetchMutes({ maxId }) - .then((mutes) => { - if (reset) { - store.commit('saveMuteIds', map(mutes, 'id')) - } else { - map(mutes, 'id').map((id) => store.commit('addMuteId', id)) - } - if (mutes.length) { - store.commit('setMuteIdsMaxId', last(mutes).id) - } - store.commit('addNewUsers', mutes) - return mutes - }) + return fetchMutes({ + maxId, + credentials: useCredentialsStore().current, + }).then((mutes) => { + if (reset) { + store.commit('saveMuteIds', map(mutes, 'id')) + } else { + map(mutes, 'id').map((id) => store.commit('addMuteId', id)) + } + if (mutes.length) { + store.commit('setMuteIdsMaxId', last(mutes).id) + } + store.commit('addNewUsers', mutes) + return mutes + }) }, muteUser(store, data) { - return muteUser(store, data) + return localMuteUser(store, data) }, unmuteUser(store, id) { return unmuteUser(store, id) @@ -485,18 +508,18 @@ const users = { return showReblogs(store, id) }, muteUsers(store, data = []) { - return Promise.all(data.map((d) => muteUser(store, d))) + return Promise.all(data.map((d) => localMuteUser(store, d))) }, unmuteUsers(store, ids = []) { return Promise.all(ids.map((d) => unmuteUser(store, d))) }, fetchDomainMutes(store) { - return store.rootState.api.backendInteractor - .fetchDomainMutes() - .then((domainMutes) => { - store.commit('saveDomainMutes', domainMutes) - return domainMutes - }) + return fetchDomainMutes({ + credentials: useCredentialsStore().current, + }).then((domainMutes) => { + store.commit('saveDomainMutes', domainMutes) + return domainMutes + }) }, muteDomain(store, domain) { return muteDomain(store, domain) @@ -513,24 +536,28 @@ const users = { fetchFriends({ rootState, commit }, id) { const user = rootState.users.usersObject[id] const maxId = last(user.friendIds) - return rootState.api.backendInteractor - .fetchFriends({ id, maxId }) - .then((friends) => { - commit('addNewUsers', friends) - commit('saveFriendIds', { id, friendIds: map(friends, 'id') }) - return friends - }) + return fetchFriends({ + id, + maxId, + credentials: useCredentialsStore().current, + }).then((friends) => { + commit('addNewUsers', friends) + commit('saveFriendIds', { id, friendIds: map(friends, 'id') }) + return friends + }) }, fetchFollowers({ rootState, commit }, id) { const user = rootState.users.usersObject[id] const maxId = last(user.followerIds) - return rootState.api.backendInteractor - .fetchFollowers({ id, maxId }) - .then((followers) => { - commit('addNewUsers', followers) - commit('saveFollowerIds', { id, followerIds: map(followers, 'id') }) - return followers - }) + return fetchFollowers({ + id, + maxId, + credentials: useCredentialsStore().current, + }).then((followers) => { + commit('addNewUsers', followers) + commit('saveFollowerIds', { id, followerIds: map(followers, 'id') }) + return followers + }) }, clearFriends({ commit }, userId) { commit('clearFriends', userId) @@ -539,18 +566,22 @@ const users = { commit('clearFollowers', userId) }, subscribeUser({ rootState, commit }, id) { - return rootState.api.backendInteractor - .followUser({ id, notify: true }) - .then((relationship) => - commit('updateUserRelationship', [relationship]), - ) + return followUser({ + id, + notify: true, + credentials: useCredentialsStore().current, + }).then((relationship) => + commit('updateUserRelationship', [relationship]), + ) }, unsubscribeUser({ rootState, commit }, id) { - return rootState.api.backendInteractor - .followUser({ id, notify: false }) - .then((relationship) => - commit('updateUserRelationship', [relationship]), - ) + return followUser({ + id, + notify: false, + credentials: useCredentialsStore().current, + }).then((relationship) => + commit('updateUserRelationship', [relationship]), + ) }, registerPushNotifications(store) { const token = store.state.currentUser.credentials @@ -611,12 +642,13 @@ const users = { }) }, searchUsers({ rootState, commit }, { query }) { - return rootState.api.backendInteractor - .searchUsers({ query }) - .then((users) => { - commit('addNewUsers', users) - return users - }) + return searchUsers({ + query, + credentials: useCredentialsStore().current, + }).then((users) => { + commit('addNewUsers', users) + return users + }) }, async signUp(store, userInfo) { const oauthStore = useOAuthStore() @@ -645,8 +677,10 @@ const users = { throw e } }, - async getCaptcha(store) { - return store.rootState.api.backendInteractor.getCaptcha() + getCaptcha(store) { + return getCaptcha({ + credentials: useCredentialsStore().current, + }) }, logout(store) { @@ -670,13 +704,10 @@ const users = { store.dispatch('disconnectFromSocket') oauth.clearToken() store.dispatch('stopFetchingTimeline', 'friends') - store.commit( - 'setBackendInteractor', - backendInteractorService(oauth.getToken), - ) + useCredentialsStore().setCredentials(null) store.dispatch('stopFetchingNotifications') - store.dispatch('stopFetchingLists') - store.dispatch('stopFetchingBookmarkFolders') + useListsStore().stopFetching() + useBookmarkFoldersStore().stopFetching() store.dispatch('stopFetchingFollowRequests') store.commit('clearNotifications') store.commit('resetStatuses') @@ -691,8 +722,9 @@ const users = { const commit = store.commit const dispatch = store.dispatch commit('beginLogin') - store.rootState.api.backendInteractor - .verifyCredentials(accessToken) + verifyCredentials({ + credentials: useCredentialsStore().current, + }) .then((data) => { if (!data.error) { const user = data @@ -721,11 +753,8 @@ const users = { useInterfaceStore().setNotificationPermission(permission), ) - // Set our new backend interactor - commit( - 'setBackendInteractor', - backendInteractorService(accessToken), - ) + // Update credentials + useCredentialsStore().setCredentials(accessToken) // Do server-side storage migrations @@ -764,8 +793,8 @@ const users = { } } - dispatch('startFetchingLists') - dispatch('startFetchingBookmarkFolders') + useListsStore().startFetching() + useBookmarkFoldersStore().startFetching() if (user.locked) { dispatch('startFetchingFollowRequests') @@ -799,9 +828,9 @@ const users = { useInterfaceStore().setLayoutHeight(windowHeight()) // Fetch our friends - store.rootState.api.backendInteractor - .fetchFriends({ id: user.id }) - .then((friends) => commit('addNewUsers', friends)) + fetchFriends({ id: user.id }).then((friends) => + commit('addNewUsers', friends), + ) } else { const response = data.error // Authentication failed diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 9a7d266ae..fb583bc84 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -126,6 +126,10 @@ const PLEROMA_USER_FAVORITES_TIMELINE_URL = (id) => const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders' const PLEROMA_BOOKMARK_FOLDER_URL = (id) => `/api/v1/pleroma/bookmark_folders/${id}` + +const EMOJI_PACKS_URL = (page, pageSize) => + `/api/v1/pleroma/emoji/packs?page=${page}&page_size=${pageSize}` + export const updateNotificationSettings = ({ credentials, settings }) => { const form = new FormData() @@ -682,6 +686,11 @@ export const fetchTimeline = ({ }) } +export const listEmojiPacks = ({ page, pageSize, credentials }) => + promisedRequest({ + url: EMOJI_PACKS_URL(page, pageSize), + }) + export const fetchPinnedStatuses = ({ id, credentials }) => promisedRequest({ url: MASTODON_USER_TIMELINE_URL(id) + '?pinned=true', diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js deleted file mode 100644 index c9ef67799..000000000 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ /dev/null @@ -1,72 +0,0 @@ -import bookmarkFoldersFetcher from '../../services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js' -import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service' -import listsFetcher from '../../services/lists_fetcher/lists_fetcher.service.js' -import * as apiService from '../api/api.service.js' -import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js' -import timelineFetcher from '../timeline_fetcher/timeline_fetcher.service.js' - -import { useInstanceStore } from 'src/stores/instance.js' - -const backendInteractorService = (credentials) => ({ - startFetchingTimeline({ - timeline, - store, - userId = false, - listId = false, - statusId = false, - bookmarkFolderId = false, - tag, - }) { - return timelineFetcher.startFetching({ - timeline, - store, - credentials, - userId, - listId, - statusId, - bookmarkFolderId, - tag, - }) - }, - - fetchTimeline(args) { - return timelineFetcher.fetchAndUpdate({ ...args, credentials }) - }, - - startFetchingNotifications({ store }) { - return notificationsFetcher.startFetching({ store, credentials }) - }, - - fetchNotifications(args) { - return notificationsFetcher.fetchAndUpdate({ ...args, credentials }) - }, - - startFetchingFollowRequests({ store }) { - return followRequestFetcher.startFetching({ store, credentials }) - }, - - startFetchingLists({ store }) { - return listsFetcher.startFetching({ store, credentials }) - }, - - startFetchingBookmarkFolders({ store }) { - return bookmarkFoldersFetcher.startFetching({ store, credentials }) - }, - - startUserSocket({ store }) { - const serv = useInstanceStore().server.replace('http', 'ws') - const url = apiService.getMastodonSocketURI({}, serv) - return apiService.ProcessedWS({ url, id: 'Unified', credentials }) - }, - - ...Object.entries(apiService).reduce((acc, [key, func]) => { - return { - ...acc, - [key]: (args) => func({ credentials, ...args }), - } - }, {}), - - verifyCredentials: apiService.verifyCredentials, -}) - -export default backendInteractorService diff --git a/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js b/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js deleted file mode 100644 index 987b0786a..000000000 --- a/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js +++ /dev/null @@ -1,31 +0,0 @@ -import { fetchBookmarkFolders } from '../api/api.service.js' -import { promiseInterval } from '../promise_interval/promise_interval.js' - -import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' - -const fetchAndUpdate = ({ credentials }) => { - return fetchBookmarkFolders({ credentials }) - .then( - (bookmarkFolders) => { - useBookmarkFoldersStore().setBookmarkFolders(bookmarkFolders) - }, - (rej) => { - console.error(rej) - }, - ) - .catch((e) => { - console.error(e) - }) -} - -const startFetching = ({ credentials, store }) => { - const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store }) - boundFetchAndUpdate() - return promiseInterval(boundFetchAndUpdate, 240000) -} - -const bookmarkFoldersFetcher = { - startFetching, -} - -export default bookmarkFoldersFetcher diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index 209a5f0c0..8daee3bc3 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -1,8 +1,18 @@ +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { + fetchUserRelationship, + followUser, + unfollowUser, +} from 'src/services/api/api.service.js' + const fetchRelationship = (attempt, userId, store) => new Promise((resolve, reject) => { setTimeout(() => { - store.state.api.backendInteractor - .fetchUserRelationship({ id: userId }) + fetchUserRelationship({ + id: userId, + credentials: useCredentialsStore().current, + }) .then((relationship) => { store.commit('updateUserRelationship', [relationship]) return relationship @@ -27,38 +37,40 @@ const fetchRelationship = (attempt, userId, store) => export const requestFollow = (userId, store) => new Promise((resolve) => { - store.state.api.backendInteractor - .followUser({ id: userId }) - .then((updated) => { - store.commit('updateUserRelationship', [updated]) + followUser({ + id: userId, + credentials: useCredentialsStore().current, + }).then((updated) => { + store.commit('updateUserRelationship', [updated]) - if (updated.following || (updated.locked && updated.requested)) { - // If we get result immediately or the account is locked, just stop. - resolve() - return - } + if (updated.following || (updated.locked && updated.requested)) { + // If we get result immediately or the account is locked, just stop. + resolve() + return + } - // But usually we don't get result immediately, so we ask server - // for updated user profile to confirm if we are following them - // Sometimes it takes several tries. Sometimes we end up not following - // user anyway, probably because they locked themselves and we - // don't know that yet. - // Recursive Promise, it will call itself up to 3 times. + // But usually we don't get result immediately, so we ask server + // for updated user profile to confirm if we are following them + // Sometimes it takes several tries. Sometimes we end up not following + // user anyway, probably because they locked themselves and we + // don't know that yet. + // Recursive Promise, it will call itself up to 3 times. - return fetchRelationship(1, updated, store).then(() => { - resolve() - }) + return fetchRelationship(1, updated, store).then(() => { + resolve() }) + }) }) export const requestUnfollow = (userId, store) => new Promise((resolve) => { - store.state.api.backendInteractor - .unfollowUser({ id: userId }) - .then((updated) => { - store.commit('updateUserRelationship', [updated]) - resolve({ - updated, - }) + unfollowUser({ + id: userId, + credentials: useCredentialsStore().current, + }).then((updated) => { + store.commit('updateUserRelationship', [updated]) + resolve({ + updated, }) + }) }) diff --git a/src/services/lists_fetcher/lists_fetcher.service.js b/src/services/lists_fetcher/lists_fetcher.service.js deleted file mode 100644 index f647f8271..000000000 --- a/src/services/lists_fetcher/lists_fetcher.service.js +++ /dev/null @@ -1,31 +0,0 @@ -import { fetchLists } from '../api/api.service.js' -import { promiseInterval } from '../promise_interval/promise_interval.js' - -import { useListsStore } from 'src/stores/lists.js' - -const fetchAndUpdate = ({ credentials }) => { - return fetchLists({ credentials }) - .then( - (lists) => { - useListsStore().setLists(lists) - }, - (rej) => { - console.error(rej) - }, - ) - .catch((e) => { - console.error(e) - }) -} - -const startFetching = ({ credentials, store }) => { - const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store }) - boundFetchAndUpdate() - return promiseInterval(boundFetchAndUpdate, 240000) -} - -const listsFetcher = { - startFetching, -} - -export default listsFetcher diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index 8f4ce5352..d76c5f6d5 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -4,16 +4,26 @@ import { defineStore } from 'pinia' import { useCredentialsStore } from 'src/stores/credentials.js' import { + addNewEmojiFile, changeStatusScope, + createEmojiPack, deleteAccounts, + deleteEmojiPack, disableMFA, + downloadRemoteEmojiPack, + downloadRemoteEmojiPackZIP, getAvailableFrontends, getInstanceConfigDescriptions, getInstanceDBConfig, getUserData, + importEmojiFromFS, + installFrontend, + listEmojiPacks, + listRemoteEmojiPacks, listStatuses, listUsers, pushInstanceDBConfig, + reloadEmoji, requirePasswordChange, resendConfirmationEmail, setUsersActivationStatus, @@ -342,6 +352,12 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) }, + installFrontend() { + return installFrontend({ + credentials: useCredentialsStore().current, + }) + }, + // Statuses stuff async fetchStatuses(opts) { const { total, activities } = await listStatuses({ @@ -526,5 +542,62 @@ export const useAdminSettingsStore = defineStore('adminSettings', { window.vuex.commit('updateUserAdminData', { user }) }) }, + reloadEmoji() { + return reloadEmoji({ credentials: useCredentialsStore().current }) + }, + importEmojiFromFS() { + return importEmojiFromFS({ credentials: useCredentialsStore().current }) + }, + listEmojiPacks() { + return listEmojiPacks({ credentials: useCredentialsStore().current }) + }, + listRemoteEmojiPacks() { + return listRemoteEmojiPacks({ + credentials: useCredentialsStore().current, + }) + }, + addNewEmojiFile({ packName, file, shortcode, filename }) { + return addNewEmojiFile({ + packName, + file, + shortcode, + filename, + credentials: useCredentialsStore().current, + }) + }, + downloadRemoteEmojiPack({ instance, packName, as }) { + return downloadRemoteEmojiPack({ + instance, + packName, + as, + credentials: useCredentialsStore().current, + }) + }, + downloadRemoteEmojiPackZIP({ url, packName }) { + return downloadRemoteEmojiPackZIP({ + url, + packName, + credentials: useCredentialsStore().current, + }) + }, + createEmojiPack({ name }) { + return createEmojiPack({ + name, + credentials: useCredentialsStore().current, + }) + }, + deleteEmojiPack({ name }) { + return createEmojiPack({ + name, + credentials: useCredentialsStore().current, + }) + }, + saveEmojiPackMetadata({ name, newData }) { + return createEmojiPack({ + name, + newData, + credentials: useCredentialsStore().current, + }) + }, }, }) diff --git a/src/stores/bookmark_folders.js b/src/stores/bookmark_folders.js index 54e38f701..c0345cbf8 100644 --- a/src/stores/bookmark_folders.js +++ b/src/stores/bookmark_folders.js @@ -6,8 +6,10 @@ import { useCredentialsStore } from 'src/stores/credentials.js' import { createBookmarkFolder, deleteBookmarkFolder, + fetchBookmarkFolders, updateBookmarkFolder, } from 'src/services/api/api.service.js' +import { promiseInterval } from 'src/services/promise_interval/promise_interval.js' export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { state: () => ({ @@ -24,6 +26,23 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { }, }, actions: { + startFetching() { + promiseInterval(() => { + this.fetcher = fetchBookmarkFolders({ + credentials: useCredentialsStore().current, + }) + .then( + (folders) => this.setBookmarkFolders(folders), + (rej) => console.error(rej), + ) + .catch((e) => { + console.error(e) + }) + }, 240000) + }, + stopFetching() { + this.fetcher?.stop() + }, setBookmarkFolders(value) { this.allFolders = value }, diff --git a/src/stores/emoji.js b/src/stores/emoji.js index e28143300..83b897593 100644 --- a/src/stores/emoji.js +++ b/src/stores/emoji.js @@ -1,9 +1,11 @@ import { merge } from 'lodash' import { defineStore } from 'pinia' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { ensureFinalFallback } from 'src/i18n/languages.js' +import { listEmojiPacks } from 'src/services/api/api.service.js' import { annotationsLoader } from 'virtual:pleroma-fe/emoji-annotations' @@ -183,13 +185,13 @@ export const useEmojiStore = defineStore('emoji', { async getAdminPacksLocal(refresh) { if (!refresh && this.adminPacksLocal) return this.adminPacksLocal - const backendInteractor = window.vuex.state.api.backendInteractor - const listFunction = backendInteractor.listEmojiPacks - this.adminPacksLocalLoading = true this.adminPacksLocal = await this.getAdminPacks( useInstanceStore().server, - listFunction, + () => + listEmojiPacks({ + credentials: useCredentialsStore().current, + }), ) this.adminPacksLocalLoading = false }, diff --git a/src/stores/lists.js b/src/stores/lists.js index b33a119cc..8b90c379c 100644 --- a/src/stores/lists.js +++ b/src/stores/lists.js @@ -1,8 +1,23 @@ import { find, remove } from 'lodash' import { defineStore } from 'pinia' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { + addAccountsToList, + createList, + deleteList, + fetchLists, + getList, + getListAccounts, + removeAccountsFromList, + updateList, +} from 'src/services/api/api.service.js' +import { promiseInterval } from 'src/services/promise_interval/promise_interval.js' + export const useListsStore = defineStore('lists', { state: () => ({ + fetcher: null, allLists: [], allListsObject: {}, }), @@ -18,34 +33,58 @@ export const useListsStore = defineStore('lists', { }, }, actions: { + startFetching() { + promiseInterval(() => { + this.fetcher = fetchLists({ + credentials: useCredentialsStore().current, + }) + .then( + (lists) => this.setLists(lists), + (rej) => console.error(rej), + ) + .catch((e) => { + console.error(e) + }) + }, 240000) + }, + stopFetching() { + this.fetcher?.stop() + }, setLists(value) { this.allLists = value }, createList({ title }) { - return window.vuex.state.api.backendInteractor - .createList({ title }) - .then((list) => { - this.setList({ listId: list.id, title }) - return list - }) + return createList({ + title, + credentials: useCredentialsStore().current, + }).then((list) => { + this.setList({ listId: list.id, title }) + return list + }) }, fetchList({ listId }) { - return window.vuex.state.api.backendInteractor - .getList({ listId }) - .then((list) => this.setList({ listId: list.id, title: list.title })) + return getList({ + listId, + credentials: useCredentialsStore().current, + }).then((list) => this.setList({ listId: list.id, title: list.title })) }, fetchListAccounts({ listId }) { - return window.vuex.state.api.backendInteractor - .getListAccounts({ listId }) - .then((accountIds) => { - if (!this.allListsObject[listId]) { - this.allListsObject[listId] = { accountIds: [] } - } - this.allListsObject[listId].accountIds = accountIds - }) + return getListAccounts({ + listId, + credentials: useCredentialsStore().current, + }).then((accountIds) => { + if (!this.allListsObject[listId]) { + this.allListsObject[listId] = { accountIds: [] } + } + this.allListsObject[listId].accountIds = accountIds + }) }, setList({ listId, title }) { - window.vuex.state.api.backendInteractor.updateList({ listId, title }) + updateList({ + listId, + title, + credentials: useCredentialsStore().current, + }) if (!this.allListsObject[listId]) { this.allListsObject[listId] = { accountIds: [] } @@ -68,46 +107,55 @@ export const useListsStore = defineStore('lists', { } this.allListsObject[listId].accountIds = accountIds if (added.length > 0) { - window.vuex.state.api.backendInteractor.addAccountsToList({ + addAccountsToList({ listId, accountIds: added, + credentials: useCredentialsStore().current, }) } if (removed.length > 0) { - window.vuex.state.api.backendInteractor.removeAccountsFromList({ + removeAccountsFromList({ listId, accountIds: removed, + credentials: useCredentialsStore().current, }) } }, addListAccount({ listId, accountId }) { - return window.vuex.state.api.backendInteractor - .addAccountsToList({ listId, accountIds: [accountId] }) - .then((result) => { - if (!this.allListsObject[listId]) { - this.allListsObject[listId] = { accountIds: [] } - } - this.allListsObject[listId].accountIds.push(accountId) - return result - }) + return addAccountsToList({ + listId, + accountIds: [accountId], + credentials: useCredentialsStore().current, + }).then((result) => { + if (!this.allListsObject[listId]) { + this.allListsObject[listId] = { accountIds: [] } + } + this.allListsObject[listId].accountIds.push(accountId) + return result + }) }, removeListAccount({ listId, accountId }) { - return window.vuex.state.api.backendInteractor - .removeAccountsFromList({ listId, accountIds: [accountId] }) - .then((result) => { - if (!this.allListsObject[listId]) { - this.allListsObject[listId] = { accountIds: [] } - } - const { accountIds } = this.allListsObject[listId] - const set = new Set(accountIds) - set.delete(accountId) - this.allListsObject[listId].accountIds = [...set] + return removeAccountsFromList({ + listId, + accountIds: [accountId], + credentials: useCredentialsStore().current, + }).then((result) => { + if (!this.allListsObject[listId]) { + this.allListsObject[listId] = { accountIds: [] } + } + const { accountIds } = this.allListsObject[listId] + const set = new Set(accountIds) + set.delete(accountId) + this.allListsObject[listId].accountIds = [...set] - return result - }) + return result + }) }, deleteList({ listId }) { - window.vuex.state.api.backendInteractor.deleteList({ listId }) + deleteList({ + listId, + credentials: useCredentialsStore().current, + }) delete this.allListsObject[listId] remove(this.allLists, (list) => list.id === listId) diff --git a/src/stores/oauth_tokens.js b/src/stores/oauth_tokens.js index ae9b396ac..0322f8cf1 100644 --- a/src/stores/oauth_tokens.js +++ b/src/stores/oauth_tokens.js @@ -1,25 +1,33 @@ import { defineStore } from 'pinia' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { + fetchOAuthTokens, + revokeOAuthToken, +} from 'src/services/api/api.service.js' + export const useOAuthTokensStore = defineStore('oauthTokens', { state: () => ({ tokens: [], }), actions: { fetchTokens() { - window.vuex.state.api.backendInteractor - .fetchOAuthTokens() - .then((tokens) => { - this.swapTokens(tokens) - }) + fetchOAuthTokens({ + credentials: useCredentialsStore().current, + }).then((tokens) => { + this.swapTokens(tokens) + }) }, revokeToken(id) { - window.vuex.state.api.backendInteractor - .revokeOAuthToken({ id }) - .then((response) => { - if (response.status === 201) { - this.swapTokens(this.tokens.filter((token) => token.id !== id)) - } - }) + revokeOAuthToken({ + id, + credentials: useCredentialsStore().current, + }).then((response) => { + if (response.status === 201) { + this.swapTokens(this.tokens.filter((token) => token.id !== id)) + } + }) }, swapTokens(tokens) { this.tokens = tokens diff --git a/src/stores/polls.js b/src/stores/polls.js index aac8a2421..5af6deb0d 100644 --- a/src/stores/polls.js +++ b/src/stores/polls.js @@ -1,6 +1,10 @@ import { merge } from 'lodash' import { defineStore } from 'pinia' +import { useCredentialsStore } from 'src/stores/credentials.js' + +import { fetchPoll, vote } from 'src/services/api/api.service.js' + export const usePollsStore = defineStore('polls', { state: () => ({ // Contains key = id, value = number of trackers for this poll @@ -19,16 +23,17 @@ export const usePollsStore = defineStore('polls', { } }, updateTrackedPoll(pollId) { - window.vuex.state.api.backendInteractor - .fetchPoll({ pollId }) - .then((poll) => { - setTimeout(() => { - if (this.trackedPolls[pollId]) { - this.updateTrackedPoll(pollId) - } - }, 30 * 1000) - this.mergeOrAddPoll(poll) - }) + fetchPoll({ + pollId, + credentials: useCredentialsStore().current, + }).then((poll) => { + setTimeout(() => { + if (this.trackedPolls[pollId]) { + this.updateTrackedPoll(pollId) + } + }, 30 * 1000) + this.mergeOrAddPoll(poll) + }) }, trackPoll(pollId) { if (!this.trackedPolls[pollId]) { @@ -50,12 +55,14 @@ export const usePollsStore = defineStore('polls', { } }, votePoll({ pollId, choices }) { - return window.vuex.state.api.backendInteractor - .vote({ pollId, choices }) - .then((poll) => { - this.mergeOrAddPoll(poll) - return poll - }) + return vote({ + pollId, + choices, + credentials: useCredentialsStore().current, + }).then((poll) => { + this.mergeOrAddPoll(poll) + return poll + }) }, }, }) diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js index 3010fc738..bcb881fff 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -20,6 +20,7 @@ import { toRaw } from 'vue' import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' +import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useLocalConfigStore } from 'src/stores/local_config.js' @@ -31,6 +32,7 @@ import { validateSetting, } from 'src/modules/default_config_state.js' import { oldDefaultConfigSync } from 'src/modules/old_default_config_state.js' +import { updateProfileJSON } from 'src/services/api/api.service.js' export const VERSION = 2 export const NEW_USER_DATE = new Date('2026-03-16') // date of writing this, basically @@ -789,7 +791,10 @@ export const useSyncConfigStore = defineStore('sync_config', { if (!needPush) return this.updateCache({ username: window.vuex.state.users.currentUser.fqn }) const params = { pleroma_settings_store: { 'pleroma-fe': this.cache } } - window.vuex.state.api.backendInteractor.updateProfileJSON({ params }) + updateProfileJSON({ + params, + credentials: useCredentialsStore().current, + }) }, }, persist: { diff --git a/src/stores/user_highlight.js b/src/stores/user_highlight.js index f41c41628..564a4395a 100644 --- a/src/stores/user_highlight.js +++ b/src/stores/user_highlight.js @@ -14,7 +14,10 @@ import { import { defineStore } from 'pinia' import { toRaw } from 'vue' +import { useCredentialsStore } from 'src/stores/credentials.js' + import { storage } from 'src/lib/storage.js' +import { updateProfileJSON } from 'src/services/api/api.service.js' export const NEW_USER_DATE = new Date('2022-08-04') // date of writing this, basically @@ -344,12 +347,13 @@ export const useUserHighlightStore = defineStore('user_highlight', { const params = { pleroma_settings_store: { user_highlight: this.cache }, } - window.vuex.state.api.backendInteractor - .updateProfileJSON({ params }) - .then((user) => { - this.initUserHighlight(user) - this.dirty = false - }) + updateProfileJSON({ + params, + credentials: useCredentialsStore().current, + }).then((user) => { + this.initUserHighlight(user) + this.dirty = false + }) }, }, persist: { diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js index 8b198420b..36d323575 100644 --- a/test/unit/specs/components/user_profile.spec.js +++ b/test/unit/specs/components/user_profile.spec.js @@ -4,7 +4,6 @@ import { createStore } from 'vuex' import UserProfile from 'src/components/user_profile/user_profile.vue' import { getters } from 'src/modules/users.js' -import backendInteractorService from 'src/services/backend_interactor_service/backend_interactor_service.js' const mutations = { clearTimeline: () => { @@ -53,10 +52,6 @@ const externalProfileStore = createStore({ actions, getters: testGetters, state: { - api: { - fetchers: {}, - backendInteractor: backendInteractorService(''), - }, interface: { browserSupport: '', }, @@ -116,10 +111,6 @@ const localProfileStore = createStore({ actions, getters: testGetters, state: { - api: { - fetchers: {}, - backendInteractor: backendInteractorService(''), - }, interface: { browserSupport: '', }, From 7999f2d34b4f05fa4e0930b4215f8d9f47a647e2 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 15 Jun 2026 20:25:28 +0300 Subject: [PATCH 04/86] fix emoji tab --- .../settings_modal/helpers/emoji_editing_popover.vue | 2 +- src/stores/admin_settings.js | 10 +++++++--- src/stores/emoji.js | 8 +++----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue index 2c7b151be..102d231ba 100644 --- a/src/components/settings_modal/helpers/emoji_editing_popover.vue +++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue @@ -159,7 +159,7 @@ import { addNewEmojiFile, deleteEmojiFile, updateEmojiFile, -} from 'src/services/api/api.service.js' +} from 'src/services/api/admin.js' export default { components: { diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index d76c5f6d5..1ced1a1c1 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -548,11 +548,15 @@ export const useAdminSettingsStore = defineStore('adminSettings', { importEmojiFromFS() { return importEmojiFromFS({ credentials: useCredentialsStore().current }) }, - listEmojiPacks() { - return listEmojiPacks({ credentials: useCredentialsStore().current }) + listEmojiPacks(params) { + return listEmojiPacks({ + ...params, + credentials: useCredentialsStore().current + }) }, - listRemoteEmojiPacks() { + listRemoteEmojiPacks(params) { return listRemoteEmojiPacks({ + ...params, credentials: useCredentialsStore().current, }) }, diff --git a/src/stores/emoji.js b/src/stores/emoji.js index 83b897593..2a30d651b 100644 --- a/src/stores/emoji.js +++ b/src/stores/emoji.js @@ -188,8 +188,8 @@ export const useEmojiStore = defineStore('emoji', { this.adminPacksLocalLoading = true this.adminPacksLocal = await this.getAdminPacks( useInstanceStore().server, - () => - listEmojiPacks({ + (params) => listEmojiPacks({ + ...params, credentials: useCredentialsStore().current, }), ) @@ -208,7 +208,6 @@ export const useEmojiStore = defineStore('emoji', { page: 1, pageSize: 0, }) - .then((data) => data.json()) .then((data) => { if (data.error !== undefined) { return Promise.reject(data.error) @@ -223,7 +222,6 @@ export const useEmojiStore = defineStore('emoji', { page: i, pageSize, }) - .then((data) => data.json()) .then((pageData) => { if (pageData.error !== undefined) { return Promise.reject(pageData.error) @@ -249,7 +247,7 @@ export const useEmojiStore = defineStore('emoji', { }, {}) }) .catch((data) => { - this.displayError(data) + console.error(data) }) }, From 42c0403ade83bc8cd0c59327f45d0f01fe4a992d Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 16:29:15 +0300 Subject: [PATCH 05/86] move emoji packs away from admin --- src/services/api/admin.js | 7 ------- src/stores/admin_settings.js | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/services/api/admin.js b/src/services/api/admin.js index debc61575..659832ae8 100644 --- a/src/services/api/admin.js +++ b/src/services/api/admin.js @@ -66,8 +66,6 @@ const REQUIRE_PASSWORD_CHANGE_URL = const DISABLE_MFA_URL = '/api/v1/pleroma/admin/users/disable_mfa' const EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji' const EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import' -const EMOJI_PACKS_URL = (page, pageSize) => - `/api/v1/pleroma/emoji/packs?page=${page}&page_size=${pageSize}` const EMOJI_PACK_URL = (name) => `/api/v1/pleroma/emoji/pack?name=${name}` const EMOJI_PACKS_DL_REMOTE_URL = '/api/v1/pleroma/emoji/packs/download' const EMOJI_PACKS_DL_REMOTE_ZIP_URL = '/api/v1/pleroma/emoji/packs/download_zip' @@ -407,11 +405,6 @@ export const createEmojiPack = ({ name, credentials }) => credentials, }) -export const listEmojiPacks = ({ page, pageSize, credentials }) => - promisedRequest({ - url: EMOJI_PACKS_URL(page, pageSize), - }) - export const listRemoteEmojiPacks = ({ instance, page, diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index 1ced1a1c1..9440d17f8 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -18,7 +18,6 @@ import { getUserData, importEmojiFromFS, installFrontend, - listEmojiPacks, listRemoteEmojiPacks, listStatuses, listUsers, @@ -33,6 +32,7 @@ import { setUsersSuggestionStatus, setUsersTags, } from 'src/services/api/admin.js' +import { listEmojiPacks } from 'src/services/api/api.service.js' import { parseStatus } from 'src/services/entity_normalizer/entity_normalizer.service.js' export const defaultState = { From 941211ea6c3c4cfe7c51083572567159be92cf9e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 16:32:45 +0300 Subject: [PATCH 06/86] fix list endpoints --- src/services/api/admin.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/api/admin.js b/src/services/api/admin.js index 659832ae8..64a88ca9b 100644 --- a/src/services/api/admin.js +++ b/src/services/api/admin.js @@ -6,12 +6,12 @@ const REPORTS = '/api/v1/pleroma/admin/reports' const CONFIG_URL = '/api/v1/pleroma/admin/config' const DESCRIPTIONS_URL = '/api/v1/pleroma/admin/config/descriptions' -const ANNOUNCEMENTS_URL = (id) => '/api/v1/pleroma/admin/announcements/${id}' +const ANNOUNCEMENTS_URL = (id = '') => `/api/v1/pleroma/admin/announcements/${id}` const FRONTENDS_URL = '/api/v1/pleroma/admin/frontends' const FRONTENDS_INSTALL_URL = '/api/v1/pleroma/admin/frontends/install' -const USERS_URL = (nickname) => `/api/v1/pleroma/admin/users/${nickname}` +const USERS_URL = (nickname = '') => `/api/v1/pleroma/admin/users/${nickname}` const USERS_URL_LIST = ({ page, pageSize, @@ -178,8 +178,8 @@ export const deleteAccounts = ({ credentials, screen_names: nicknames }) => }, }) -export const getAnnouncements = ({ credentials }) => - promisedRequest({ url: ANNOUNCEMENTS_URL(), credentials }) +export const getAnnouncements = ({ id, credentials }) => + promisedRequest({ url: ANNOUNCEMENTS_URL(id), credentials }) // the reported list is hardly useful because standards are for dating i guess, // so make sure to fetchIfMissing right afterward using this call From e5c27fccc617dbf24e7aa8749c8cac95b9553432 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 16:37:47 +0300 Subject: [PATCH 07/86] fix status fetching --- src/services/api/api.service.js | 36 +++++++++++---------------------- 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index fb583bc84..1aeeaa603 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -524,42 +524,33 @@ export const fetchConversation = ({ id, credentials }) => url: MASTODON_STATUS_CONTEXT_URL(id), credentials, }) - .then((data) => { - if (data.ok) { - return data - } - throw new Error('Error fetching timeline', data) - }) .then(({ ancestors, descendants }) => ({ ancestors: ancestors.map(parseStatus), descendants: descendants.map(parseStatus), })) + .catch((error) => { + throw new Error('Error fetching timeline', error) + }) export const fetchStatus = ({ id, credentials }) => promisedRequest({ url: MASTODON_STATUS_URL(id), credentials, }) - .then((data) => { - if (data.ok) { - return data - } - throw new Error('Error fetching timeline', { cause: data }) - }) .then((data) => parseStatus(data)) + .catch((error) => { + throw new Error('Error fetching timeline', error) + }) export const fetchStatusSource = ({ id, credentials }) => promisedRequest({ url: MASTODON_STATUS_SOURCE_URL(id), credentials, }) - .then((data) => { - if (data.ok) { - return data - } - throw new Error('Error fetching source', { cause: data }) - }) .then((data) => parseSource(data)) + .catch((error) => { + throw new Error('Error fetching timeline', error) + }) export const fetchStatusHistory = ({ status, credentials }) => promisedRequest({ @@ -1281,17 +1272,14 @@ export const search2 = ({ url, credentials, }) - .then((data) => { - if (data.ok) { - return data - } - throw new Error('Error fetching search result', data) - }) .then((data) => { data.accounts = data.accounts.slice(0, limit).map((u) => parseUser(u)) data.statuses = data.statuses.slice(0, limit).map((s) => parseStatus(s)) return data }) + .catch((error) => { + throw new Error('Error fetching timeline', error) + }) } export const fetchKnownDomains = ({ credentials }) => From 9d8a52be5b2795d931bd0d1950750fab69fdc2be Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 16:44:25 +0300 Subject: [PATCH 08/86] don't rely on vuex anymore here --- src/stores/credentials.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/credentials.js b/src/stores/credentials.js index 2070162c4..38f9227e8 100644 --- a/src/stores/credentials.js +++ b/src/stores/credentials.js @@ -13,7 +13,7 @@ export const useCredentialsStore = defineStore('credentials', { }, getters: { current() { - return window.vuex.state.users.currentUser.credentials + return this.credentials }, }, }) From 7c3bab0070af91f3dcc358e11ab5a21bbcea3ae9 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 16:48:08 +0300 Subject: [PATCH 09/86] don't fetch chats twice --- src/modules/chats.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/chats.js b/src/modules/chats.js index 4d56782a0..92d951be4 100644 --- a/src/modules/chats.js +++ b/src/modules/chats.js @@ -59,7 +59,6 @@ const chatsModule = { // Chat list startFetchingChats({ dispatch, commit }) { const fetcher = () => dispatch('fetchChats', { latest: true }) - fetcher() commit('setChatListFetcher', { fetcher: () => promiseInterval(fetcher, 5000), }) From 2b7b1835e99cf0da5a1df3629009cc0ef21cfec4 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 17:06:17 +0300 Subject: [PATCH 10/86] cleaner promise_interval --- .../promise_interval/promise_interval.js | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/services/promise_interval/promise_interval.js b/src/services/promise_interval/promise_interval.js index 46ac68996..c77768b4f 100644 --- a/src/services/promise_interval/promise_interval.js +++ b/src/services/promise_interval/promise_interval.js @@ -4,32 +4,38 @@ // time after the first interval. // - interval is the interval delay in ms. +const wait = (timeout) => { + let timeoutId + const promise = () => new Promise((resolve) => { + timeoutId = window.setTimeout(() => resolve(), timeout) + }) + return { timeoutId, promise } +} + export const promiseInterval = (promiseCall, interval) => { let stopped = false let timeout = null - const func = () => { - const promise = promiseCall() - // something unexpected happened and promiseCall did not - // return a promise, abort the loop. - if (!(promise && promise.finally)) { - console.warn( - 'promiseInterval: promise call did not return a promise, stopping interval.', - ) - return - } - promise.finally(() => { - if (stopped) return - timeout = window.setTimeout(func, interval) - }) - } - const stopFetcher = () => { stopped = true window.clearTimeout(timeout) } - timeout = window.setTimeout(func, interval) + const loop = new Promise(async (resolve, reject) => { + try { + while (!stopped) { + await promiseCall() + const { timeoutId, promise } = wait(interval) + timeout = timeoutId + await promise() + } + resolve() + } catch (e) { + reject(e) + } + }) + + loop.then() return { stop: stopFetcher } } From 529a2d100bbf5f7eb1dd5def3905e9a07a2c4cac Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 17:08:22 +0300 Subject: [PATCH 11/86] fix announcements --- src/stores/announcements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/announcements.js b/src/stores/announcements.js index a40d4d1e7..4f11e0f6a 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -46,7 +46,7 @@ export const useAnnouncementsStore = defineStore('announcements', { const fetchAnnouncements = async () => { if (!isAdmin) { - return fetchAnnouncements({ + return getAnnouncements({ credentials: useCredentialsStore().current, }) } From 3984a5aefa7fb8df6ee1d8ae95ab1b9bbddae424 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 17:32:26 +0300 Subject: [PATCH 12/86] use OAuthStore directly --- src/boot/after_store.js | 15 +--- .../bookmark_folder_edit.js | 4 +- src/components/chat/chat.js | 8 +- src/components/chat_new/chat_new.js | 4 +- src/components/conversation/conversation.js | 7 +- .../follow_request_card.js | 6 +- src/components/notification/notification.js | 6 +- .../remote_user_resolver.js | 4 +- .../helpers/emoji_editing_popover.vue | 8 +- .../settings_modal/tabs/appearance_tab.js | 4 +- .../settings_modal/tabs/composing_tab.js | 4 +- .../tabs/data_import_export_tab.js | 18 ++--- .../settings_modal/tabs/general_tab.js | 4 +- .../tabs/mutes_and_blocks_tab.js | 6 +- .../settings_modal/tabs/notifications_tab.js | 4 +- .../settings_modal/tabs/profile_tab.js | 4 +- .../settings_modal/tabs/security_tab/mfa.js | 10 +-- .../tabs/security_tab/mfa_totp.js | 4 +- .../tabs/security_tab/security_tab.js | 16 ++-- src/components/user_card/user_card.js | 2 +- .../user_reporting_modal.js | 5 +- src/components/who_to_follow/who_to_follow.js | 6 +- .../who_to_follow_panel.js | 4 +- src/modules/api.js | 12 +-- src/modules/chats.js | 8 +- src/modules/notifications.js | 4 +- src/modules/profileConfig.js | 6 +- src/modules/statuses.js | 42 +++++----- src/modules/users.js | 45 +++++------ src/services/api/admin.js | 3 +- .../follow_manipulate/follow_manipulate.js | 8 +- .../promise_interval/promise_interval.js | 7 +- src/stores/admin_settings.js | 78 +++++++++---------- src/stores/announcements.js | 16 ++-- src/stores/bookmark_folders.js | 10 +-- src/stores/credentials.js | 19 ----- src/stores/emoji.js | 20 ++--- src/stores/lists.js | 22 +++--- src/stores/oauth.js | 5 +- src/stores/oauth_tokens.js | 6 +- src/stores/polls.js | 6 +- src/stores/reports.js | 4 +- src/stores/sync_config.js | 4 +- src/stores/user_highlight.js | 4 +- 44 files changed, 226 insertions(+), 256 deletions(-) delete mode 100644 src/stores/credentials.js diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 2bab043ad..895fe910c 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -30,7 +30,6 @@ import routes from './routes' import { useAnnouncementsStore } from 'src/stores/announcements' import { useAuthFlowStore } from 'src/stores/auth_flow' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useEmojiStore } from 'src/stores/emoji.js' import { useI18nStore } from 'src/stores/i18n' import { useInstanceStore } from 'src/stores/instance.js' @@ -38,7 +37,7 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j import { useInterfaceStore } from 'src/stores/interface.js' import { useLocalConfigStore } from 'src/stores/local_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useOAuthStore } from 'src/stores/oauth' +import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' @@ -261,13 +260,6 @@ const getStickers = async ({ store }) => { } } -const getAppSecret = async ({ store }) => { - const oauth = useOAuthStore() - if (oauth.userToken) { - useCredentialsStore().setCredentials(oauth.getToken) - } -} - const resolveStaffAccounts = ({ store, accounts }) => { const nicknames = accounts.map((uri) => uri.split('/').pop()) useInstanceStore().set({ @@ -458,14 +450,13 @@ const setConfig = async ({ store }) => { const apiConfig = configInfos[0] const staticConfig = configInfos[1] - getAppSecret({ store }) await setSettings({ store, apiConfig, staticConfig }) } const checkOAuthToken = async ({ store }) => { const oauth = useOAuthStore() - if (oauth.getUserToken) { - return store.dispatch('loginUser', oauth.getUserToken) + if (oauth.userToken) { + return store.dispatch('loginUser', oauth.userToken) } return Promise.resolve() } diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.js b/src/components/bookmark_folder_edit/bookmark_folder_edit.js index a83825d0b..bc30790f6 100644 --- a/src/components/bookmark_folder_edit/bookmark_folder_edit.js +++ b/src/components/bookmark_folder_edit/bookmark_folder_edit.js @@ -1,8 +1,8 @@ import EmojiPicker from 'src/components/emoji_picker/emoji_picker.vue' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInterfaceStore } from 'src/stores/interface.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { fetchBookmarkFolders } from 'src/services/api/api.service.js' @@ -26,7 +26,7 @@ const BookmarkFolderEdit = { if (!this.id) return fetchBookmarkFolders({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((folders) => { const folder = folders.find((folder) => folder.id === this.id) if (!folder) return diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index 8bcc57d76..bd71c2b5d 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -16,8 +16,8 @@ import { isScrollable, } from './chat_layout_utils.js' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInterfaceStore } from 'src/stores/interface.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { chatMessages, @@ -277,7 +277,7 @@ const Chat = { id: chatId, maxId, sinceId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((messages) => { // Clear the current chat in case we're recovering from a ws connection loss. if (isFirstFetch) { @@ -312,7 +312,7 @@ const Chat = { try { chat = await getOrCreateChat({ accountId: this.recipientId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) } catch (e) { console.error('Error creating or getting a chat', e) @@ -381,7 +381,7 @@ const Chat = { sendChatMessage({ params, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then((data) => { this.$store.dispatch('addChatMessages', { diff --git a/src/components/chat_new/chat_new.js b/src/components/chat_new/chat_new.js index 2861e23c3..7b08d3163 100644 --- a/src/components/chat_new/chat_new.js +++ b/src/components/chat_new/chat_new.js @@ -3,7 +3,7 @@ import { mapGetters, mapState } from 'vuex' import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue' import UserAvatar from 'src/components/user_avatar/user_avatar.vue' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { chats } from 'src/services/api/api.service.js' @@ -27,7 +27,7 @@ const chatNew = { }, async created() { const { chats } = await chats({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) chats.forEach((chat) => this.suggestions.push(chat.account)) }, diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 0e3a3f729..6bc0b7ad0 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -7,9 +7,9 @@ import QuickViewSettings from 'src/components/quick_view_settings/quick_view_set import ThreadTree from 'src/components/thread_tree/thread_tree.vue' import { WSConnectionStatus } from '../../services/api/api.service.js' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInterfaceStore } from 'src/stores/interface' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { fetchConversation, fetchStatus } from 'src/services/api/api.service.js' @@ -441,7 +441,7 @@ const conversation = { if (this.status) { fetchConversation({ id: this.statusId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(({ ancestors, descendants }) => { this.$store.dispatch('addNewStatuses', { statuses: ancestors }) this.$store.dispatch('addNewStatuses', { statuses: descendants }) @@ -451,13 +451,14 @@ const conversation = { this.loadStatusError = null fetchStatus({ id: this.statusId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then((status) => { this.$store.dispatch('addNewStatuses', { statuses: [status] }) this.fetchConversation() }) .catch((error) => { + console.error(error) this.loadStatusError = error }) } diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js index 7e983f60b..178f36894 100644 --- a/src/components/follow_request_card/follow_request_card.js +++ b/src/components/follow_request_card/follow_request_card.js @@ -3,8 +3,8 @@ import { defineAsyncComponent } from 'vue' import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js' import BasicUserCard from '../basic_user_card/basic_user_card.vue' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { approveUser, denyUser } from 'src/services/api/api.service.js' @@ -53,7 +53,7 @@ const FollowRequestCard = { doApprove() { approveUser({ id: this.user.id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) this.$store.dispatch('removeFollowRequest', this.user) @@ -79,7 +79,7 @@ const FollowRequestCard = { denyUser({ id: this.user.id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(() => { this.$store.dispatch('dismissNotificationLocal', { id: notifId }) this.$store.dispatch('removeFollowRequest', this.user) diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index c651d667e..8fc77d91d 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -13,9 +13,9 @@ import { highlightStyle, } from '../../services/user_highlighter/user_highlighter.js' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' import { approveUser, denyUser } from 'src/services/api/api.service.js' @@ -146,7 +146,7 @@ const Notification = { doApprove() { approveUser({ id: this.user.id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('markSingleNotificationAsSeen', { @@ -170,7 +170,7 @@ const Notification = { doDeny() { denyUser({ id: this.user.id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(() => { this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id, diff --git a/src/components/remote_user_resolver/remote_user_resolver.js b/src/components/remote_user_resolver/remote_user_resolver.js index 3f3b72ba9..bdef5b2a4 100644 --- a/src/components/remote_user_resolver/remote_user_resolver.js +++ b/src/components/remote_user_resolver/remote_user_resolver.js @@ -1,4 +1,4 @@ -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { fetchUser } from 'src/services/api/api.service.js' @@ -14,7 +14,7 @@ const RemoteUserResolver = { const id = this.$route.params.username + '@' + this.$route.params.hostname fetchUser({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then((externalUser) => { if (externalUser.error) { diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue index 102d231ba..6cee8cebe 100644 --- a/src/components/settings_modal/helpers/emoji_editing_popover.vue +++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue @@ -153,7 +153,7 @@ import Popover from 'components/popover/popover.vue' import SelectComponent from 'components/select/select.vue' import { defineAsyncComponent } from 'vue' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { addNewEmojiFile, @@ -257,7 +257,7 @@ export default { newShortcode: this.editedShortcode, newFilename: this.editedFile, force: false, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then((resp) => { if (resp.error !== undefined) { @@ -281,7 +281,7 @@ export default { : this.emojiAddr(this.file), shortcode: this.editedShortcode, filename: this.editedFile, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then((resp) => resp.json()) .then((resp) => { @@ -308,7 +308,7 @@ export default { deleteEmojiFile({ packName: this.packName, shortcode: this.shortcode, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then((resp) => resp.json()) .then((resp) => { diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js index 595153e61..77686b736 100644 --- a/src/components/settings_modal/tabs/appearance_tab.js +++ b/src/components/settings_modal/tabs/appearance_tab.js @@ -10,9 +10,9 @@ import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' import Preview from './old_theme_tab/theme_preview.vue' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { normalizeThemeData, useInterfaceStore } from 'src/stores/interface.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { updateProfileImages } from 'src/services/api/api.service.js' import { newImporter } from 'src/services/export_import/export_import.js' @@ -488,7 +488,7 @@ const AppearanceTab = { this.backgroundUploading = true updateProfileImages({ background, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then((data) => { this.$store.commit('addNewUsers', [data]) diff --git a/src/components/settings_modal/tabs/composing_tab.js b/src/components/settings_modal/tabs/composing_tab.js index cf7aa7e3e..90d80cbf6 100644 --- a/src/components/settings_modal/tabs/composing_tab.js +++ b/src/components/settings_modal/tabs/composing_tab.js @@ -11,10 +11,10 @@ import IntegerSetting from '../helpers/integer_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { updateProfile } from 'src/services/api/api.service.js' @@ -168,7 +168,7 @@ const ComposingTab = { updateProfile({ params, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((user) => { this.$store.commit('addNewUsers', [user]) this.$store.commit('setCurrentUser', user) diff --git a/src/components/settings_modal/tabs/data_import_export_tab.js b/src/components/settings_modal/tabs/data_import_export_tab.js index 630cd85e6..05af7d27c 100644 --- a/src/components/settings_modal/tabs/data_import_export_tab.js +++ b/src/components/settings_modal/tabs/data_import_export_tab.js @@ -4,7 +4,7 @@ import Checkbox from 'src/components/checkbox/checkbox.vue' import Exporter from 'src/components/exporter/exporter.vue' import Importer from 'src/components/importer/importer.vue' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthTokensStore } from 'src/stores/oauth_tokens.js' import { @@ -47,23 +47,23 @@ const DataImportExportTab = { getFollowsContent() { return exportFriends({ id: this.user.id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(this.generateExportableUsersContent) }, getBlocksContent() { return fetchBlocks({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(this.generateExportableUsersContent) }, getMutesContent() { return fetchMutes({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(this.generateExportableUsersContent) }, importFollows(file) { return importFollows({ file, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => { if (!status) { throw new Error('failed') @@ -73,7 +73,7 @@ const DataImportExportTab = { importBlocks(file) { return importBlocks({ file, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => { if (!status) { throw new Error('failed') @@ -83,7 +83,7 @@ const DataImportExportTab = { importMutes(file) { return importMutes({ file, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => { if (!status) { throw new Error('failed') @@ -105,7 +105,7 @@ const DataImportExportTab = { }, addBackup() { addBackup({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then(() => { this.addedBackup = true @@ -119,7 +119,7 @@ const DataImportExportTab = { }, fetchBackups() { listBackups({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then((res) => { this.backups = res diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index 207fda663..893833866 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -8,11 +8,11 @@ import FloatSetting from '../helpers/float_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useLocalConfigStore } from 'src/stores/local_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { updateProfile } from 'src/services/api/api.service.js' @@ -62,7 +62,7 @@ const GeneralTab = { updateProfile({ params, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((user) => { this.$store.commit('addNewUsers', [user]) this.$store.commit('setCurrentUser', user) diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js index 43af4a048..d1f5bff12 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js @@ -9,8 +9,8 @@ import MuteCard from 'src/components/mute_card/mute_card.vue' import ProgressButton from 'src/components/progress_button/progress_button.vue' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthTokensStore } from 'src/stores/oauth_tokens.js' import { importBlocks, importFollows } from 'src/services/api/api.service.js' @@ -59,7 +59,7 @@ const MutesAndBlocks = { importFollows(file) { return importFollows({ file, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => { if (!status) { throw new Error('failed') @@ -69,7 +69,7 @@ const MutesAndBlocks = { importBlocks(file) { return importBlocks({ file, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => { if (!status) { throw new Error('failed') diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js index f3328a20a..6ef8e0ab7 100644 --- a/src/components/settings_modal/tabs/notifications_tab.js +++ b/src/components/settings_modal/tabs/notifications_tab.js @@ -1,7 +1,7 @@ import BooleanSetting from '../helpers/boolean_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { updateNotificationSettings } from 'src/services/api/api.service.js' @@ -32,7 +32,7 @@ const NotificationsTab = { methods: { updateNotificationSettings() { updateNotificationSettings({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, settings: this.notificationSettings, }) }, diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index afdeffb34..586ca30a8 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -3,7 +3,7 @@ import UserCard from 'src/components/user_card/user_card.vue' import BooleanSetting from '../helpers/boolean_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { updateProfile } from 'src/services/api/api.service.js' @@ -41,7 +41,7 @@ const ProfileTab = { } updateProfile({ params, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then((user) => { this.$store.commit('addNewUsers', [user]) diff --git a/src/components/settings_modal/tabs/security_tab/mfa.js b/src/components/settings_modal/tabs/security_tab/mfa.js index f8d0d8e3b..bba2a2ff9 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa.js +++ b/src/components/settings_modal/tabs/security_tab/mfa.js @@ -5,7 +5,7 @@ import Confirm from './confirm.vue' import RecoveryCodes from './mfa_backup_codes.vue' import TOTP from './mfa_totp.vue' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { generateMfaBackupCodes, @@ -94,7 +94,7 @@ const Mfa = { this.backupCodes.codes = [] return generateMfaBackupCodes({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((res) => { this.backupCodes.codes = res.codes this.backupCodes.inProgress = false @@ -121,7 +121,7 @@ const Mfa = { this.setupState.state = 'setupOTP' this.setupState.setupOTPState = 'prepare' mfaSetupOTP({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((res) => { this.otpSettings = res this.setupState.setupOTPState = 'confirm' @@ -133,7 +133,7 @@ const Mfa = { mfaConfirmOTP({ token: this.otpConfirmToken, password: this.currentPassword, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((res) => { if (res.error) { this.error = res.error @@ -162,7 +162,7 @@ const Mfa = { // fetch settings from server async fetchSettings() { const result = await settingsMFA({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) if (result.error) return this.settings = result.settings diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.js b/src/components/settings_modal/tabs/security_tab/mfa_totp.js index c03168d5b..b7a1426ab 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa_totp.js +++ b/src/components/settings_modal/tabs/security_tab/mfa_totp.js @@ -2,7 +2,7 @@ import { mapState } from 'vuex' import Confirm from './confirm.vue' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { mfaDisableOTP } from 'src/services/api/api.service.js' @@ -39,7 +39,7 @@ export default { this.inProgress = true mfaDisableOTP({ password: this.currentPassword, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((res) => { this.inProgress = false if (res.error) { diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js index 272cdecff..ca959ded5 100644 --- a/src/components/settings_modal/tabs/security_tab/security_tab.js +++ b/src/components/settings_modal/tabs/security_tab/security_tab.js @@ -2,9 +2,9 @@ import Checkbox from 'src/components/checkbox/checkbox.vue' import ProgressButton from 'src/components/progress_button/progress_button.vue' import Mfa from './mfa.vue' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthTokensStore } from 'src/stores/oauth_tokens' import { @@ -76,7 +76,7 @@ const SecurityTab = { }, deleteAccount() { deleteAccount({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, password: this.deleteAccountConfirmPasswordInput, }).then((res) => { if (res.status === 'success') { @@ -92,7 +92,7 @@ const SecurityTab = { password: this.changePasswordInputs[0], newPassword: this.changePasswordInputs[1], newPasswordConfirmation: this.changePasswordInputs[2], - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, } changePassword(params).then((res) => { if (res.status === 'success') { @@ -109,7 +109,7 @@ const SecurityTab = { const params = { email: this.newEmail, password: this.changeEmailPassword, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, } changeEmail(params).then((res) => { if (res.status === 'success') { @@ -125,7 +125,7 @@ const SecurityTab = { const params = { targetAccount: this.moveAccountTarget, password: this.moveAccountPassword, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, } moveAccount(params).then((res) => { if (res.status === 'success') { @@ -140,13 +140,13 @@ const SecurityTab = { removeAlias(alias) { deleteAlias({ alias, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(() => this.fetchAliases()) }, addAlias() { addAlias({ alias: this.addAliasTarget, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then(() => { this.addedAlias = true @@ -161,7 +161,7 @@ const SecurityTab = { }, fetchAliases() { listAliases({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then((res) => { this.aliases = res.aliases diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index b026c2347..879bb225a 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -16,13 +16,13 @@ import Select from 'src/components/select/select.vue' import UserAvatar from 'src/components/user_avatar/user_avatar.vue' import UserLink from 'src/components/user_link/user_link.vue' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useEmojiStore } from 'src/stores/emoji.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface' import { useMediaViewerStore } from 'src/stores/media_viewer' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { usePostStatusStore } from 'src/stores/post_status' import { useUserHighlightStore } from 'src/stores/user_highlight.js' diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js index de0454240..49e569f23 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.js +++ b/src/components/user_reporting_modal/user_reporting_modal.js @@ -5,7 +5,7 @@ import List from 'src/components/list/list.vue' import Modal from 'src/components/modal/modal.vue' import UserLink from 'src/components/user_link/user_link.vue' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { useReportsStore } from 'src/stores/reports.js' import { reportUser } from 'src/services/api/api.service.js' @@ -31,6 +31,7 @@ const UserReportingModal = { return !!this.$store.state.users.currentUser }, isOpen() { + console.log(this.reportModal) return this.isLoggedIn && this.reportModal.activated }, userId() { @@ -73,7 +74,7 @@ const UserReportingModal = { comment: this.comment, forward: this.forward, statusIds: [...this.statusIdsToReport], - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, } reportUser({ ...params }) .then(() => { diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js index a6edbf508..be4644424 100644 --- a/src/components/who_to_follow/who_to_follow.js +++ b/src/components/who_to_follow/who_to_follow.js @@ -1,8 +1,8 @@ import FollowCard from 'src/components/follow_card/follow_card.vue' import apiService from '../../services/api/api.service.js' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { fetchUser, suggestions } from 'src/services/api/api.service.js' @@ -23,7 +23,7 @@ const WhoToFollow = { reply.forEach(({ id }) => { fetchUser({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((externalUser) => { if (!externalUser.error) { this.$store.commit('addNewUsers', [externalUser]) @@ -33,7 +33,7 @@ const WhoToFollow = { }) }, getWhoToFollow() { - const credentials = useCredentialsStore().current + const credentials = useOAuthStore().token if (credentials) { suggestions({ credentials }).then((reply) => { this.showWhoToFollow(reply) diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js index c6de34af3..fa1c12278 100644 --- a/src/components/who_to_follow_panel/who_to_follow_panel.js +++ b/src/components/who_to_follow_panel/who_to_follow_panel.js @@ -1,8 +1,8 @@ import { shuffle } from 'lodash' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { fetchUser, suggestions } from 'src/services/api/api.service.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -20,7 +20,7 @@ function showWhoToFollow(panel, reply) { fetchUser({ id: name, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((externalUser) => { if (!externalUser.error) { panel.$store.commit('addNewUsers', [externalUser]) diff --git a/src/modules/api.js b/src/modules/api.js index bc0844f61..c7de7a070 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -3,10 +3,10 @@ import { Socket } from 'phoenix' import { WSConnectionStatus } from '../services/api/api.service.js' import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { useShoutStore } from 'src/stores/shout.js' import { @@ -98,7 +98,7 @@ const api = { const timelineData = rootState.statuses.timelines.friends const serv = useInstanceStore().server.replace('http', 'ws') - const credentials = useCredentialsStore().current + const credentials = useOAuthStore().token const url = getMastodonSocketURI({ credentials }, serv) state.mastoUserSocket = ProcessedWS({ @@ -266,7 +266,7 @@ const api = { statusId, bookmarkFolderId, tag, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) store.commit('addFetcher', { fetcherName: timeline, fetcher }) @@ -282,7 +282,7 @@ const api = { store, timeline, ...rest, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) }, @@ -291,7 +291,7 @@ const api = { if (store.state.fetchers.notifications) return const fetcher = notificationsFetcher.startFetching({ store, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) store.commit('addFetcher', { fetcherName: 'notifications', fetcher }) }, @@ -306,7 +306,7 @@ const api = { if (store.state.fetchers.followRequests) return const fetcher = followRequestFetcher.startFetchingFollowRequests({ store, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) store.commit('addFetcher', { fetcherName: 'followRequests', fetcher }) diff --git a/src/modules/chats.js b/src/modules/chats.js index 92d951be4..4ad82eccd 100644 --- a/src/modules/chats.js +++ b/src/modules/chats.js @@ -9,7 +9,7 @@ import { } from '../services/entity_normalizer/entity_normalizer.service.js' import { promiseInterval } from '../services/promise_interval/promise_interval.js' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { chats, @@ -68,7 +68,7 @@ const chatsModule = { }, fetchChats({ dispatch, rootState }) { return chats({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(({ chatList }) => { dispatch('addNewChats', { chats: chatList }) return chats @@ -125,14 +125,14 @@ const chatsModule = { readChat({ id, lastReadId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) } }, deleteChatMessage({ rootState, commit }, value) { deleteChatMessage({ ...value, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) commit('deleteChatMessage', { commit, ...value }) }, diff --git a/src/modules/notifications.js b/src/modules/notifications.js index 2e55477b7..3d5f787cf 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -9,9 +9,9 @@ import { maybeShowNotification, } from '../services/notification_utils/notification_utils.js' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useI18nStore } from 'src/stores/i18n.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { useReportsStore } from 'src/stores/reports.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' @@ -181,7 +181,7 @@ export const notifications = { commit('dismissNotification', { id }) dismissNotification({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) }, updateNotification({ commit }, { id, updater }) { diff --git a/src/modules/profileConfig.js b/src/modules/profileConfig.js index c6374e142..0f125e976 100644 --- a/src/modules/profileConfig.js +++ b/src/modules/profileConfig.js @@ -1,6 +1,6 @@ import { get, set } from 'lodash' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { updateNotificationSettings, @@ -12,7 +12,7 @@ const defaultApi = ({ rootState, commit }, { path, value }) => { set(params, path, value) return updateProfile({ params, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((result) => { commit('addNewUsers', [result]) commit('setCurrentUser', result) @@ -24,7 +24,7 @@ const notificationsApi = ({ rootState, commit }, { path, value, oldValue }) => { set(settings, path, value) return updateNotificationSettings({ settings, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((result) => { if (result.status === 'success') { commit('confirmProfileOption', { name, value }) diff --git a/src/modules/statuses.js b/src/modules/statuses.js index ff81a12ba..ef6aedcbc 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -38,9 +38,9 @@ import { unretweet, } from '../services/api/api.service.js' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' +import { useOAuthStore } from 'src/stores/oauth.js' const emptyTl = (userId = 0) => ({ statuses: [], @@ -632,7 +632,7 @@ const statuses = { fetchStatusSource({ rootState }, status) { return fetchStatusSource({ id: status.id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) }, fetchStatusHistory(_, status) { @@ -641,7 +641,7 @@ const statuses = { deleteStatus({ rootState, commit }, status) { deleteStatus({ id: status.id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then(() => { commit('setDeleted', { status }) @@ -667,7 +667,7 @@ const statuses = { commit('setFavorited', { status, value: true }) favorite({ id: status.id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => commit('setFavoritedConfirm', { status, @@ -680,7 +680,7 @@ const statuses = { commit('setFavorited', { status, value: false }) unfavorite({ id: status.id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => commit('setFavoritedConfirm', { status, @@ -691,7 +691,7 @@ const statuses = { fetchPinnedStatuses({ rootState, dispatch }, userId) { fetchPinnedStatuses({ id: userId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((statuses) => dispatch('addNewStatuses', { statuses, @@ -705,25 +705,25 @@ const statuses = { pinStatus({ rootState, dispatch }, statusId) { return pinOwnStatus({ id: statusId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => dispatch('addNewStatuses', { statuses: [status] })) }, unpinStatus({ rootState, dispatch }, statusId) { return unpinOwnStatus({ id: statusId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => dispatch('addNewStatuses', { statuses: [status] })) }, muteConversation({ rootState, commit }, { id: statusId }) { return muteConversation({ id: statusId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => commit('setMutedStatus', status)) }, unmuteConversation({ rootState, commit }, { id: statusId }) { return unmuteConversation({ id: statusId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => commit('setMutedStatus', status)) }, retweet({ rootState, commit }, status) { @@ -731,7 +731,7 @@ const statuses = { commit('setRetweeted', { status, value: true }) retweet({ id: status.id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => commit('setRetweetedConfirm', { status: status.retweeted_status, @@ -744,7 +744,7 @@ const statuses = { commit('setRetweeted', { status, value: false }) unretweet({ id: status.id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => commit('setRetweetedConfirm', { status, @@ -757,7 +757,7 @@ const statuses = { bookmarkStatus({ id: status.id, folder_id: status.bookmark_folder_id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => { commit('setBookmarkedConfirm', { status }) }) @@ -766,7 +766,7 @@ const statuses = { commit('setBookmarked', { status, value: false }) unbookmarkStatus({ id: status.id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((status) => { commit('setBookmarkedConfirm', { status }) }) @@ -781,11 +781,11 @@ const statuses = { Promise.all([ fetchFavoritedByUsers({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }), fetchRebloggedByUsers({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }), ]).then(([favoritedByUsers, rebloggedByUsers]) => { commit('addFavs', { @@ -808,7 +808,7 @@ const statuses = { reactWithEmoji({ id, emoji, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(() => { dispatch('fetchEmojiReactionsBy', id) }) @@ -829,7 +829,7 @@ const statuses = { fetchEmojiReactionsBy({ rootState, commit }, id) { return fetchEmojiReactions({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((emojiReactions) => { commit('addEmojiReactionsBy', { id, @@ -841,7 +841,7 @@ const statuses = { fetchFavs({ rootState, commit }, id) { fetchFavoritedByUsers({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((favoritedByUsers) => commit('addFavs', { id, @@ -853,7 +853,7 @@ const statuses = { fetchRepeats({ rootState, commit }, id) { fetchRebloggedByUsers({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((rebloggedByUsers) => commit('addRepeats', { id, @@ -870,7 +870,7 @@ const statuses = { offset, following, type, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((data) => { store.commit('addNewUsers', data.accounts) store.commit( diff --git a/src/modules/users.js b/src/modules/users.js index c7f457a47..4a4614e4d 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -21,7 +21,6 @@ import { } from '../services/window_utils/window_utils' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useEmojiStore } from 'src/stores/emoji.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' @@ -133,7 +132,7 @@ const localMuteUser = (store, args) => { return muteUser({ id, expiresIn, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((relationship) => { store.commit('updateUserRelationship', [relationship]) store.commit('addMuteId', id) @@ -154,7 +153,7 @@ const hideReblogs = (store, userId) => { return followUser({ id: userId, reblogs: false, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((relationship) => { store.commit('updateUserRelationship', [relationship]) }) @@ -164,7 +163,7 @@ const showReblogs = (store, userId) => { return followUser({ id: userId, reblogs: true, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((relationship) => store.commit('updateUserRelationship', [relationship]), ) @@ -173,14 +172,14 @@ const showReblogs = (store, userId) => { const muteDomain = (store, domain) => { return muteDomain({ domain, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(() => store.commit('addDomainMute', domain)) } const unmuteDomain = (store, domain) => { return unmuteDomain({ domain, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(() => store.commit('removeDomainMute', domain)) } @@ -404,7 +403,7 @@ const users = { fetchUser(store, id) { return fetchUser({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((user) => { store.commit('addNewUsers', [user]) return user @@ -413,7 +412,7 @@ const users = { fetchUserByName(store, name) { return fetchUserByName({ name, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((user) => { store.commit('addNewUsers', [user]) return user @@ -423,7 +422,7 @@ const users = { if (store.state.currentUser) { fetchUserRelationship({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((relationships) => store.commit('updateUserRelationship', relationships), ) @@ -433,7 +432,7 @@ const users = { if (store.state.currentUser) { fetchUserInLists({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((inLists) => store.commit('updateUserInLists', { id, inLists })) } }, @@ -443,7 +442,7 @@ const users = { const maxId = store.state.currentUser.blockIdsMaxId return fetchBlocks({ maxId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((blocks) => { if (reset) { store.commit('saveBlockIds', map(blocks, 'id')) @@ -481,7 +480,7 @@ const users = { const maxId = store.state.currentUser.muteIdsMaxId return fetchMutes({ maxId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((mutes) => { if (reset) { store.commit('saveMuteIds', map(mutes, 'id')) @@ -515,7 +514,7 @@ const users = { }, fetchDomainMutes(store) { return fetchDomainMutes({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((domainMutes) => { store.commit('saveDomainMutes', domainMutes) return domainMutes @@ -539,7 +538,7 @@ const users = { return fetchFriends({ id, maxId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((friends) => { commit('addNewUsers', friends) commit('saveFriendIds', { id, friendIds: map(friends, 'id') }) @@ -552,7 +551,7 @@ const users = { return fetchFollowers({ id, maxId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((followers) => { commit('addNewUsers', followers) commit('saveFollowerIds', { id, followerIds: map(followers, 'id') }) @@ -569,7 +568,7 @@ const users = { return followUser({ id, notify: true, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((relationship) => commit('updateUserRelationship', [relationship]), ) @@ -578,7 +577,7 @@ const users = { return followUser({ id, notify: false, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((relationship) => commit('updateUserRelationship', [relationship]), ) @@ -644,7 +643,7 @@ const users = { searchUsers({ rootState, commit }, { query }) { return searchUsers({ query, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((users) => { commit('addNewUsers', users) return users @@ -679,7 +678,7 @@ const users = { }, getCaptcha(store) { return getCaptcha({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) }, @@ -704,7 +703,6 @@ const users = { store.dispatch('disconnectFromSocket') oauth.clearToken() store.dispatch('stopFetchingTimeline', 'friends') - useCredentialsStore().setCredentials(null) store.dispatch('stopFetchingNotifications') useListsStore().stopFetching() useBookmarkFoldersStore().stopFetching() @@ -721,9 +719,11 @@ const users = { return new Promise((resolve, reject) => { const commit = store.commit const dispatch = store.dispatch + commit('beginLogin') + verifyCredentials({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then((data) => { if (!data.error) { @@ -753,9 +753,6 @@ const users = { useInterfaceStore().setNotificationPermission(permission), ) - // Update credentials - useCredentialsStore().setCredentials(accessToken) - // Do server-side storage migrations // Debug snippet to clean up storage and reset migrations diff --git a/src/services/api/admin.js b/src/services/api/admin.js index 64a88ca9b..975a7386e 100644 --- a/src/services/api/admin.js +++ b/src/services/api/admin.js @@ -6,7 +6,8 @@ const REPORTS = '/api/v1/pleroma/admin/reports' const CONFIG_URL = '/api/v1/pleroma/admin/config' const DESCRIPTIONS_URL = '/api/v1/pleroma/admin/config/descriptions' -const ANNOUNCEMENTS_URL = (id = '') => `/api/v1/pleroma/admin/announcements/${id}` +const ANNOUNCEMENTS_URL = (id = '') => + `/api/v1/pleroma/admin/announcements/${id}` const FRONTENDS_URL = '/api/v1/pleroma/admin/frontends' const FRONTENDS_INSTALL_URL = '/api/v1/pleroma/admin/frontends/install' diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index 8daee3bc3..c754c7316 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -1,4 +1,4 @@ -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { fetchUserRelationship, @@ -11,7 +11,7 @@ const fetchRelationship = (attempt, userId, store) => setTimeout(() => { fetchUserRelationship({ id: userId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then((relationship) => { store.commit('updateUserRelationship', [relationship]) @@ -39,7 +39,7 @@ export const requestFollow = (userId, store) => new Promise((resolve) => { followUser({ id: userId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((updated) => { store.commit('updateUserRelationship', [updated]) @@ -66,7 +66,7 @@ export const requestUnfollow = (userId, store) => new Promise((resolve) => { unfollowUser({ id: userId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((updated) => { store.commit('updateUserRelationship', [updated]) resolve({ diff --git a/src/services/promise_interval/promise_interval.js b/src/services/promise_interval/promise_interval.js index c77768b4f..b28b7dc5a 100644 --- a/src/services/promise_interval/promise_interval.js +++ b/src/services/promise_interval/promise_interval.js @@ -6,9 +6,10 @@ const wait = (timeout) => { let timeoutId - const promise = () => new Promise((resolve) => { - timeoutId = window.setTimeout(() => resolve(), timeout) - }) + const promise = () => + new Promise((resolve) => { + timeoutId = window.setTimeout(() => resolve(), timeout) + }) return { timeoutId, promise } } diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index 9440d17f8..884ac37a1 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -1,7 +1,7 @@ import { cloneDeep, differenceWith, flatten, get, isEqual, set } from 'lodash' import { defineStore } from 'pinia' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { addNewEmojiFile, @@ -86,7 +86,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { loadAdminStuff() { getInstanceDBConfig({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((backendDbConfig) => { if (backendDbConfig.error) { if (backendDbConfig.error.status === 400) { @@ -98,17 +98,17 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } } else { this.setInstanceAdminSettings({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, backendDbConfig, }) } }) if (this.descriptions === null) { getInstanceConfigDescriptions({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((backendDescriptions) => this.setInstanceAdminDescriptions({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, backendDescriptions, }), ) @@ -243,19 +243,19 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) pushInstanceDBConfig({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, payload: { configs: changed, }, }) .then(() => getInstanceDBConfig({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }), ) .then((backendDbConfig) => this.setInstanceAdminSettings({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, backendDbConfig, }), @@ -280,7 +280,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } pushInstanceDBConfig({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, payload: { configs: [ { @@ -293,12 +293,12 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) .then(() => getInstanceDBConfig({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }), ) .then((backendDbConfig) => this.setInstanceAdminSettings({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, backendDbConfig, }), ) @@ -311,7 +311,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { this.modifiedPaths.delete(path) return pushInstanceDBConfig({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, payload: { configs: [ { @@ -325,7 +325,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) .then(() => getInstanceDBConfig({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }), ) .then((backendDbConfig) => @@ -336,7 +336,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { // Frontends Stuff loadFrontendsStuff() { getAvailableFrontends({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((frontends) => this.setAvailableFrontends({ frontends })) }, @@ -354,14 +354,14 @@ export const useAdminSettingsStore = defineStore('adminSettings', { installFrontend() { return installFrontend({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) }, // Statuses stuff async fetchStatuses(opts) { const { total, activities } = await listStatuses({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, opts, }) @@ -376,7 +376,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async changeStatusScope(opts) { const raw = await changeStatusScope({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, opts, }) const status = parseStatus(raw) @@ -387,7 +387,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { // Users stuff async fetchUsers(opts) { const { users, count } = await listUsers({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, opts, }) @@ -409,7 +409,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { const { screen_name } = user const result = await api({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, screen_name, }) window.vuex.commit('updateUserAdminData', { user: result }) @@ -419,7 +419,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { const api = deleteAccounts const resultUserIds = await api({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, screen_names, }) @@ -437,7 +437,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { const screen_names = users.map((u) => u.screen_name) return resendConfirmationEmail({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, screen_names, }) }, @@ -445,7 +445,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { const screen_names = users.map((u) => u.screen_name) return requirePasswordChange({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, screen_names, }) }, @@ -454,7 +454,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { const { screen_name } = user return disableMFA({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, screen_name, }) }, @@ -463,7 +463,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { const api = setUsersTags await api({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, screen_names, tags, value, @@ -478,7 +478,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { const api = setUsersRight await api({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, screen_names, right, value, @@ -493,7 +493,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { const api = setUsersActivationStatus const resultUsers = await api({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, screen_names, value, }) @@ -507,7 +507,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { const api = setUsersSuggestionStatus const resultUsers = await api({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, screen_names, value, }) @@ -521,7 +521,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { const api = setUsersConfirmationStatus await api({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, screen_names, }) @@ -534,7 +534,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { const api = setUsersApprovalStatus const resultUsers = await api({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, screen_names, }) @@ -543,21 +543,21 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) }, reloadEmoji() { - return reloadEmoji({ credentials: useCredentialsStore().current }) + return reloadEmoji({ credentials: useOAuthStore().token }) }, importEmojiFromFS() { - return importEmojiFromFS({ credentials: useCredentialsStore().current }) + return importEmojiFromFS({ credentials: useOAuthStore().token }) }, listEmojiPacks(params) { return listEmojiPacks({ ...params, - credentials: useCredentialsStore().current + credentials: useOAuthStore().token, }) }, listRemoteEmojiPacks(params) { return listRemoteEmojiPacks({ ...params, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) }, addNewEmojiFile({ packName, file, shortcode, filename }) { @@ -566,7 +566,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { file, shortcode, filename, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) }, downloadRemoteEmojiPack({ instance, packName, as }) { @@ -574,33 +574,33 @@ export const useAdminSettingsStore = defineStore('adminSettings', { instance, packName, as, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) }, downloadRemoteEmojiPackZIP({ url, packName }) { return downloadRemoteEmojiPackZIP({ url, packName, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) }, createEmojiPack({ name }) { return createEmojiPack({ name, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) }, deleteEmojiPack({ name }) { return createEmojiPack({ name, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) }, saveEmojiPackMetadata({ name, newData }) { return createEmojiPack({ name, newData, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) }, }, diff --git a/src/stores/announcements.js b/src/stores/announcements.js index 4f11e0f6a..87d83813a 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -1,6 +1,6 @@ import { defineStore } from 'pinia' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { getAnnouncements as adminGetAnnouncements, @@ -47,15 +47,15 @@ export const useAnnouncementsStore = defineStore('announcements', { const fetchAnnouncements = async () => { if (!isAdmin) { return getAnnouncements({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) } const all = await adminGetAnnouncements({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) const visible = await getAnnouncements({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) const visibleObject = visible.reduce((a, c) => { a[c.id] = c @@ -93,7 +93,7 @@ export const useAnnouncementsStore = defineStore('announcements', { markAnnouncementAsRead(id) { return dismissAnnouncement({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(() => { const index = this.announcements.findIndex((a) => a.id === id) @@ -124,7 +124,7 @@ export const useAnnouncementsStore = defineStore('announcements', { }, postAnnouncement({ content, startsAt, endsAt, allDay }) { return postAnnouncement({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, content, startsAt, endsAt, @@ -140,7 +140,7 @@ export const useAnnouncementsStore = defineStore('announcements', { startsAt, endsAt, allDay, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(() => { return this.fetchAnnouncements() }) @@ -148,7 +148,7 @@ export const useAnnouncementsStore = defineStore('announcements', { deleteAnnouncement(id) { return deleteAnnouncement({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then(() => { return this.fetchAnnouncements() }) diff --git a/src/stores/bookmark_folders.js b/src/stores/bookmark_folders.js index c0345cbf8..3e16121b1 100644 --- a/src/stores/bookmark_folders.js +++ b/src/stores/bookmark_folders.js @@ -1,7 +1,7 @@ import { find, remove } from 'lodash' import { defineStore } from 'pinia' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { createBookmarkFolder, @@ -29,7 +29,7 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { startFetching() { promiseInterval(() => { this.fetcher = fetchBookmarkFolders({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then( (folders) => this.setBookmarkFolders(folders), @@ -60,7 +60,7 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { return createBookmarkFolder({ name, emoji, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((folder) => { this.setBookmarkFolder(folder) return folder @@ -68,7 +68,7 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { }, updateBookmarkFolder({ folderId, name, emoji }) { return updateBookmarkFolder({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, folderId, name, emoji, @@ -80,7 +80,7 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { deleteBookmarkFolder({ folderId }) { deleteBookmarkFolder({ folderId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) remove(this.allFolders, (folder) => folder.id === folderId) }, diff --git a/src/stores/credentials.js b/src/stores/credentials.js deleted file mode 100644 index 38f9227e8..000000000 --- a/src/stores/credentials.js +++ /dev/null @@ -1,19 +0,0 @@ -import { defineStore } from 'pinia' - -const defaultState = { - credentials: null, -} - -export const useCredentialsStore = defineStore('credentials', { - state: () => ({ ...defaultState }), - actions: { - setCredentials(credentials) { - this.credentials = credentials - }, - }, - getters: { - current() { - return this.credentials - }, - }, -}) diff --git a/src/stores/emoji.js b/src/stores/emoji.js index 2a30d651b..079dedb9f 100644 --- a/src/stores/emoji.js +++ b/src/stores/emoji.js @@ -1,8 +1,8 @@ import { merge } from 'lodash' import { defineStore } from 'pinia' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { ensureFinalFallback } from 'src/i18n/languages.js' import { listEmojiPacks } from 'src/services/api/api.service.js' @@ -188,9 +188,10 @@ export const useEmojiStore = defineStore('emoji', { this.adminPacksLocalLoading = true this.adminPacksLocal = await this.getAdminPacks( useInstanceStore().server, - (params) => listEmojiPacks({ + (params) => + listEmojiPacks({ ...params, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }), ) this.adminPacksLocalLoading = false @@ -221,14 +222,13 @@ export const useEmojiStore = defineStore('emoji', { instance, page: i, pageSize, - }) - .then((pageData) => { - if (pageData.error !== undefined) { - return Promise.reject(pageData.error) - } + }).then((pageData) => { + if (pageData.error !== undefined) { + return Promise.reject(pageData.error) + } - return pageData.packs - }), + return pageData.packs + }), ) } diff --git a/src/stores/lists.js b/src/stores/lists.js index 8b90c379c..c361b6e5e 100644 --- a/src/stores/lists.js +++ b/src/stores/lists.js @@ -1,7 +1,7 @@ import { find, remove } from 'lodash' import { defineStore } from 'pinia' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { addAccountsToList, @@ -36,7 +36,7 @@ export const useListsStore = defineStore('lists', { startFetching() { promiseInterval(() => { this.fetcher = fetchLists({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) .then( (lists) => this.setLists(lists), @@ -56,7 +56,7 @@ export const useListsStore = defineStore('lists', { createList({ title }) { return createList({ title, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((list) => { this.setList({ listId: list.id, title }) return list @@ -65,13 +65,13 @@ export const useListsStore = defineStore('lists', { fetchList({ listId }) { return getList({ listId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((list) => this.setList({ listId: list.id, title: list.title })) }, fetchListAccounts({ listId }) { return getListAccounts({ listId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((accountIds) => { if (!this.allListsObject[listId]) { this.allListsObject[listId] = { accountIds: [] } @@ -83,7 +83,7 @@ export const useListsStore = defineStore('lists', { updateList({ listId, title, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) if (!this.allListsObject[listId]) { @@ -110,14 +110,14 @@ export const useListsStore = defineStore('lists', { addAccountsToList({ listId, accountIds: added, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) } if (removed.length > 0) { removeAccountsFromList({ listId, accountIds: removed, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) } }, @@ -125,7 +125,7 @@ export const useListsStore = defineStore('lists', { return addAccountsToList({ listId, accountIds: [accountId], - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((result) => { if (!this.allListsObject[listId]) { this.allListsObject[listId] = { accountIds: [] } @@ -138,7 +138,7 @@ export const useListsStore = defineStore('lists', { return removeAccountsFromList({ listId, accountIds: [accountId], - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((result) => { if (!this.allListsObject[listId]) { this.allListsObject[listId] = { accountIds: [] } @@ -154,7 +154,7 @@ export const useListsStore = defineStore('lists', { deleteList({ listId }) { deleteList({ listId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) delete this.allListsObject[listId] diff --git a/src/stores/oauth.js b/src/stores/oauth.js index 2a79c2fa9..116169b81 100644 --- a/src/stores/oauth.js +++ b/src/stores/oauth.js @@ -41,12 +41,9 @@ export const useOAuthStore = defineStore('oauth', { userToken: false, }), getters: { - getToken() { + token() { return this.userToken || this.appToken }, - getUserToken() { - return this.userToken - }, }, actions: { setClientData({ clientId, clientSecret }) { diff --git a/src/stores/oauth_tokens.js b/src/stores/oauth_tokens.js index 0322f8cf1..3f9138cec 100644 --- a/src/stores/oauth_tokens.js +++ b/src/stores/oauth_tokens.js @@ -1,6 +1,6 @@ import { defineStore } from 'pinia' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { fetchOAuthTokens, @@ -14,7 +14,7 @@ export const useOAuthTokensStore = defineStore('oauthTokens', { actions: { fetchTokens() { fetchOAuthTokens({ - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((tokens) => { this.swapTokens(tokens) }) @@ -22,7 +22,7 @@ export const useOAuthTokensStore = defineStore('oauthTokens', { revokeToken(id) { revokeOAuthToken({ id, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((response) => { if (response.status === 201) { this.swapTokens(this.tokens.filter((token) => token.id !== id)) diff --git a/src/stores/polls.js b/src/stores/polls.js index 5af6deb0d..78ccd059b 100644 --- a/src/stores/polls.js +++ b/src/stores/polls.js @@ -1,7 +1,7 @@ import { merge } from 'lodash' import { defineStore } from 'pinia' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { fetchPoll, vote } from 'src/services/api/api.service.js' @@ -25,7 +25,7 @@ export const usePollsStore = defineStore('polls', { updateTrackedPoll(pollId) { fetchPoll({ pollId, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((poll) => { setTimeout(() => { if (this.trackedPolls[pollId]) { @@ -58,7 +58,7 @@ export const usePollsStore = defineStore('polls', { return vote({ pollId, choices, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((poll) => { this.mergeOrAddPoll(poll) return poll diff --git a/src/stores/reports.js b/src/stores/reports.js index e7fa66f7b..2fea2e8e6 100644 --- a/src/stores/reports.js +++ b/src/stores/reports.js @@ -1,8 +1,8 @@ import { filter } from 'lodash' import { defineStore } from 'pinia' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInterfaceStore } from 'src/stores/interface.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { setReportState } from 'src/services/api/admin.js' @@ -45,7 +45,7 @@ export const useReportsStore = defineStore('reports', { setReportState({ id, state, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).catch((e) => { console.error('Failed to set report state', e) useInterfaceStore().pushGlobalNotice({ diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js index bcb881fff..348579b72 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -20,9 +20,9 @@ import { toRaw } from 'vue' import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' -import { useCredentialsStore } from 'src/stores/credentials.js' import { useInstanceStore } from 'src/stores/instance.js' import { useLocalConfigStore } from 'src/stores/local_config.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { storage } from 'src/lib/storage.js' import { @@ -793,7 +793,7 @@ export const useSyncConfigStore = defineStore('sync_config', { const params = { pleroma_settings_store: { 'pleroma-fe': this.cache } } updateProfileJSON({ params, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }) }, }, diff --git a/src/stores/user_highlight.js b/src/stores/user_highlight.js index 564a4395a..b2cd680f5 100644 --- a/src/stores/user_highlight.js +++ b/src/stores/user_highlight.js @@ -14,7 +14,7 @@ import { import { defineStore } from 'pinia' import { toRaw } from 'vue' -import { useCredentialsStore } from 'src/stores/credentials.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { storage } from 'src/lib/storage.js' import { updateProfileJSON } from 'src/services/api/api.service.js' @@ -349,7 +349,7 @@ export const useUserHighlightStore = defineStore('user_highlight', { } updateProfileJSON({ params, - credentials: useCredentialsStore().current, + credentials: useOAuthStore().token, }).then((user) => { this.initUserHighlight(user) this.dirty = false From 9eee55df4ab927b4ea15334eaa55b629d6ae111e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 18:45:58 +0300 Subject: [PATCH 13/86] lint --- .../announcements_page/announcements_page.js | 4 +--- .../promise_interval/promise_interval.js | 21 +++++++------------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js index 385a6f796..0f7933e5f 100644 --- a/src/components/announcements_page/announcements_page.js +++ b/src/components/announcements_page/announcements_page.js @@ -35,9 +35,7 @@ const AnnouncementsPage = { canPostAnnouncement() { return ( this.currentUser && - this.currentUser.privileges.has( - 'announcements_manage_announcements', - ) + this.currentUser.privileges.has('announcements_manage_announcements') ) }, }, diff --git a/src/services/promise_interval/promise_interval.js b/src/services/promise_interval/promise_interval.js index b28b7dc5a..d9396c643 100644 --- a/src/services/promise_interval/promise_interval.js +++ b/src/services/promise_interval/promise_interval.js @@ -22,21 +22,16 @@ export const promiseInterval = (promiseCall, interval) => { window.clearTimeout(timeout) } - const loop = new Promise(async (resolve, reject) => { - try { - while (!stopped) { - await promiseCall() - const { timeoutId, promise } = wait(interval) - timeout = timeoutId - await promise() - } - resolve() - } catch (e) { - reject(e) + const loop = async () => { + while (!stopped) { + await promiseCall() + const { timeoutId, promise } = wait(interval) + timeout = timeoutId + await promise() } - }) + } - loop.then() + loop().then() return { stop: stopFetcher } } From 484a76b9ba7280476edee2685e0a7f6adc61b135 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 19:07:27 +0300 Subject: [PATCH 14/86] fix build --- build/sw_plugin.js | 14 +++++++++++--- src/components/who_to_follow/who_to_follow.js | 1 - .../notification_utils/notification_utils.js | 2 -- src/sw.js | 2 -- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/build/sw_plugin.js b/build/sw_plugin.js index 066c8da12..f278d55f6 100644 --- a/build/sw_plugin.js +++ b/build/sw_plugin.js @@ -102,8 +102,12 @@ export const buildSwPlugin = ({ swSrc, swDest }) => { }, }) - const swBundle = await build(config) - return swBundle.output[0] + try { + const swBundle = await build(config) + return swBundle.output[0] + } catch (e) { + console.error('Error building ServiceWorker:', e) + } }, }, closeBundle: { @@ -112,7 +116,11 @@ export const buildSwPlugin = ({ swSrc, swDest }) => { async handler() { if (process.env.VITEST) return console.info('Building service worker for production') - await build(config) + try { + await build(config) + } catch (e) { + console.error('Error building ServiceWorker:', e) + } }, }, } diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js index be4644424..b7bb24b45 100644 --- a/src/components/who_to_follow/who_to_follow.js +++ b/src/components/who_to_follow/who_to_follow.js @@ -1,5 +1,4 @@ import FollowCard from 'src/components/follow_card/follow_card.vue' -import apiService from '../../services/api/api.service.js' import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index e7987146a..37e5e95ad 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -1,8 +1,6 @@ import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js' import { muteFilterHits } from '../status_parser/status_parser.js' -import { useAnnouncementsStore } from 'src/stores/announcements.js' - import FaviconService from 'src/services/favicon_service/favicon_service.js' export const ACTIONABLE_NOTIFICATION_TYPES = new Set([ diff --git a/src/sw.js b/src/sw.js index 1e7abd3de..f1c1b75d2 100644 --- a/src/sw.js +++ b/src/sw.js @@ -1,7 +1,5 @@ /* eslint-env serviceworker */ -import 'virtual:pleroma-fe/service_worker_env' - import { createI18n } from 'vue-i18n' import { storage } from 'src/lib/storage.js' From c062ae66e3eeb04909a584cbc13503392202e1e2 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 22:32:17 +0300 Subject: [PATCH 15/86] fix follows --- src/services/api/api.service.js | 8 +-- .../follow_manipulate/follow_manipulate.js | 63 ++++++++----------- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 1aeeaa603..692bf19fd 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -245,19 +245,19 @@ export const getCaptcha = () => }) export const followUser = ({ id, credentials, ...options }) => { - const form = {} + const payload = {} if (options.reblogs !== undefined) { - form.reblogs = options.reblogs + payload.reblogs = options.reblogs } if (options.notify !== undefined) { - form.notify = options.notify + payload.notify = options.notify } return promisedRequest({ url: MASTODON_FOLLOW_URL(id), - formData: form, + payload, credentials, method: 'POST', }) diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index c754c7316..1a3b2395c 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -35,42 +35,33 @@ const fetchRelationship = (attempt, userId, store) => } }) -export const requestFollow = (userId, store) => - new Promise((resolve) => { - followUser({ - id: userId, - credentials: useOAuthStore().token, - }).then((updated) => { - store.commit('updateUserRelationship', [updated]) - - if (updated.following || (updated.locked && updated.requested)) { - // If we get result immediately or the account is locked, just stop. - resolve() - return - } - - // But usually we don't get result immediately, so we ask server - // for updated user profile to confirm if we are following them - // Sometimes it takes several tries. Sometimes we end up not following - // user anyway, probably because they locked themselves and we - // don't know that yet. - // Recursive Promise, it will call itself up to 3 times. - - return fetchRelationship(1, updated, store).then(() => { - resolve() - }) - }) +export const requestFollow = async (userId, store) => { + const updated = await followUser({ + id: userId, + credentials: useOAuthStore().token, }) -export const requestUnfollow = (userId, store) => - new Promise((resolve) => { - unfollowUser({ - id: userId, - credentials: useOAuthStore().token, - }).then((updated) => { - store.commit('updateUserRelationship', [updated]) - resolve({ - updated, - }) - }) + store.commit('updateUserRelationship', [updated]) + + if (updated.following || (updated.locked && updated.requested)) { + // If we get result immediately or the account is locked, just stop. + return + } + + // But usually we don't get result immediately, so we ask server + // for updated user profile to confirm if we are following them + // Sometimes it takes several tries. Sometimes we end up not following + // user anyway, probably because they locked themselves and we + // don't know that yet. + // Recursive Promise, it will call itself up to 3 times. + return await fetchRelationship(1, updated, store) +} + +export const requestUnfollow = async (userId, store) => { + const updated = await unfollowUser({ + id: userId, + credentials: useOAuthStore().token, }) + + return await store.commit('updateUserRelationship', [updated]) +} From bd06c8801abce1c61bc8dda3c029e5cdc330243b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 23:14:52 +0300 Subject: [PATCH 16/86] refactor and unify how query strings are formed --- src/modules/users.js | 4 +- src/services/api/api.service.js | 218 +++++++----------- src/services/api/helpers.js | 66 ++++++ .../notifications_fetcher.service.js | 16 +- .../timeline_fetcher.service.js | 10 +- test/unit/specs/services/api/helpers.spec.js | 90 ++++++++ .../entity_normalizer.spec.js | 5 +- 7 files changed, 263 insertions(+), 146 deletions(-) create mode 100644 test/unit/specs/services/api/helpers.spec.js diff --git a/src/modules/users.js b/src/modules/users.js index 4a4614e4d..e379f9447 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -798,8 +798,8 @@ const users = { } if (useMergedConfigStore().mergedConfig.useStreamingApi) { - dispatch('fetchTimeline', { timeline: 'friends', since: null }) - dispatch('fetchNotifications', { since: null }) + dispatch('fetchTimeline', { timeline: 'friends', sinceId: null }) + dispatch('fetchNotifications', { sinceId: null }) dispatch('enableMastoSockets', true) .catch((error) => { console.error( diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 692bf19fd..864123f5f 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -9,7 +9,7 @@ import { parseStatus, parseUser, } from '../entity_normalizer/entity_normalizer.service.js' -import { promisedRequest } from './helpers.js' +import { paramsString, promisedRequest } from './helpers.js' import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' @@ -46,8 +46,16 @@ const MASTODON_UNRETWEET_URL = (id) => `/api/v1/statuses/${id}/unreblog` const MASTODON_DELETE_URL = (id) => `/api/v1/statuses/${id}` const MASTODON_FOLLOW_URL = (id) => `/api/v1/accounts/${id}/follow` const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow` -const MASTODON_FOLLOWING_URL = (id) => `/api/v1/accounts/${id}/following` -const MASTODON_FOLLOWERS_URL = (id) => `/api/v1/accounts/${id}/followers` +const MASTODON_FOLLOWING_URL = ( + id, + { minId, maxId, sinceId, limit, withRelationships }, +) => + `/api/v1/accounts/${id}/following${paramsString({ minId, maxId, sinceId, limit, withRelationships })}` +const MASTODON_FOLLOWERS_URL = ( + id, + { minId, maxId, sinceId, limit, withRelationships }, +) => + `/api/v1/accounts/${id}/followers${paramsString({ minId, maxId, sinceId, limit, withRelationships })}` const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests' const MASTODON_APPROVE_USER_URL = (id) => `/api/v1/follow_requests/${id}/authorize` @@ -61,7 +69,8 @@ const MASTODON_STATUS_SOURCE_URL = (id) => `/api/v1/statuses/${id}/source` const MASTODON_STATUS_HISTORY_URL = (id) => `/api/v1/statuses/${id}/history` const MASTODON_USER_URL = '/api/v1/accounts' const MASTODON_USER_LOOKUP_URL = '/api/v1/accounts/lookup' -const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships' +const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) => + `/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}` const MASTODON_USER_TIMELINE_URL = (id) => `/api/v1/accounts/${id}/statuses` const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists` const MASTODON_LIST_URL = (id) => `/api/v1/lists/${id}` @@ -70,8 +79,20 @@ const MASTODON_LIST_ACCOUNTS_URL = (id) => `/api/v1/lists/${id}/accounts` const MASTODON_TAG_TIMELINE_URL = (tag) => `/api/v1/timelines/tag/${tag}` const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks' const AKKOMA_BUBBLE_TIMELINE_URL = '/api/v1/timelines/bubble' -const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/' -const MASTODON_USER_MUTES_URL = '/api/v1/mutes/' +const MASTODON_USER_BLOCKS_URL = ({ + maxId, + sinceId, + limit, + withRelationships, +}) => + `/api/v1/blocks/${paramsString({ maxId, sinceId, limit, withRelationships })}` +const MASTODON_USER_MUTES_URL = ({ + maxId, + sinceId, + limit, + withRelationships, +}) => + `/api/v1/mutes/${paramsString({ maxId, sinceId, limit, withRelationships })}` const MASTODON_BLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/block` const MASTODON_UNBLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/unblock` const MASTODON_MUTE_USER_URL = (id) => `/api/v1/accounts/${id}/mute` @@ -113,12 +134,14 @@ const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats' const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}` -const PLEROMA_CHAT_MESSAGES_URL = (id) => `/api/v1/pleroma/chats/${id}/messages` +const PLEROMA_CHAT_MESSAGES_URL = (id, { maxId, sinceId, limit }) => + `/api/v1/pleroma/chats/${id}/messages${paramsString({ maxId, sinceId, limit })}` const PLEROMA_CHAT_READ_URL = (id) => `/api/v1/pleroma/chats/${id}/read` const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}` const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups' -const PLEROMA_SCROBBLES_URL = (id) => `/api/v1/pleroma/accounts/${id}/scrobbles` +const PLEROMA_SCROBBLES_URL = (id, { maxId, sinceId, minId, limit, offset }) => + `/api/v1/pleroma/accounts/${id}/scrobbles${paramsString({ maxId, sinceId, minId, limit, offset })}` const PLEROMA_STATUS_QUOTES_URL = (id) => `/api/v1/pleroma/statuses/${id}/quotes` const PLEROMA_USER_FAVORITES_TIMELINE_URL = (id) => @@ -128,20 +151,14 @@ const PLEROMA_BOOKMARK_FOLDER_URL = (id) => `/api/v1/pleroma/bookmark_folders/${id}` const EMOJI_PACKS_URL = (page, pageSize) => - `/api/v1/pleroma/emoji/packs?page=${page}&page_size=${pageSize}` + `/api/v1/pleroma/emoji/packs${paramsString({ page, pageSize })}` export const updateNotificationSettings = ({ credentials, settings }) => { - const form = new FormData() - - each(settings, (value, key) => { - form.append(key, value) - }) - return promisedRequest({ - url: `${NOTIFICATION_SETTINGS_URL}?${new URLSearchParams(settings)}`, + url: NOTIFICATION_SETTINGS_URL, credentials, method: 'PUT', - formData: form, + payload: settings, }) } @@ -380,35 +397,17 @@ export const fetchUserByName = ({ name, credentials }) => }) .then((id) => fetchUser({ id, credentials })) -export const fetchUserRelationship = ({ id, credentials }) => +export const fetchUserRelationship = ({ id, withSuspended, credentials }) => promisedRequest({ - url: `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}`, + url: MASTODON_USER_RELATIONSHIPS_URL({ id, withSuspended }), credentials, }) -export const fetchFriends = ({ - id, - maxId, - sinceId, - limit = 20, - credentials, -}) => { - let url = MASTODON_FOLLOWING_URL(id) - const args = [ - maxId && `max_id=${maxId}`, - sinceId && `since_id=${sinceId}`, - limit && `limit=${limit}`, - 'with_relationships=true', - ] - .filter((_) => _) - .join('&') - - url = url + (args ? '?' + args : '') - return promisedRequest({ - url, +export const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => + promisedRequest({ + url: MASTODON_FOLLOWING_URL(id, { maxId, sinceId, limit }), credentials, }).then((data) => data.map(parseUser)) -} export const exportFriends = ({ id, credentials }) => { // biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO refactor this @@ -418,7 +417,12 @@ export const exportFriends = ({ id, credentials }) => { let more = true while (more) { const maxId = friends.length > 0 ? last(friends).id : undefined - const users = await fetchFriends({ id, maxId, credentials }) + const users = await fetchFriends({ + id, + maxId, + credentials, + withRelationships: true, + }) friends = concat(friends, users) if (users.length === 0) { more = false @@ -437,23 +441,16 @@ export const fetchFollowers = ({ sinceId, limit = 20, credentials, -}) => { - let url = MASTODON_FOLLOWERS_URL(id) - const args = [ - maxId && `max_id=${maxId}`, - sinceId && `since_id=${sinceId}`, - limit && `limit=${limit}`, - 'with_relationships=true', - ] - .filter((_) => _) - .join('&') - - url += args ? '?' + args : '' - return promisedRequest({ - url, +}) => + promisedRequest({ + url: MASTODON_FOLLOWERS_URL(id, { + maxId, + sinceId, + limit, + withRelationships: true, + }), credentials, }).then((data) => data.map(parseUser)) -} export const fetchFollowRequests = ({ credentials }) => promisedRequest({ @@ -567,17 +564,17 @@ export const fetchStatusHistory = ({ status, credentials }) => export const fetchTimeline = ({ timeline, credentials, - since = false, - minId = false, - until = false, - userId = false, - listId = false, - statusId = false, - tag = false, - withMuted = false, + sinceId, + minId, + maxId, + userId, + listId, + statusId, + tag, + withMuted, replyVisibility = 'all', includeTypes = [], - bookmarkFolderId = false, + bookmarkFolderId, }) => { const timelineUrls = { public: MASTODON_PUBLIC_TIMELINE, @@ -595,8 +592,14 @@ export const fetchTimeline = ({ quotes: PLEROMA_STATUS_QUOTES_URL, bubble: AKKOMA_BUBBLE_TIMELINE_URL, } + const isNotifications = timeline === 'notifications' - const params = [] + const params = { + minId, + sinceId, + maxId, + limit: 20, + } let url = timelineUrls[timeline] @@ -616,51 +619,34 @@ export const fetchTimeline = ({ url = url(statusId) } - if (minId) { - params.push(['min_id', minId]) - } - if (since) { - params.push(['since_id', since]) - } - if (until) { - params.push(['max_id', until]) - } if (tag) { url = url(tag) } + if (timeline === 'media') { - params.push(['only_media', 1]) + params.onlyMedia = 1 } if (timeline === 'public') { - params.push(['local', true]) + params.local = true } if (timeline === 'public' || timeline === 'publicAndExternal') { - params.push(['only_media', false]) + params.onlyMedia = false } if (timeline !== 'favorites' && timeline !== 'bookmarks') { - params.push(['with_muted', withMuted]) + params.withMuted = withMuted } if (replyVisibility !== 'all') { - params.push(['reply_visibility', replyVisibility]) + params.replyVisibility = replyVisibility } if (includeTypes.size > 0) { - includeTypes.forEach((type) => { - params.push(['include_types[]', type]) - }) + params.includeTypes = includeTypes } if (timeline === 'bookmarks' && bookmarkFolderId) { - params.push(['folder_id', bookmarkFolderId]) + params.folderId = bookmarkFolderId } - params.push(['limit', 20]) - - const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join( - '&', - ) - url += `?${queryString}` - return promisedRequest({ - url, + url: url + paramsString(params), credentials, }).then(async (data) => { const pagination = parseLinkHeaderPagination( @@ -1036,16 +1022,11 @@ export const generateMfaBackupCodes = ({ credentials }) => method: 'GET', }) -export const fetchMutes = ({ maxId, credentials }) => { - const query = new URLSearchParams({ with_relationships: true }) - if (maxId) { - query.append('max_id', maxId) - } - return promisedRequest({ - url: `${MASTODON_USER_MUTES_URL}?${query.toString()}`, +export const fetchMutes = ({ maxId, credentials }) => + promisedRequest({ + url: MASTODON_USER_MUTES_URL({ maxId, withRelationships: true }), credentials, }).then((users) => users.map(parseUser)) -} export const muteUser = ({ id, expiresIn, credentials }) => { const payload = {} @@ -1068,16 +1049,11 @@ export const unmuteUser = ({ id, credentials }) => method: 'POST', }) -export const fetchBlocks = ({ maxId, credentials }) => { - const query = new URLSearchParams({ with_relationships: true }) - if (maxId) { - query.append('max_id', maxId) - } - return promisedRequest({ - url: `${MASTODON_USER_BLOCKS_URL}?${query.toString()}`, +export const fetchBlocks = ({ maxId, credentials }) => + promisedRequest({ + url: MASTODON_USER_BLOCKS_URL({ maxId, withRelationships: true }), credentials, }).then((users) => users.map(parseUser)) -} export const addBackup = ({ credentials }) => promisedRequest({ @@ -1519,19 +1495,8 @@ export const chatMessages = ({ sinceId, limit = 20, }) => { - let url = PLEROMA_CHAT_MESSAGES_URL(id) - const args = [ - maxId && `max_id=${maxId}`, - sinceId && `since_id=${sinceId}`, - limit && `limit=${limit}`, - ] - .filter((_) => _) - .join('&') - - url = url + (args ? '?' + args : '') - return promisedRequest({ - url, + url: PLEROMA_CHAT_MESSAGES_URL(id, { maxId, sinceId, limit }), method: 'GET', credentials, }) @@ -1584,15 +1549,10 @@ export const deleteChatMessage = ({ chatId, messageId, credentials }) => credentials, }) -export const fetchScrobbles = ({ accountId, limit = 1 }) => { - let url = PLEROMA_SCROBBLES_URL(accountId) - const params = [['limit', limit]] - const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join( - '&', - ) - url += `?${queryString}` - return promisedRequest({ url }) -} +export const fetchScrobbles = ({ accountId, limit = 1 }) => + promisedRequest({ + url: PLEROMA_SCROBBLES_URL(accountId, { limit }), + }) export const fetchBookmarkFolders = ({ credentials }) => promisedRequest({ diff --git a/src/services/api/helpers.js b/src/services/api/helpers.js index 80012cca3..e0629303d 100644 --- a/src/services/api/helpers.js +++ b/src/services/api/helpers.js @@ -1,5 +1,71 @@ +import { snakeCase } from 'lodash' + import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' +export const paramsString = (params = {}) => { + if (params == null || params === undefined) return '' + + if (typeof params !== 'object' || Array.isArray(params)) { + throw new Error('Params are not an object!') + } + + const entries = (() => { + if (params instanceof Map) { + return params.entries() + } else { + return Object.entries(params) + } + })() + + if (entries.length === 0) return '' + + const arrays = [] + const nonArrays = [] + + entries.forEach(([k, v]) => { + if (v == null) return // Drop nulls + if ( + (typeof v === 'object' && !Array.isArray(v)) || + typeof v === 'function' + ) { + throw new Error('Param cannot be non-primitive!') + } + if (Array.isArray(v)) { + arrays.push([k, v]) + } else { + nonArrays.push([k, v]) + } + }) + + arrays.forEach(([k, array]) => { + array.forEach((v) => { + if ( + typeof v === 'object' || + typeof v === 'function' || + typeof v === 'undefined' + ) + throw new Error('Array param cannot contain non-primitives!') + }) + }) + + return ( + '?' + + [ + ...nonArrays.map(([k, v]) => [snakeCase(k), v]), + // turning [a,[1,2,3]] into [[a[],1],[a[],2],[a[],3]] + ...arrays.reduce( + (acc, [k, arrayValue]) => [ + ...acc, + ...arrayValue.map((v) => [snakeCase(k) + '[]', v]), + ], + [], + ), + ] + .map(([k, v]) => `${k}=${window.encodeURIComponent(v)}`) + .join('&') + ) +} + export const promisedRequest = ({ method, url, diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index ec6c47a5d..e8fa3d16d 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -25,7 +25,7 @@ const mastoApiNotificationTypes = new Set([ 'pleroma:report', ]) -const fetchAndUpdate = ({ store, credentials, older = false, since }) => { +const fetchAndUpdate = ({ store, credentials, older = false, sinceId }) => { const args = { credentials } const rootState = store.rootState || store.state const timelineData = rootState.notifications @@ -35,24 +35,24 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => { mastoApiNotificationTypes.add('pleroma:chat_mention') } - args.includeTypes = mastoApiNotificationTypes + args.includeTypes = [...mastoApiNotificationTypes] args.withMuted = !hideMutedPosts args.timeline = 'notifications' if (older) { if (timelineData.minId !== Number.POSITIVE_INFINITY) { - args.until = timelineData.minId + args.maxId = timelineData.minId } return fetchNotifications({ store, args, older }) } else { // fetch new notifications if ( - since === undefined && + sinceId === undefined && timelineData.maxId !== Number.POSITIVE_INFINITY ) { - args.since = timelineData.maxId - } else if (since !== null) { - args.since = since + args.sinceId = timelineData.maxId + } else if (sinceId !== null) { + args.sinceId = sinceId } const result = fetchNotifications({ store, args, older }) @@ -69,7 +69,7 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => { if (readNotifsIds.length > 0 && readNotifsIds.length > 0) { const minId = Math.min(...unreadNotifsIds) // Oldest known unread notification if (minId !== Infinity) { - args.since = false // Don't use since_id since it sorta conflicts with min_id + args.sinceId = null // Don't use since_id since it sorta conflicts with min_id args.minId = minId - 1 // go beyond fetchNotifications({ store, args, older }) } diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 8e9b8a2e9..f2f9e6d41 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -41,7 +41,7 @@ const fetchAndUpdate = ({ bookmarkFolderId = false, tag = false, until, - since, + sinceId, }) => { const args = { timeline, credentials } const rootState = store.rootState || store.state @@ -53,10 +53,10 @@ const fetchAndUpdate = ({ if (older) { args.until = until || timelineData.minId } else { - if (since === undefined) { - args.since = timelineData.maxId - } else if (since !== null) { - args.since = since + if (sinceId === undefined) { + args.sinceId = timelineData.maxId + } else if (sinceId !== null) { + args.sinceId = sinceId } } diff --git a/test/unit/specs/services/api/helpers.spec.js b/test/unit/specs/services/api/helpers.spec.js new file mode 100644 index 000000000..3ffb83c70 --- /dev/null +++ b/test/unit/specs/services/api/helpers.spec.js @@ -0,0 +1,90 @@ +import { paramsString } from 'src/services/api/helpers.js' + +describe('API Helpers', () => { + describe.only('paramsString', () => { + it('should return empty string when given empty object', () => { + const string = paramsString({}) + + expect(string).to.eq('') + }) + + it('should return empty string when given null', () => { + const string = paramsString(null) + + expect(string).to.eq('') + }) + + it('should return empty string when given undefined', () => { + const string = paramsString(undefined) + + expect(string).to.eq('') + }) + + it('should return URI param string for normal object', () => { + const string = paramsString({ a: 1, b: '3' }) + + expect(string).to.eq('?a=1&b=3') + }) + + it('should encode objects correctly', () => { + const string = paramsString({ foo: true, bar: [1, 2, 3] }) + + expect(string).to.eq('?foo=true&bar[]=1&bar[]=2&bar[]=3') + }) + + it('should drop nullish params', () => { + const string = paramsString({ present: 'yes', missing: null }) + + expect(string).to.eq('?present=yes') + }) + + it('should convert camelCase keys to snake_keys objects correctly', () => { + const string = paramsString({ isActive: true, MaybeNot: false }) + + expect(string).to.eq('?is_active=true&maybe_not=false') + }) + + it('should work with maps', () => { + const string = paramsString( + new Map([ + ['key', 'yes'], + ['key2', 'also yes'], + ]), + ) + + expect(string).to.eq('?key=yes&key_2=also%20yes') + }) + + it('should escape components correctly', () => { + const string = paramsString({ gachi: '♂', muchi: 'Билли Геррингтон' }) + + expect(string).to.eq( + '?gachi=%E2%99%82&muchi=%D0%91%D0%B8%D0%BB%D0%BB%D0%B8%20%D0%93%D0%B5%D1%80%D1%80%D0%B8%D0%BD%D0%B3%D1%82%D0%BE%D0%BD', + ) + }) + + it('should throw when passed a non-object', () => { + expect(() => { + paramsString('Totally an object') + }).to.throw() + }) + + it('should throw when passed an array', () => { + expect(() => { + paramsString(['Totally an object']) + }).to.throw() + }) + + it('should throw when array param is non-primitive', () => { + expect(() => { + paramsString({ a: [() => ''] }) + }).to.throw() + }) + + it('should throw when array param is nullish', () => { + expect(() => { + paramsString({ a: [1, null, 3] }) + }).to.throw() + }) + }) +}) diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js index 43ce4da5d..a9cb3fd27 100644 --- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js +++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js @@ -1,10 +1,11 @@ +import mastoapidata from '../../../../fixtures/mastoapi.json' + import { parseLinkHeaderPagination, parseNotification, parseStatus, parseUser, -} from '../../../../../src/services/entity_normalizer/entity_normalizer.service.js' -import mastoapidata from '../../../../fixtures/mastoapi.json' +} from 'src/services/entity_normalizer/entity_normalizer.service.js' const makeMockUserMasto = (overrides = {}) => { return Object.assign( From 913fa3805095cec60ea7b2fdad17186768d2fab0 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 23:15:13 +0300 Subject: [PATCH 17/86] lint --- src/modules/users.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/users.js b/src/modules/users.js index e379f9447..471c19289 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -798,7 +798,10 @@ const users = { } if (useMergedConfigStore().mergedConfig.useStreamingApi) { - dispatch('fetchTimeline', { timeline: 'friends', sinceId: null }) + dispatch('fetchTimeline', { + timeline: 'friends', + sinceId: null, + }) dispatch('fetchNotifications', { sinceId: null }) dispatch('enableMastoSockets', true) .catch((error) => { From 93043ed33047c25ff9a94f8c4683ba2b8186b162 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 23:18:45 +0300 Subject: [PATCH 18/86] oops --- src/sw.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sw.js b/src/sw.js index f1c1b75d2..1e7abd3de 100644 --- a/src/sw.js +++ b/src/sw.js @@ -1,5 +1,7 @@ /* eslint-env serviceworker */ +import 'virtual:pleroma-fe/service_worker_env' + import { createI18n } from 'vue-i18n' import { storage } from 'src/lib/storage.js' From 6e809aa11be2489e256f4abe55ff4c0e2e7fcc45 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 23:18:49 +0300 Subject: [PATCH 19/86] lint --- src/services/api/helpers.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/services/api/helpers.js b/src/services/api/helpers.js index e0629303d..d18b13228 100644 --- a/src/services/api/helpers.js +++ b/src/services/api/helpers.js @@ -126,6 +126,12 @@ export const promisedRequest = ({ ) } + if (typeof json !== 'object') { + return resolve({ + _response: response, + _value: json + }) + } json._response = response return resolve(json) From d13c1bef9dd49086477e0bed360aba2dab582567 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 23:20:13 +0300 Subject: [PATCH 20/86] lint --- src/services/api/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/api/helpers.js b/src/services/api/helpers.js index d18b13228..236893501 100644 --- a/src/services/api/helpers.js +++ b/src/services/api/helpers.js @@ -129,7 +129,7 @@ export const promisedRequest = ({ if (typeof json !== 'object') { return resolve({ _response: response, - _value: json + _value: json, }) } json._response = response From 82c3fa0bd1cf97e2353f2acecfff5fd6069f13fb Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 16 Jun 2026 23:29:19 +0300 Subject: [PATCH 21/86] fix "until" --- .../timeline_fetcher/timeline_fetcher.service.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index f2f9e6d41..510e97ab0 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -40,7 +40,7 @@ const fetchAndUpdate = ({ statusId = false, bookmarkFolderId = false, tag = false, - until, + maxId, sinceId, }) => { const args = { timeline, credentials } @@ -51,7 +51,7 @@ const fetchAndUpdate = ({ const loggedIn = !!rootState.users.currentUser if (older) { - args.until = until || timelineData.minId + args.maxId = maxId || timelineData.minId } else { if (sinceId === undefined) { args.sinceId = timelineData.maxId @@ -119,11 +119,11 @@ const startFetching = ({ timeline = 'friends', credentials, store, - userId = false, - listId = false, - statusId = false, - bookmarkFolderId = false, - tag = false, + userId, + listId, + statusId, + bookmarkFolderId, + tag, }) => { const rootState = store.rootState || store.state const timelineData = rootState.statuses.timelines[camelCase(timeline)] From 04a0b0b8a85acbb1c2683010e09fa521d613fbce Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 13:33:30 +0300 Subject: [PATCH 22/86] refactor promisedRequest to use async/await --- src/services/api/helpers.js | 72 +++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/src/services/api/helpers.js b/src/services/api/helpers.js index 236893501..c01f2958c 100644 --- a/src/services/api/helpers.js +++ b/src/services/api/helpers.js @@ -66,7 +66,7 @@ export const paramsString = (params = {}) => { ) } -export const promisedRequest = ({ +export const promisedRequest = async ({ method, url, params, @@ -107,47 +107,41 @@ export const promisedRequest = ({ } } - return fetch(url, options).then((response) => { - return new Promise((resolve, reject) => { - // 204 is "No content", which fails to parse json (as you'd might think) - if (response.ok && response.status === 204) resolve() + const response = await fetch(url, options) - return response - .json() - .then((json) => { - if (!response.ok) { - return reject( - new StatusCodeError( - response.status, - json, - { url, options }, - response, - ), - ) - } + // 204 is "No content", which fails to parse json (as you'd might think) + if (response.ok && response.status === 204) return { _response: response } - if (typeof json !== 'object') { - return resolve({ - _response: response, - _value: json, - }) - } - json._response = response + if (!response.ok) { + throw new StatusCodeError( + response.status, + json, + { url, options }, + response, + ) + } - return resolve(json) - }) - .catch((error) => { - return reject( - new StatusCodeError( - response.status, - error, - { url, options }, - response, - ), - ) - }) - }) - }) + try { + const json = await response.json() + + if (typeof json !== 'object') { + return { + _response: response, + _value: json, + } + } + + json._response = response + + return json + } catch (error) { + throw new StatusCodeError( + response.status, + error, + { url, options }, + response, + ) + } } const authHeaders = (accessToken) => { From ebf70406625a2e5988d383eb21407acb6675d263 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 14:26:41 +0300 Subject: [PATCH 23/86] separate authenticated endpoints to user.js --- .../bookmark_folder_edit.js | 2 +- src/components/chat/chat.js | 2 +- src/components/chat_new/chat_new.js | 2 +- .../follow_request_card.js | 2 +- src/components/notification/notification.js | 2 +- .../settings_modal/tabs/appearance_tab.js | 2 +- .../settings_modal/tabs/composing_tab.js | 2 +- .../tabs/data_import_export_tab.js | 2 +- .../settings_modal/tabs/general_tab.js | 2 +- .../tabs/mutes_and_blocks_tab.js | 2 +- .../settings_modal/tabs/notifications_tab.js | 2 +- .../settings_modal/tabs/profile_tab.js | 2 +- .../settings_modal/tabs/security_tab/mfa.js | 2 +- .../tabs/security_tab/mfa_totp.js | 2 +- .../tabs/security_tab/security_tab.js | 2 +- src/components/user_card/user_card.js | 2 +- .../user_reporting_modal.js | 2 +- src/modules/chats.js | 6 +- src/modules/notifications.js | 4 +- src/modules/profileConfig.js | 2 +- src/modules/statuses.js | 20 +- src/modules/users.js | 16 +- src/services/api/api.service.js | 993 +----------------- src/services/api/chats.js | 86 ++ src/services/api/helpers.js | 20 +- src/services/api/user.js | 926 ++++++++++++++++ .../follow_manipulate/follow_manipulate.js | 2 +- .../follow_request_fetcher.service.js | 4 +- .../status_poster/status_poster.service.js | 2 +- src/stores/announcements.js | 6 +- src/stores/bookmark_folders.js | 2 +- src/stores/lists.js | 2 +- src/stores/oauth_tokens.js | 5 +- src/stores/polls.js | 3 +- src/stores/sync_config.js | 2 +- src/stores/user_highlight.js | 2 +- 36 files changed, 1082 insertions(+), 1055 deletions(-) create mode 100644 src/services/api/chats.js create mode 100644 src/services/api/user.js diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.js b/src/components/bookmark_folder_edit/bookmark_folder_edit.js index bc30790f6..74bcf3109 100644 --- a/src/components/bookmark_folder_edit/bookmark_folder_edit.js +++ b/src/components/bookmark_folder_edit/bookmark_folder_edit.js @@ -4,7 +4,7 @@ import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchBookmarkFolders } from 'src/services/api/api.service.js' +import { fetchBookmarkFolders } from 'src/services/api/user.js' const BookmarkFolderEdit = { data() { diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index bd71c2b5d..3ea9e3fd6 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -23,7 +23,7 @@ import { chatMessages, getOrCreateChat, sendChatMessage, -} from 'src/services/api/api.service.js' +} from 'src/services/api/chats.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons' diff --git a/src/components/chat_new/chat_new.js b/src/components/chat_new/chat_new.js index 7b08d3163..3e0cc8db3 100644 --- a/src/components/chat_new/chat_new.js +++ b/src/components/chat_new/chat_new.js @@ -5,7 +5,7 @@ import UserAvatar from 'src/components/user_avatar/user_avatar.vue' import { useOAuthStore } from 'src/stores/oauth.js' -import { chats } from 'src/services/api/api.service.js' +import { chats } from 'src/services/api/chats.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons' diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js index 178f36894..1ee9b5c3b 100644 --- a/src/components/follow_request_card/follow_request_card.js +++ b/src/components/follow_request_card/follow_request_card.js @@ -6,7 +6,7 @@ import BasicUserCard from '../basic_user_card/basic_user_card.vue' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { approveUser, denyUser } from 'src/services/api/api.service.js' +import { approveUser, denyUser } from 'src/services/api/user.js' const FollowRequestCard = { props: ['user'], diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 8fc77d91d..55311255e 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -18,7 +18,7 @@ import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' -import { approveUser, denyUser } from 'src/services/api/api.service.js' +import { approveUser, denyUser } from 'src/services/api/user.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import { library } from '@fortawesome/fontawesome-svg-core' diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js index 77686b736..75f0d74aa 100644 --- a/src/components/settings_modal/tabs/appearance_tab.js +++ b/src/components/settings_modal/tabs/appearance_tab.js @@ -14,7 +14,7 @@ import { useInstanceStore } from 'src/stores/instance.js' import { normalizeThemeData, useInterfaceStore } from 'src/stores/interface.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { updateProfileImages } from 'src/services/api/api.service.js' +import { updateProfileImages } from 'src/services/api/user.js' import { newImporter } from 'src/services/export_import/export_import.js' import { adoptStyleSheets, diff --git a/src/components/settings_modal/tabs/composing_tab.js b/src/components/settings_modal/tabs/composing_tab.js index 90d80cbf6..31d0a5706 100644 --- a/src/components/settings_modal/tabs/composing_tab.js +++ b/src/components/settings_modal/tabs/composing_tab.js @@ -17,7 +17,7 @@ import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' -import { updateProfile } from 'src/services/api/api.service.js' +import { updateProfile } from 'src/services/api/user.js' import localeService from 'src/services/locale/locale.service.js' import { cacheKey, clearCache, emojiCacheKey } from 'src/services/sw/sw.js' diff --git a/src/components/settings_modal/tabs/data_import_export_tab.js b/src/components/settings_modal/tabs/data_import_export_tab.js index 05af7d27c..0329ff125 100644 --- a/src/components/settings_modal/tabs/data_import_export_tab.js +++ b/src/components/settings_modal/tabs/data_import_export_tab.js @@ -16,7 +16,7 @@ import { importFollows, importMutes, listBackups, -} from 'src/services/api/api.service.js' +} from 'src/services/api/user.js' const DataImportExportTab = { data() { diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index 893833866..48bbd67c7 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -15,7 +15,7 @@ import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' -import { updateProfile } from 'src/services/api/api.service.js' +import { updateProfile } from 'src/services/api/user.js' import localeService from 'src/services/locale/locale.service.js' const GeneralTab = { diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js index d1f5bff12..da9a8cbb7 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js @@ -13,7 +13,7 @@ import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthTokensStore } from 'src/stores/oauth_tokens.js' -import { importBlocks, importFollows } from 'src/services/api/api.service.js' +import { importBlocks, importFollows } from 'src/services/api/user.js' const MutesAndBlocks = { data() { diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js index 6ef8e0ab7..5fd1e8483 100644 --- a/src/components/settings_modal/tabs/notifications_tab.js +++ b/src/components/settings_modal/tabs/notifications_tab.js @@ -3,7 +3,7 @@ import SharedComputedObject from '../helpers/shared_computed_object.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { updateNotificationSettings } from 'src/services/api/api.service.js' +import { updateNotificationSettings } from 'src/services/api/user.js' const NotificationsTab = { data() { diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index 586ca30a8..b03c2012f 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -5,7 +5,7 @@ import SharedComputedObject from '../helpers/shared_computed_object.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { updateProfile } from 'src/services/api/api.service.js' +import { updateProfile } from 'src/services/api/user.js' import { library } from '@fortawesome/fontawesome-svg-core' import { diff --git a/src/components/settings_modal/tabs/security_tab/mfa.js b/src/components/settings_modal/tabs/security_tab/mfa.js index bba2a2ff9..32ce9ed21 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa.js +++ b/src/components/settings_modal/tabs/security_tab/mfa.js @@ -12,7 +12,7 @@ import { mfaConfirmOTP, mfaSetupOTP, settingsMFA, -} from 'src/services/api/api.service.js' +} from 'src/services/api/user.js' const Mfa = { data: () => ({ diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.js b/src/components/settings_modal/tabs/security_tab/mfa_totp.js index b7a1426ab..393a36249 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa_totp.js +++ b/src/components/settings_modal/tabs/security_tab/mfa_totp.js @@ -4,7 +4,7 @@ import Confirm from './confirm.vue' import { useOAuthStore } from 'src/stores/oauth.js' -import { mfaDisableOTP } from 'src/services/api/api.service.js' +import { mfaDisableOTP } from 'src/services/api/user.js' export default { props: ['settings'], diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js index ca959ded5..35576e0d0 100644 --- a/src/components/settings_modal/tabs/security_tab/security_tab.js +++ b/src/components/settings_modal/tabs/security_tab/security_tab.js @@ -15,7 +15,7 @@ import { deleteAlias, listAliases, moveAccount, -} from 'src/services/api/api.service.js' +} from 'src/services/api/user.js' import localeService from 'src/services/locale/locale.service.js' const SecurityTab = { diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 1a35c019b..0ae234afe 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -26,7 +26,7 @@ import { useOAuthStore } from 'src/stores/oauth.js' import { usePostStatusStore } from 'src/stores/post_status' import { useUserHighlightStore } from 'src/stores/user_highlight.js' -import { updateProfile } from 'src/services/api/api.service.js' +import { updateProfile } from 'src/services/api/user.js' import { propsToNative } from 'src/services/attributes_helper/attributes_helper.service.js' import localeService from 'src/services/locale/locale.service.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js index 49e569f23..68e30903e 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.js +++ b/src/components/user_reporting_modal/user_reporting_modal.js @@ -8,7 +8,7 @@ import UserLink from 'src/components/user_link/user_link.vue' import { useOAuthStore } from 'src/stores/oauth.js' import { useReportsStore } from 'src/stores/reports.js' -import { reportUser } from 'src/services/api/api.service.js' +import { reportUser } from 'src/services/api/user.js' const UserReportingModal = { components: { diff --git a/src/modules/chats.js b/src/modules/chats.js index 4ad82eccd..8bd353a70 100644 --- a/src/modules/chats.js +++ b/src/modules/chats.js @@ -11,11 +11,7 @@ import { promiseInterval } from '../services/promise_interval/promise_interval.j import { useOAuthStore } from 'src/stores/oauth.js' -import { - chats, - deleteChatMessage, - readChat, -} from 'src/services/api/api.service.js' +import { chats, deleteChatMessage, readChat } from 'src/services/api/chats.js' const emptyChatList = () => ({ data: [], diff --git a/src/modules/notifications.js b/src/modules/notifications.js index 3d5f787cf..d759be83b 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -1,4 +1,4 @@ -import { markNotificationsAsSeen } from '../services/api/api.service.js' +import { markNotificationsAsSeen } from '../services/api/user.js' import { closeAllDesktopNotifications, closeDesktopNotification, @@ -15,7 +15,7 @@ import { useOAuthStore } from 'src/stores/oauth.js' import { useReportsStore } from 'src/stores/reports.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' -import { dismissNotification } from 'src/services/api/api.service.js' +import { dismissNotification } from 'src/services/api/user.js' const emptyNotifications = () => ({ desktopNotificationSilence: true, diff --git a/src/modules/profileConfig.js b/src/modules/profileConfig.js index 0f125e976..a7f3e73d8 100644 --- a/src/modules/profileConfig.js +++ b/src/modules/profileConfig.js @@ -5,7 +5,7 @@ import { useOAuthStore } from 'src/stores/oauth.js' import { updateNotificationSettings, updateProfile, -} from 'src/services/api/api.service.js' +} from 'src/services/api/user.js' const defaultApi = ({ rootState, commit }, { path, value }) => { const params = {} diff --git a/src/modules/statuses.js b/src/modules/statuses.js index ef6aedcbc..c0cffb7ed 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -13,10 +13,11 @@ import { slice, } from 'lodash' +import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' +import { useInterfaceStore } from 'src/stores/interface.js' +import { useOAuthStore } from 'src/stores/oauth.js' + import { - bookmarkStatus, - deleteStatus, - favorite, fetchEmojiReactions, fetchFavoritedByUsers, fetchPinnedStatuses, @@ -25,22 +26,23 @@ import { fetchStatus, fetchStatusHistory, fetchStatusSource, + search2, +} from 'src/services/api/api.service.js' +import { + bookmarkStatus, + deleteStatus, + favorite, muteConversation, pinOwnStatus, reactWithEmoji, retweet, - search2, unbookmarkStatus, unfavorite, unmuteConversation, unpinOwnStatus, unreactWithEmoji, unretweet, -} from '../services/api/api.service.js' - -import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' -import { useInterfaceStore } from 'src/stores/interface.js' -import { useOAuthStore } from 'src/stores/oauth.js' +} from 'src/services/api/user.js' const emptyTl = (userId = 0) => ({ statuses: [], diff --git a/src/modules/users.js b/src/modules/users.js index 471c19289..c4aa9a1c5 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -32,21 +32,23 @@ import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' import { - fetchBlocks, - fetchDomainMutes, fetchFollowers, fetchFriends, - fetchMutes, fetchUser, fetchUserByName, - fetchUserInLists, - fetchUserRelationship, - followUser, getCaptcha, - muteUser, searchUsers, verifyCredentials, } from 'src/services/api/api.service.js' +import { + fetchBlocks, + fetchDomainMutes, + fetchMutes, + fetchUserInLists, + fetchUserRelationship, + followUser, + muteUser, +} from 'src/services/api/user.js' // TODO: Unify with mergeOrAdd in statuses.js export const mergeOrAdd = (arr, obj, item) => { diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 864123f5f..66847a32b 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -13,39 +13,12 @@ import { paramsString, promisedRequest } from './helpers.js' import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' -/* eslint-env browser */ -const MUTES_IMPORT_URL = '/api/pleroma/mutes_import' -const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import' -const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' -const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' -const CHANGE_EMAIL_URL = '/api/pleroma/change_email' -const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' -const MOVE_ACCOUNT_URL = '/api/pleroma/move_account' -const ALIASES_URL = '/api/pleroma/aliases' const SUGGESTIONS_URL = '/api/v1/suggestions' -const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings' -const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read' - -const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa' -const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes' - -const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp' -const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp' -const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/totp' - +/* eslint-env browser */ const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' const MASTODON_REGISTRATION_URL = '/api/v1/accounts' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications' -const MASTODON_DISMISS_NOTIFICATION_URL = (id) => - `/api/v1/notifications/${id}/dismiss` -const MASTODON_FAVORITE_URL = (id) => `/api/v1/statuses/${id}/favourite` -const MASTODON_UNFAVORITE_URL = (id) => `/api/v1/statuses/${id}/unfavourite` -const MASTODON_RETWEET_URL = (id) => `/api/v1/statuses/${id}/reblog` -const MASTODON_UNRETWEET_URL = (id) => `/api/v1/statuses/${id}/unreblog` -const MASTODON_DELETE_URL = (id) => `/api/v1/statuses/${id}` -const MASTODON_FOLLOW_URL = (id) => `/api/v1/accounts/${id}/follow` -const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow` const MASTODON_FOLLOWING_URL = ( id, { minId, maxId, sinceId, limit, withRelationships }, @@ -56,178 +29,42 @@ const MASTODON_FOLLOWERS_URL = ( { minId, maxId, sinceId, limit, withRelationships }, ) => `/api/v1/accounts/${id}/followers${paramsString({ minId, maxId, sinceId, limit, withRelationships })}` -const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests' -const MASTODON_APPROVE_USER_URL = (id) => - `/api/v1/follow_requests/${id}/authorize` -const MASTODON_DENY_USER_URL = (id) => `/api/v1/follow_requests/${id}/reject` +const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home' +const MASTODON_LIST_TIMELINE_URL = (id) => `/api/v1/timelines/list/${id}` +const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks' const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = '/api/v1/timelines/direct' const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public' -const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home' -const MASTODON_STATUS_URL = (id) => `/api/v1/statuses/${id}` +export const MASTODON_STATUS_URL = (id) => `/api/v1/statuses/${id}` const MASTODON_STATUS_CONTEXT_URL = (id) => `/api/v1/statuses/${id}/context` const MASTODON_STATUS_SOURCE_URL = (id) => `/api/v1/statuses/${id}/source` const MASTODON_STATUS_HISTORY_URL = (id) => `/api/v1/statuses/${id}/history` const MASTODON_USER_URL = '/api/v1/accounts' const MASTODON_USER_LOOKUP_URL = '/api/v1/accounts/lookup' -const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) => - `/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}` const MASTODON_USER_TIMELINE_URL = (id) => `/api/v1/accounts/${id}/statuses` -const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists` -const MASTODON_LIST_URL = (id) => `/api/v1/lists/${id}` -const MASTODON_LIST_TIMELINE_URL = (id) => `/api/v1/timelines/list/${id}` -const MASTODON_LIST_ACCOUNTS_URL = (id) => `/api/v1/lists/${id}/accounts` const MASTODON_TAG_TIMELINE_URL = (tag) => `/api/v1/timelines/tag/${tag}` -const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks' const AKKOMA_BUBBLE_TIMELINE_URL = '/api/v1/timelines/bubble' -const MASTODON_USER_BLOCKS_URL = ({ - maxId, - sinceId, - limit, - withRelationships, -}) => - `/api/v1/blocks/${paramsString({ maxId, sinceId, limit, withRelationships })}` -const MASTODON_USER_MUTES_URL = ({ - maxId, - sinceId, - limit, - withRelationships, -}) => - `/api/v1/mutes/${paramsString({ maxId, sinceId, limit, withRelationships })}` -const MASTODON_BLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/block` -const MASTODON_UNBLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/unblock` -const MASTODON_MUTE_USER_URL = (id) => `/api/v1/accounts/${id}/mute` -const MASTODON_UNMUTE_USER_URL = (id) => `/api/v1/accounts/${id}/unmute` -const MASTODON_REMOVE_USER_FROM_FOLLOWERS = (id) => - `/api/v1/accounts/${id}/remove_from_followers` -const MASTODON_USER_NOTE_URL = (id) => `/api/v1/accounts/${id}/note` -const MASTODON_BOOKMARK_STATUS_URL = (id) => `/api/v1/statuses/${id}/bookmark` -const MASTODON_UNBOOKMARK_STATUS_URL = (id) => - `/api/v1/statuses/${id}/unbookmark` -const MASTODON_POST_STATUS_URL = '/api/v1/statuses' -const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media' -const MASTODON_VOTE_URL = (id) => `/api/v1/polls/${id}/votes` const MASTODON_POLL_URL = (id) => `/api/v1/polls/${id}` const MASTODON_STATUS_FAVORITEDBY_URL = (id) => `/api/v1/statuses/${id}/favourited_by` const MASTODON_STATUS_REBLOGGEDBY_URL = (id) => `/api/v1/statuses/${id}/reblogged_by` -const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials' -const MASTODON_REPORT_USER_URL = '/api/v1/reports' -const MASTODON_PIN_OWN_STATUS = (id) => `/api/v1/statuses/${id}/pin` -const MASTODON_UNPIN_OWN_STATUS = (id) => `/api/v1/statuses/${id}/unpin` -const MASTODON_MUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/mute` -const MASTODON_UNMUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/unmute` const MASTODON_SEARCH_2 = '/api/v2/search' const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' -const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks' -const MASTODON_LISTS_URL = '/api/v1/lists' const MASTODON_STREAMING = '/api/v1/streaming' const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers' const MASTODON_ANNOUNCEMENTS_URL = '/api/v1/announcements' -const MASTODON_ANNOUNCEMENTS_DISMISS_URL = (id) => - `/api/v1/announcements/${id}/dismiss` const PLEROMA_EMOJI_REACTIONS_URL = (id) => `/api/v1/pleroma/statuses/${id}/reactions` -const PLEROMA_EMOJI_REACT_URL = (id, emoji) => - `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` -const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => - `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` -const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats' -const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}` -const PLEROMA_CHAT_MESSAGES_URL = (id, { maxId, sinceId, limit }) => - `/api/v1/pleroma/chats/${id}/messages${paramsString({ maxId, sinceId, limit })}` -const PLEROMA_CHAT_READ_URL = (id) => `/api/v1/pleroma/chats/${id}/read` -const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => - `/api/v1/pleroma/chats/${chatId}/messages/${messageId}` -const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups' const PLEROMA_SCROBBLES_URL = (id, { maxId, sinceId, minId, limit, offset }) => `/api/v1/pleroma/accounts/${id}/scrobbles${paramsString({ maxId, sinceId, minId, limit, offset })}` const PLEROMA_STATUS_QUOTES_URL = (id) => `/api/v1/pleroma/statuses/${id}/quotes` const PLEROMA_USER_FAVORITES_TIMELINE_URL = (id) => `/api/v1/pleroma/accounts/${id}/favourites` -const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders' -const PLEROMA_BOOKMARK_FOLDER_URL = (id) => - `/api/v1/pleroma/bookmark_folders/${id}` const EMOJI_PACKS_URL = (page, pageSize) => `/api/v1/pleroma/emoji/packs${paramsString({ page, pageSize })}` -export const updateNotificationSettings = ({ credentials, settings }) => { - return promisedRequest({ - url: NOTIFICATION_SETTINGS_URL, - credentials, - method: 'PUT', - payload: settings, - }) -} - -export const updateProfileImages = ({ - credentials, - avatar = null, - avatarName = null, - banner = null, - background = null, -}) => { - const form = new FormData() - if (avatar !== null) { - if (avatarName !== null) { - form.append('avatar', avatar, avatarName) - } else { - form.append('avatar', avatar) - } - } - if (banner !== null) form.append('header', banner) - if (background !== null) form.append('pleroma_background_image', background) - return promisedRequest({ - url: MASTODON_PROFILE_UPDATE_URL, - credentials, - method: 'PATCH', - formData: form, - }).then((data) => { - if (data.error) { - throw new Error(data.error) - } - return parseUser(data) - }) -} - -export const updateProfile = ({ credentials, params }) => { - const formData = new FormData() - - for (const name in params) { - if (name === 'fields_attributes') { - params[name].forEach((param, i) => { - formData.append(name + `[${i}][name]`, param.name) - formData.append(name + `[${i}][value]`, param.value) - }) - } else { - if (typeof params[name] === 'object') { - console.warn( - 'Object detected in updateProfile API call. This will not work, use updateProfileJSON instead.', - ) - console.warn('Object:\n' + JSON.stringify(params[name], null, 2)) - } - formData.append(name, params[name]) - } - } - - return promisedRequest({ - url: MASTODON_PROFILE_UPDATE_URL, - credentials, - method: 'PATCH', - formData, - }).then((data) => parseUser(data)) -} - -export const updateProfileJSON = ({ credentials, params }) => - promisedRequest({ - url: MASTODON_PROFILE_UPDATE_URL, - credentials, - payload: params, - method: 'PATCH', - }).then((data) => parseUser(data)) - // Params needed: // nickname // email @@ -261,118 +98,6 @@ export const getCaptcha = () => url: '/api/pleroma/captcha', }) -export const followUser = ({ id, credentials, ...options }) => { - const payload = {} - - if (options.reblogs !== undefined) { - payload.reblogs = options.reblogs - } - - if (options.notify !== undefined) { - payload.notify = options.notify - } - - return promisedRequest({ - url: MASTODON_FOLLOW_URL(id), - payload, - credentials, - method: 'POST', - }) -} - -export const unfollowUser = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_UNFOLLOW_URL(id), - credentials, - method: 'POST', - }) - -export const fetchUserInLists = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_USER_IN_LISTS(id), - credentials, - }) - -export const pinOwnStatus = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_PIN_OWN_STATUS(id), - credentials, - method: 'POST', - }).then((data) => parseStatus(data)) - -export const unpinOwnStatus = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_UNPIN_OWN_STATUS(id), - credentials, - method: 'POST', - }).then((data) => parseStatus(data)) - -export const muteConversation = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_MUTE_CONVERSATION(id), - credentials, - method: 'POST', - }).then((data) => parseStatus(data)) - -export const unmuteConversation = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_UNMUTE_CONVERSATION(id), - credentials, - method: 'POST', - }).then((data) => parseStatus(data)) - -export const blockUser = ({ id, expiresIn, credentials }) => { - const payload = {} - if (expiresIn) { - payload.duration = expiresIn - } - - return promisedRequest({ - url: MASTODON_BLOCK_USER_URL(id), - credentials, - method: 'POST', - payload, - }) -} - -export const unblockUser = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_UNBLOCK_USER_URL(id), - credentials, - method: 'POST', - }) - -export const removeUserFromFollowers = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_REMOVE_USER_FROM_FOLLOWERS(id), - credentials, - method: 'POST', - }) - -export const editUserNote = ({ id, credentials, comment }) => - promisedRequest({ - url: MASTODON_USER_NOTE_URL(id), - credentials, - payload: { - comment, - }, - method: 'POST', - }) - -export const approveUser = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_APPROVE_USER_URL(id), - credentials, - method: 'POST', - }) - -export const denyUser = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_DENY_USER_URL(id), - credentials, - method: 'POST', - }) - export const fetchUser = ({ id, credentials }) => promisedRequest({ url: `${MASTODON_USER_URL}/${id}`, @@ -397,44 +122,12 @@ export const fetchUserByName = ({ name, credentials }) => }) .then((id) => fetchUser({ id, credentials })) -export const fetchUserRelationship = ({ id, withSuspended, credentials }) => - promisedRequest({ - url: MASTODON_USER_RELATIONSHIPS_URL({ id, withSuspended }), - credentials, - }) - export const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => promisedRequest({ url: MASTODON_FOLLOWING_URL(id, { maxId, sinceId, limit }), credentials, }).then((data) => data.map(parseUser)) -export const exportFriends = ({ id, credentials }) => { - // biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO refactor this - return new Promise(async (resolve, reject) => { - try { - let friends = [] - let more = true - while (more) { - const maxId = friends.length > 0 ? last(friends).id : undefined - const users = await fetchFriends({ - id, - maxId, - credentials, - withRelationships: true, - }) - friends = concat(friends, users) - if (users.length === 0) { - more = false - } - } - resolve(friends) - } catch (err) { - reject(err) - } - }) -} - export const fetchFollowers = ({ id, maxId, @@ -452,70 +145,6 @@ export const fetchFollowers = ({ credentials, }).then((data) => data.map(parseUser)) -export const fetchFollowRequests = ({ credentials }) => - promisedRequest({ - url: MASTODON_FOLLOW_REQUESTS_URL, - credentials, - }).then((data) => data.map(parseUser)) - -export const fetchLists = ({ credentials }) => - promisedRequest({ - url: MASTODON_LISTS_URL, - credentials, - }) - -export const createList = ({ title, credentials }) => - promisedRequest({ - url: MASTODON_LISTS_URL, - credentials, - method: 'POST', - payload: { title }, - }) - -export const getList = ({ listId, credentials }) => - promisedRequest({ - url: MASTODON_LIST_URL(listId), - credentials, - }) - -export const updateList = ({ listId, title, credentials }) => - promisedRequest({ - url: MASTODON_LIST_URL(listId), - - credentials, - method: 'PUT', - payload: { title }, - }) - -export const getListAccounts = ({ listId, credentials }) => - promisedRequest({ - url: MASTODON_LIST_ACCOUNTS_URL(listId), - credentials, - }).then((data) => data.map(({ id }) => id)) - -export const addAccountsToList = ({ listId, accountIds, credentials }) => - promisedRequest({ - url: MASTODON_LIST_ACCOUNTS_URL(listId), - credentials, - method: 'POST', - payload: { account_ids: accountIds }, - }) - -export const removeAccountsFromList = ({ listId, accountIds, credentials }) => - promisedRequest({ - url: MASTODON_LIST_ACCOUNTS_URL(listId), - credentials, - method: 'DELETE', - payload: { account_ids: accountIds }, - }) - -export const deleteList = ({ listId, credentials }) => - promisedRequest({ - url: MASTODON_LIST_URL(listId), - method: 'DELETE', - credentials, - }) - export const fetchConversation = ({ id, credentials }) => promisedRequest({ url: MASTODON_STATUS_CONTEXT_URL(id), @@ -680,452 +309,12 @@ export const verifyCredentials = ({ credentials }) => credentials, }).then((data) => (data.error ? data : parseUser(data))) -export const favorite = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_FAVORITE_URL(id), - method: 'POST', - credentials, - }).then((data) => parseStatus(data)) - -export const unfavorite = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_UNFAVORITE_URL(id), - method: 'POST', - credentials, - }).then((data) => parseStatus(data)) - -export const retweet = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_RETWEET_URL(id), - method: 'POST', - credentials, - }).then((data) => parseStatus(data)) - -export const unretweet = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_UNRETWEET_URL(id), - method: 'POST', - credentials, - }).then((data) => parseStatus(data)) - -export const bookmarkStatus = ({ id, credentials, ...options }) => - promisedRequest({ - url: MASTODON_BOOKMARK_STATUS_URL(id), - credentials, - method: 'POST', - payload: { - folder_id: options.folder_id, - }, - }) - -export const unbookmarkStatus = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_UNBOOKMARK_STATUS_URL(id), - credentials, - method: 'POST', - }) - -export const postStatus = ({ - credentials, - status, - spoilerText, - visibility, - sensitive, - poll, - mediaIds = [], - inReplyToStatusId, - quoteId, - contentType, - preview, - idempotencyKey, -}) => { - const form = new FormData() - const pollOptions = poll.options || [] - - form.append('status', status) - form.append('source', 'Pleroma FE') - if (spoilerText) form.append('spoiler_text', spoilerText) - if (visibility) form.append('visibility', visibility) - if (sensitive) form.append('sensitive', sensitive) - if (contentType) form.append('content_type', contentType) - mediaIds.forEach((val) => { - form.append('media_ids[]', val) - }) - if (pollOptions.some((option) => option !== '')) { - const normalizedPoll = { - expires_in: parseInt(poll.expiresIn, 10), - multiple: poll.multiple, - } - Object.keys(normalizedPoll).forEach((key) => { - form.append(`poll[${key}]`, normalizedPoll[key]) - }) - - pollOptions.forEach((option) => { - form.append('poll[options][]', option) - }) - } - if (inReplyToStatusId) { - form.append('in_reply_to_id', inReplyToStatusId) - } - if (quoteId) { - form.append('quote_id', quoteId) - } - if (preview) { - form.append('preview', 'true') - } - - const headers = {} - if (idempotencyKey) { - headers['idempotency-key'] = idempotencyKey - } - - return promisedRequest({ - url: MASTODON_POST_STATUS_URL, - formData: form, - method: 'POST', - credentials, - headers, - }).then((data) => (data.error ? data : parseStatus(data))) -} - -export const editStatus = ({ - id, - credentials, - status, - spoilerText, - sensitive, - poll, - mediaIds = [], - contentType, -}) => { - const form = new FormData() - const pollOptions = poll.options || [] - - form.append('status', status) - if (spoilerText) form.append('spoiler_text', spoilerText) - if (sensitive) form.append('sensitive', sensitive) - if (contentType) form.append('content_type', contentType) - mediaIds.forEach((val) => { - form.append('media_ids[]', val) - }) - - if (pollOptions.some((option) => option !== '')) { - const normalizedPoll = { - expires_in: parseInt(poll.expiresIn, 10), - multiple: poll.multiple, - } - Object.keys(normalizedPoll).forEach((key) => { - form.append(`poll[${key}]`, normalizedPoll[key]) - }) - - pollOptions.forEach((option) => { - form.append('poll[options][]', option) - }) - } - - return promisedRequest({ - url: MASTODON_STATUS_URL(id), - formData: form, - method: 'PUT', - credentials, - }).then((data) => (data.error ? data : parseStatus(data))) -} - -export const deleteStatus = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_DELETE_URL(id), - credentials, - method: 'DELETE', - }) - -export const uploadMedia = ({ formData, credentials }) => - promisedRequest({ - url: MASTODON_MEDIA_UPLOAD_URL, - formData, - method: 'POST', - credentials, - }).then((data) => parseAttachment(data)) - -export const setMediaDescription = ({ id, description, credentials }) => - promisedRequest({ - url: `${MASTODON_MEDIA_UPLOAD_URL}/${id}`, - method: 'PUT', - credentials, - payload: { - description, - }, - }).then((data) => parseAttachment(data)) - -export const importMutes = ({ file, credentials }) => { - const formData = new FormData() - formData.append('list', file) - return promisedRequest({ - url: MUTES_IMPORT_URL, - formData, - method: 'POST', - credentials, - }).then((response) => response.ok) -} - -export const importBlocks = ({ file, credentials }) => { - const formData = new FormData() - formData.append('list', file) - return promisedRequest({ - url: BLOCKS_IMPORT_URL, - formData, - method: 'POST', - credentials, - }).then((response) => response.ok) -} - -export const importFollows = ({ file, credentials }) => { - const formData = new FormData() - formData.append('list', file) - return promisedRequest({ - url: FOLLOW_IMPORT_URL, - formData, - method: 'POST', - credentials, - }).then((response) => response.ok) -} - -export const deleteAccount = ({ credentials, password }) => { - const formData = new FormData() - - formData.append('password', password) - - return promisedRequest({ - url: DELETE_ACCOUNT_URL, - formData, - method: 'POST', - credentials, - }) -} - -export const changeEmail = ({ credentials, email, password }) => { - const form = new FormData() - - form.append('email', email) - form.append('password', password) - - return promisedRequest({ - url: CHANGE_EMAIL_URL, - formData: form, - method: 'POST', - credentials, - }) -} - -export const moveAccount = ({ credentials, password, targetAccount }) => { - const form = new FormData() - - form.append('password', password) - form.append('target_account', targetAccount) - - return promisedRequest({ - url: MOVE_ACCOUNT_URL, - formData: form, - method: 'POST', - credentials, - }) -} - -export const addAlias = ({ credentials, alias }) => - promisedRequest({ - url: ALIASES_URL, - method: 'PUT', - credentials, - payload: { alias }, - }) - -export const deleteAlias = ({ credentials, alias }) => - promisedRequest({ - url: ALIASES_URL, - method: 'DELETE', - credentials, - payload: { alias }, - }) - -export const listAliases = ({ credentials }) => - promisedRequest({ - url: ALIASES_URL, - method: 'GET', - credentials, - params: { - _cacheBooster: new Date().getTime(), - }, - }) - -export const changePassword = ({ - credentials, - password, - newPassword, - newPasswordConfirmation, -}) => { - const form = new FormData() - - form.append('password', password) - form.append('new_password', newPassword) - form.append('new_password_confirmation', newPasswordConfirmation) - - return promisedRequest({ - url: CHANGE_PASSWORD_URL, - formData: form, - method: 'POST', - credentials, - }) -} - -export const settingsMFA = ({ credentials }) => - promisedRequest({ - url: MFA_SETTINGS_URL, - credentials, - method: 'GET', - }) - -export const mfaDisableOTP = ({ credentials, password }) => { - const form = new FormData() - - form.append('password', password) - - return promisedRequest({ - url: MFA_DISABLE_OTP_URL, - formData: form, - method: 'DELETE', - credentials, - }) -} - -export const mfaConfirmOTP = ({ credentials, password, token }) => { - const form = new FormData() - - form.append('password', password) - form.append('code', token) - - return promisedRequest({ - url: MFA_CONFIRM_OTP_URL, - formData: form, - credentials, - method: 'POST', - }) -} -export const mfaSetupOTP = ({ credentials }) => - promisedRequest({ - url: MFA_SETUP_OTP_URL, - credentials, - method: 'GET', - }) -export const generateMfaBackupCodes = ({ credentials }) => - promisedRequest({ - url: MFA_BACKUP_CODES_URL, - credentials, - method: 'GET', - }) - -export const fetchMutes = ({ maxId, credentials }) => - promisedRequest({ - url: MASTODON_USER_MUTES_URL({ maxId, withRelationships: true }), - credentials, - }).then((users) => users.map(parseUser)) - -export const muteUser = ({ id, expiresIn, credentials }) => { - const payload = {} - if (expiresIn) { - payload.expires_in = expiresIn - } - - return promisedRequest({ - url: MASTODON_MUTE_USER_URL(id), - credentials, - method: 'POST', - payload, - }) -} - -export const unmuteUser = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_UNMUTE_USER_URL(id), - credentials, - method: 'POST', - }) - -export const fetchBlocks = ({ maxId, credentials }) => - promisedRequest({ - url: MASTODON_USER_BLOCKS_URL({ maxId, withRelationships: true }), - credentials, - }).then((users) => users.map(parseUser)) - -export const addBackup = ({ credentials }) => - promisedRequest({ - url: PLEROMA_BACKUP_URL, - method: 'POST', - credentials, - }) - -export const listBackups = ({ credentials }) => - promisedRequest({ - url: PLEROMA_BACKUP_URL, - method: 'GET', - credentials, - params: { - _cacheBooster: new Date().getTime(), - }, - }) - -export const fetchOAuthTokens = ({ credentials }) => - promisedRequest({ - url: '/api/oauth_tokens.json', - credentials, - }) - -export const revokeOAuthToken = ({ id, credentials }) => - promisedRequest({ - url: `/api/oauth_tokens/${id}`, - credentials, - method: 'DELETE', - }) - export const suggestions = ({ credentials }) => promisedRequest({ url: SUGGESTIONS_URL, credentials, }) -export const markNotificationsAsSeen = ({ - id, - credentials, - single = false, -}) => { - const formData = new FormData() - - if (single) { - formData.append('id', id) - } else { - formData.append('max_id', id) - } - - return promisedRequest({ - url: NOTIFICATION_READ_URL, - formData, - credentials, - method: 'POST', - }) -} - -export const vote = ({ pollId, choices, credentials }) => { - const form = new FormData() - form.append('choices', choices) - - return promisedRequest({ - url: MASTODON_VOTE_URL(encodeURIComponent(pollId)), - method: 'POST', - credentials, - payload: { - choices, - }, - }) -} - export const fetchPoll = ({ pollId, credentials }) => promisedRequest({ url: MASTODON_POLL_URL(encodeURIComponent(pollId)), @@ -1158,39 +347,6 @@ export const fetchEmojiReactions = ({ id, credentials }) => }), ) -export const reactWithEmoji = ({ id, emoji, credentials }) => - promisedRequest({ - url: PLEROMA_EMOJI_REACT_URL(id, emoji), - method: 'PUT', - credentials, - }).then(parseStatus) - -export const unreactWithEmoji = ({ id, emoji, credentials }) => - promisedRequest({ - url: PLEROMA_EMOJI_UNREACT_URL(id, emoji), - method: 'DELETE', - credentials, - }).then(parseStatus) - -export const reportUser = ({ - credentials, - userId, - statusIds, - comment, - forward, -}) => - promisedRequest({ - url: MASTODON_REPORT_USER_URL, - method: 'POST', - payload: { - account_id: userId, - status_ids: statusIds, - comment, - forward, - }, - credentials, - }) - export const searchUsers = ({ credentials, query }) => promisedRequest({ url: MASTODON_USER_SEARCH_URL, @@ -1261,43 +417,9 @@ export const search2 = ({ export const fetchKnownDomains = ({ credentials }) => promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials }) -export const fetchDomainMutes = ({ credentials }) => - promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials }) - -export const muteDomain = ({ domain, credentials }) => - promisedRequest({ - url: MASTODON_DOMAIN_BLOCKS_URL, - method: 'POST', - payload: { domain }, - credentials, - }) - -export const unmuteDomain = ({ domain, credentials }) => - promisedRequest({ - url: MASTODON_DOMAIN_BLOCKS_URL, - method: 'DELETE', - payload: { domain }, - credentials, - }) - -export const dismissNotification = ({ credentials, id }) => - promisedRequest({ - url: MASTODON_DISMISS_NOTIFICATION_URL(id), - method: 'POST', - payload: { id }, - credentials, - }) - export const getAnnouncements = ({ credentials }) => promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials }) -export const dismissAnnouncement = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_ANNOUNCEMENTS_DISMISS_URL(id), - credentials, - method: 'POST', - }) - export const getMastodonSocketURI = ( { credentials, stream, args = {} }, base, @@ -1473,112 +595,7 @@ export const WSConnectionStatus = Object.freeze({ STARTING_INITIAL: 6, }) -export const chats = ({ credentials }) => - promisedRequest({ - url: PLEROMA_CHATS_URL, - credentials, - }).then((data) => ({ - chatList: data.map(parseChat).filter((c) => c), - })) - -export const getOrCreateChat = ({ accountId, credentials }) => - promisedRequest({ - url: PLEROMA_CHAT_URL(accountId), - method: 'POST', - credentials, - }) - -export const chatMessages = ({ - id, - credentials, - maxId, - sinceId, - limit = 20, -}) => { - return promisedRequest({ - url: PLEROMA_CHAT_MESSAGES_URL(id, { maxId, sinceId, limit }), - method: 'GET', - credentials, - }) -} - -export const sendChatMessage = ({ - id, - content, - mediaId = null, - idempotencyKey, - credentials, -}) => { - const payload = { - content, - } - - if (mediaId) { - payload.media_id = mediaId - } - - const headers = {} - - if (idempotencyKey) { - headers['idempotency-key'] = idempotencyKey - } - - return promisedRequest({ - url: PLEROMA_CHAT_MESSAGES_URL(id), - method: 'POST', - payload, - credentials, - headers, - }) -} - -export const readChat = ({ id, lastReadId, credentials }) => - promisedRequest({ - url: PLEROMA_CHAT_READ_URL(id), - method: 'POST', - payload: { - last_read_id: lastReadId, - }, - credentials, - }) - -export const deleteChatMessage = ({ chatId, messageId, credentials }) => - promisedRequest({ - url: PLEROMA_DELETE_CHAT_MESSAGE_URL(chatId, messageId), - method: 'DELETE', - credentials, - }) - export const fetchScrobbles = ({ accountId, limit = 1 }) => promisedRequest({ url: PLEROMA_SCROBBLES_URL(accountId, { limit }), }) - -export const fetchBookmarkFolders = ({ credentials }) => - promisedRequest({ - url: PLEROMA_BOOKMARK_FOLDERS_URL, - credentials, - }) - -export const createBookmarkFolder = ({ name, emoji, credentials }) => - promisedRequest({ - url: PLEROMA_BOOKMARK_FOLDERS_URL, - credentials, - method: 'POST', - payload: { name, emoji }, - }) - -export const updateBookmarkFolder = ({ folderId, name, emoji, credentials }) => - promisedRequest({ - url: PLEROMA_BOOKMARK_FOLDER_URL(folderId), - credentials, - method: 'PATCH', - payload: { name, emoji }, - }) - -export const deleteBookmarkFolder = ({ folderId, credentials }) => - promisedRequest({ - url: PLEROMA_BOOKMARK_FOLDER_URL(folderId), - method: 'DELETE', - credentials, - }) diff --git a/src/services/api/chats.js b/src/services/api/chats.js new file mode 100644 index 000000000..61be62c52 --- /dev/null +++ b/src/services/api/chats.js @@ -0,0 +1,86 @@ +import { parseChat } from '../entity_normalizer/entity_normalizer.service.js' +import { paramsString, promisedRequest } from './helpers.js' + +const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats' +const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}` +const PLEROMA_CHAT_MESSAGES_URL = (id, { maxId, sinceId, limit }) => + `/api/v1/pleroma/chats/${id}/messages${paramsString({ maxId, sinceId, limit })}` +const PLEROMA_CHAT_READ_URL = (id) => `/api/v1/pleroma/chats/${id}/read` +const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => + `/api/v1/pleroma/chats/${chatId}/messages/${messageId}` + +export const chats = ({ credentials }) => + promisedRequest({ + url: PLEROMA_CHATS_URL, + credentials, + }).then((data) => ({ + chatList: data.map(parseChat).filter((c) => c), + })) + +export const getOrCreateChat = ({ accountId, credentials }) => + promisedRequest({ + url: PLEROMA_CHAT_URL(accountId), + method: 'POST', + credentials, + }) + +export const chatMessages = ({ + id, + credentials, + maxId, + sinceId, + limit = 20, +}) => { + return promisedRequest({ + url: PLEROMA_CHAT_MESSAGES_URL(id, { maxId, sinceId, limit }), + method: 'GET', + credentials, + }) +} + +export const sendChatMessage = ({ + id, + content, + mediaId = null, + idempotencyKey, + credentials, +}) => { + const payload = { + content, + } + + if (mediaId) { + payload.media_id = mediaId + } + + const headers = {} + + if (idempotencyKey) { + headers['idempotency-key'] = idempotencyKey + } + + return promisedRequest({ + url: PLEROMA_CHAT_MESSAGES_URL(id), + method: 'POST', + payload, + credentials, + headers, + }) +} + +export const readChat = ({ id, lastReadId, credentials }) => + promisedRequest({ + url: PLEROMA_CHAT_READ_URL(id), + method: 'POST', + payload: { + last_read_id: lastReadId, + }, + credentials, + }) + +export const deleteChatMessage = ({ chatId, messageId, credentials }) => + promisedRequest({ + url: PLEROMA_DELETE_CHAT_MESSAGE_URL(chatId, messageId), + method: 'DELETE', + credentials, + }) diff --git a/src/services/api/helpers.js b/src/services/api/helpers.js index c01f2958c..1e8f257fb 100644 --- a/src/services/api/helpers.js +++ b/src/services/api/helpers.js @@ -83,9 +83,11 @@ export const promisedRequest = async ({ ...headers, }, } + if (!formData) { options.headers['Content-Type'] = 'application/json' } + if (params) { url += '?' + @@ -112,15 +114,6 @@ export const promisedRequest = async ({ // 204 is "No content", which fails to parse json (as you'd might think) if (response.ok && response.status === 204) return { _response: response } - if (!response.ok) { - throw new StatusCodeError( - response.status, - json, - { url, options }, - response, - ) - } - try { const json = await response.json() @@ -133,6 +126,15 @@ export const promisedRequest = async ({ json._response = response + if (!response.ok) { + throw new StatusCodeError( + response.status, + json, + { url, options }, + response, + ) + } + return json } catch (error) { throw new StatusCodeError( diff --git a/src/services/api/user.js b/src/services/api/user.js new file mode 100644 index 000000000..bdfc8f295 --- /dev/null +++ b/src/services/api/user.js @@ -0,0 +1,926 @@ +import { concat, each, last, map } from 'lodash' + +import { + parseAttachment, + parseNotification, + parseSource, + parseStatus, + parseUser, +} from '../entity_normalizer/entity_normalizer.service.js' +import { fetchFriends, MASTODON_STATUS_URL } from './api.service.js' +import { paramsString, promisedRequest } from './helpers.js' + +const MUTES_IMPORT_URL = '/api/pleroma/mutes_import' +const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import' +const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' +const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' +const CHANGE_EMAIL_URL = '/api/pleroma/change_email' +const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' +const MOVE_ACCOUNT_URL = '/api/pleroma/move_account' +const ALIASES_URL = '/api/pleroma/aliases' +const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings' +const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read' + +const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa' +const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes' + +const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp' +const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp' +const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/totp' + +const MASTODON_DISMISS_NOTIFICATION_URL = (id) => + `/api/v1/notifications/${id}/dismiss` +const MASTODON_FAVORITE_URL = (id) => `/api/v1/statuses/${id}/favourite` +const MASTODON_UNFAVORITE_URL = (id) => `/api/v1/statuses/${id}/unfavourite` +const MASTODON_RETWEET_URL = (id) => `/api/v1/statuses/${id}/reblog` +const MASTODON_UNRETWEET_URL = (id) => `/api/v1/statuses/${id}/unreblog` +const MASTODON_DELETE_URL = (id) => `/api/v1/statuses/${id}` +const MASTODON_FOLLOW_URL = (id) => `/api/v1/accounts/${id}/follow` +const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow` + +const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests' +const MASTODON_APPROVE_USER_URL = (id) => + `/api/v1/follow_requests/${id}/authorize` +const MASTODON_DENY_USER_URL = (id) => `/api/v1/follow_requests/${id}/reject` +const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) => + `/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}` +const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists` +const MASTODON_LIST_URL = (id) => `/api/v1/lists/${id}` +const MASTODON_LIST_ACCOUNTS_URL = (id) => `/api/v1/lists/${id}/accounts` +const MASTODON_USER_BLOCKS_URL = ({ + maxId, + sinceId, + limit, + withRelationships, +}) => + `/api/v1/blocks/${paramsString({ maxId, sinceId, limit, withRelationships })}` +const MASTODON_USER_MUTES_URL = ({ + maxId, + sinceId, + limit, + withRelationships, +}) => + `/api/v1/mutes/${paramsString({ maxId, sinceId, limit, withRelationships })}` +const MASTODON_BLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/block` +const MASTODON_UNBLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/unblock` +const MASTODON_MUTE_USER_URL = (id) => `/api/v1/accounts/${id}/mute` +const MASTODON_UNMUTE_USER_URL = (id) => `/api/v1/accounts/${id}/unmute` +const MASTODON_REMOVE_USER_FROM_FOLLOWERS = (id) => + `/api/v1/accounts/${id}/remove_from_followers` +const MASTODON_USER_NOTE_URL = (id) => `/api/v1/accounts/${id}/note` +const MASTODON_BOOKMARK_STATUS_URL = (id) => `/api/v1/statuses/${id}/bookmark` +const MASTODON_UNBOOKMARK_STATUS_URL = (id) => + `/api/v1/statuses/${id}/unbookmark` +const MASTODON_POST_STATUS_URL = '/api/v1/statuses' +const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media' +const MASTODON_VOTE_URL = (id) => `/api/v1/polls/${id}/votes` +const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials' +const MASTODON_REPORT_USER_URL = '/api/v1/reports' +const MASTODON_PIN_OWN_STATUS = (id) => `/api/v1/statuses/${id}/pin` +const MASTODON_UNPIN_OWN_STATUS = (id) => `/api/v1/statuses/${id}/unpin` +const MASTODON_MUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/mute` +const MASTODON_UNMUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/unmute` +const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks' +const MASTODON_LISTS_URL = '/api/v1/lists' +const MASTODON_ANNOUNCEMENTS_DISMISS_URL = (id) => + `/api/v1/announcements/${id}/dismiss` +const PLEROMA_EMOJI_REACT_URL = (id, emoji) => + `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` +const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => + `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` +const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups' +const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders' +const PLEROMA_BOOKMARK_FOLDER_URL = (id) => + `/api/v1/pleroma/bookmark_folders/${id}` + +// #Posts +export const favorite = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_FAVORITE_URL(id), + method: 'POST', + credentials, + }).then((data) => parseStatus(data)) + +export const unfavorite = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_UNFAVORITE_URL(id), + method: 'POST', + credentials, + }).then((data) => parseStatus(data)) + +export const retweet = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_RETWEET_URL(id), + method: 'POST', + credentials, + }).then((data) => parseStatus(data)) + +export const unretweet = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_UNRETWEET_URL(id), + method: 'POST', + credentials, + }).then((data) => parseStatus(data)) + +export const reactWithEmoji = ({ id, emoji, credentials }) => + promisedRequest({ + url: PLEROMA_EMOJI_REACT_URL(id, emoji), + method: 'PUT', + credentials, + }).then(parseStatus) + +export const unreactWithEmoji = ({ id, emoji, credentials }) => + promisedRequest({ + url: PLEROMA_EMOJI_UNREACT_URL(id, emoji), + method: 'DELETE', + credentials, + }).then(parseStatus) + +export const bookmarkStatus = ({ id, credentials, ...options }) => + promisedRequest({ + url: MASTODON_BOOKMARK_STATUS_URL(id), + credentials, + method: 'POST', + payload: { + folder_id: options.folder_id, + }, + }) + +export const unbookmarkStatus = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_UNBOOKMARK_STATUS_URL(id), + credentials, + method: 'POST', + }) + +export const pinOwnStatus = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_PIN_OWN_STATUS(id), + credentials, + method: 'POST', + }).then((data) => parseStatus(data)) + +export const unpinOwnStatus = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_UNPIN_OWN_STATUS(id), + credentials, + method: 'POST', + }).then((data) => parseStatus(data)) + +export const muteConversation = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_MUTE_CONVERSATION(id), + credentials, + method: 'POST', + }).then((data) => parseStatus(data)) + +export const unmuteConversation = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_UNMUTE_CONVERSATION(id), + credentials, + method: 'POST', + }).then((data) => parseStatus(data)) + +export const vote = ({ pollId, choices, credentials }) => { + const form = new FormData() + form.append('choices', choices) + + return promisedRequest({ + url: MASTODON_VOTE_URL(encodeURIComponent(pollId)), + method: 'POST', + credentials, + payload: { + choices, + }, + }) +} + +// #Posting +export const postStatus = ({ + credentials, + status, + spoilerText, + visibility, + sensitive, + poll, + mediaIds = [], + inReplyToStatusId, + quoteId, + contentType, + preview, + idempotencyKey, +}) => { + const form = new FormData() + const pollOptions = poll.options || [] + + form.append('status', status) + form.append('source', 'Pleroma FE') + if (spoilerText) form.append('spoiler_text', spoilerText) + if (visibility) form.append('visibility', visibility) + if (sensitive) form.append('sensitive', sensitive) + if (contentType) form.append('content_type', contentType) + mediaIds.forEach((val) => { + form.append('media_ids[]', val) + }) + if (pollOptions.some((option) => option !== '')) { + const normalizedPoll = { + expires_in: parseInt(poll.expiresIn, 10), + multiple: poll.multiple, + } + Object.keys(normalizedPoll).forEach((key) => { + form.append(`poll[${key}]`, normalizedPoll[key]) + }) + + pollOptions.forEach((option) => { + form.append('poll[options][]', option) + }) + } + if (inReplyToStatusId) { + form.append('in_reply_to_id', inReplyToStatusId) + } + if (quoteId) { + form.append('quote_id', quoteId) + } + if (preview) { + form.append('preview', 'true') + } + + const headers = {} + if (idempotencyKey) { + headers['idempotency-key'] = idempotencyKey + } + + return promisedRequest({ + url: MASTODON_POST_STATUS_URL, + formData: form, + method: 'POST', + credentials, + headers, + }).then((data) => (data.error ? data : parseStatus(data))) +} + +export const editStatus = ({ + id, + credentials, + status, + spoilerText, + sensitive, + poll, + mediaIds = [], + contentType, +}) => { + const form = new FormData() + const pollOptions = poll.options || [] + + form.append('status', status) + if (spoilerText) form.append('spoiler_text', spoilerText) + if (sensitive) form.append('sensitive', sensitive) + if (contentType) form.append('content_type', contentType) + mediaIds.forEach((val) => { + form.append('media_ids[]', val) + }) + + if (pollOptions.some((option) => option !== '')) { + const normalizedPoll = { + expires_in: parseInt(poll.expiresIn, 10), + multiple: poll.multiple, + } + Object.keys(normalizedPoll).forEach((key) => { + form.append(`poll[${key}]`, normalizedPoll[key]) + }) + + pollOptions.forEach((option) => { + form.append('poll[options][]', option) + }) + } + + return promisedRequest({ + url: MASTODON_STATUS_URL(id), + formData: form, + method: 'PUT', + credentials, + }).then((data) => (data.error ? data : parseStatus(data))) +} + +export const deleteStatus = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_DELETE_URL(id), + credentials, + method: 'DELETE', + }) + +export const uploadMedia = ({ formData, credentials }) => + promisedRequest({ + url: MASTODON_MEDIA_UPLOAD_URL, + formData, + method: 'POST', + credentials, + }).then((data) => parseAttachment(data)) + +export const setMediaDescription = ({ id, description, credentials }) => + promisedRequest({ + url: `${MASTODON_MEDIA_UPLOAD_URL}/${id}`, + method: 'PUT', + credentials, + payload: { + description, + }, + }).then((data) => parseAttachment(data)) + +// #Notifications +export const dismissNotification = ({ credentials, id }) => + promisedRequest({ + url: MASTODON_DISMISS_NOTIFICATION_URL(id), + method: 'POST', + payload: { id }, + credentials, + }) + +export const dismissAnnouncement = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_ANNOUNCEMENTS_DISMISS_URL(id), + credentials, + method: 'POST', + }) + +export const markNotificationsAsSeen = ({ + id, + credentials, + single = false, +}) => { + const formData = new FormData() + + if (single) { + formData.append('id', id) + } else { + formData.append('max_id', id) + } + + return promisedRequest({ + url: NOTIFICATION_READ_URL, + formData, + credentials, + method: 'POST', + }) +} + +// #Imports +export const importMutes = ({ file, credentials }) => { + const formData = new FormData() + formData.append('list', file) + return promisedRequest({ + url: MUTES_IMPORT_URL, + formData, + method: 'POST', + credentials, + }).then((response) => response.ok) +} + +export const importBlocks = ({ file, credentials }) => { + const formData = new FormData() + formData.append('list', file) + return promisedRequest({ + url: BLOCKS_IMPORT_URL, + formData, + method: 'POST', + credentials, + }).then((response) => response.ok) +} + +export const importFollows = ({ file, credentials }) => { + const formData = new FormData() + formData.append('list', file) + return promisedRequest({ + url: FOLLOW_IMPORT_URL, + formData, + method: 'POST', + credentials, + }).then((response) => response.ok) +} + +export const exportFriends = ({ id, credentials }) => { + // biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO refactor this + return new Promise(async (resolve, reject) => { + try { + let friends = [] + let more = true + while (more) { + const maxId = friends.length > 0 ? last(friends).id : undefined + const users = await fetchFriends({ + id, + maxId, + credentials, + withRelationships: true, + }) + friends = concat(friends, users) + if (users.length === 0) { + more = false + } + } + resolve(friends) + } catch (err) { + reject(err) + } + }) +} + +// #Profile settings +export const updateNotificationSettings = ({ credentials, settings }) => { + return promisedRequest({ + url: NOTIFICATION_SETTINGS_URL, + credentials, + method: 'PUT', + payload: settings, + }) +} + +export const updateProfileImages = ({ + credentials, + avatar = null, + avatarName = null, + banner = null, + background = null, +}) => { + const form = new FormData() + if (avatar !== null) { + if (avatarName !== null) { + form.append('avatar', avatar, avatarName) + } else { + form.append('avatar', avatar) + } + } + if (banner !== null) form.append('header', banner) + if (background !== null) form.append('pleroma_background_image', background) + return promisedRequest({ + url: MASTODON_PROFILE_UPDATE_URL, + credentials, + method: 'PATCH', + formData: form, + }).then((data) => { + if (data.error) { + throw new Error(data.error) + } + return parseUser(data) + }) +} + +export const updateProfile = ({ credentials, params }) => { + const formData = new FormData() + + for (const name in params) { + if (name === 'fields_attributes') { + params[name].forEach((param, i) => { + formData.append(name + `[${i}][name]`, param.name) + formData.append(name + `[${i}][value]`, param.value) + }) + } else { + if (typeof params[name] === 'object') { + console.warn( + 'Object detected in updateProfile API call. This will not work, use updateProfileJSON instead.', + ) + console.warn('Object:\n' + JSON.stringify(params[name], null, 2)) + } + formData.append(name, params[name]) + } + } + + return promisedRequest({ + url: MASTODON_PROFILE_UPDATE_URL, + credentials, + method: 'PATCH', + formData, + }).then((data) => parseUser(data)) +} + +export const updateProfileJSON = ({ credentials, params }) => + promisedRequest({ + url: MASTODON_PROFILE_UPDATE_URL, + credentials, + payload: params, + method: 'PATCH', + }).then((data) => parseUser(data)) + +export const changeEmail = ({ credentials, email, password }) => { + const form = new FormData() + + form.append('email', email) + form.append('password', password) + + return promisedRequest({ + url: CHANGE_EMAIL_URL, + formData: form, + method: 'POST', + credentials, + }) +} + +export const moveAccount = ({ credentials, password, targetAccount }) => { + const form = new FormData() + + form.append('password', password) + form.append('target_account', targetAccount) + + return promisedRequest({ + url: MOVE_ACCOUNT_URL, + formData: form, + method: 'POST', + credentials, + }) +} + +export const changePassword = ({ + credentials, + password, + newPassword, + newPasswordConfirmation, +}) => { + const form = new FormData() + + form.append('password', password) + form.append('new_password', newPassword) + form.append('new_password_confirmation', newPasswordConfirmation) + + return promisedRequest({ + url: CHANGE_PASSWORD_URL, + formData: form, + method: 'POST', + credentials, + }) +} + +// #MFA +export const settingsMFA = ({ credentials }) => + promisedRequest({ + url: MFA_SETTINGS_URL, + credentials, + method: 'GET', + }) + +export const mfaDisableOTP = ({ credentials, password }) => { + const form = new FormData() + + form.append('password', password) + + return promisedRequest({ + url: MFA_DISABLE_OTP_URL, + formData: form, + method: 'DELETE', + credentials, + }) +} + +export const mfaConfirmOTP = ({ credentials, password, token }) => { + const form = new FormData() + + form.append('password', password) + form.append('code', token) + + return promisedRequest({ + url: MFA_CONFIRM_OTP_URL, + formData: form, + credentials, + method: 'POST', + }) +} +export const mfaSetupOTP = ({ credentials }) => + promisedRequest({ + url: MFA_SETUP_OTP_URL, + credentials, + method: 'GET', + }) +export const generateMfaBackupCodes = ({ credentials }) => + promisedRequest({ + url: MFA_BACKUP_CODES_URL, + credentials, + method: 'GET', + }) + +// #Aliases +export const addAlias = ({ credentials, alias }) => + promisedRequest({ + url: ALIASES_URL, + method: 'PUT', + credentials, + payload: { alias }, + }) + +export const deleteAlias = ({ credentials, alias }) => + promisedRequest({ + url: ALIASES_URL, + method: 'DELETE', + credentials, + payload: { alias }, + }) + +export const listAliases = ({ credentials }) => + promisedRequest({ + url: ALIASES_URL, + method: 'GET', + credentials, + params: { + _cacheBooster: new Date().getTime(), + }, + }) + +// User manipulation +export const fetchUserRelationship = ({ id, withSuspended, credentials }) => + promisedRequest({ + url: MASTODON_USER_RELATIONSHIPS_URL({ id, withSuspended }), + credentials, + }) + +export const followUser = ({ id, credentials, ...options }) => { + const payload = {} + + if (options.reblogs !== undefined) { + payload.reblogs = options.reblogs + } + + if (options.notify !== undefined) { + payload.notify = options.notify + } + + return promisedRequest({ + url: MASTODON_FOLLOW_URL(id), + payload, + credentials, + method: 'POST', + }) +} + +export const unfollowUser = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_UNFOLLOW_URL(id), + credentials, + method: 'POST', + }) +export const fetchUserInLists = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_USER_IN_LISTS(id), + credentials, + }) + +export const removeUserFromFollowers = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_REMOVE_USER_FROM_FOLLOWERS(id), + credentials, + method: 'POST', + }) + +export const fetchFollowRequests = ({ credentials }) => + promisedRequest({ + url: MASTODON_FOLLOW_REQUESTS_URL, + credentials, + }).then((data) => data.map(parseUser)) + +export const approveUser = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_APPROVE_USER_URL(id), + credentials, + method: 'POST', + }) + +export const denyUser = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_DENY_USER_URL(id), + credentials, + method: 'POST', + }) + +export const editUserNote = ({ id, credentials, comment }) => + promisedRequest({ + url: MASTODON_USER_NOTE_URL(id), + credentials, + payload: { + comment, + }, + method: 'POST', + }) + +export const fetchMutes = ({ maxId, credentials }) => + promisedRequest({ + url: MASTODON_USER_MUTES_URL({ maxId, withRelationships: true }), + credentials, + }).then((users) => users.map(parseUser)) + +export const muteUser = ({ id, expiresIn, credentials }) => { + const payload = {} + if (expiresIn) { + payload.expires_in = expiresIn + } + + return promisedRequest({ + url: MASTODON_MUTE_USER_URL(id), + credentials, + method: 'POST', + payload, + }) +} + +export const unmuteUser = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_UNMUTE_USER_URL(id), + credentials, + method: 'POST', + }) + +export const fetchBlocks = ({ maxId, credentials }) => + promisedRequest({ + url: MASTODON_USER_BLOCKS_URL({ maxId, withRelationships: true }), + credentials, + }).then((users) => users.map(parseUser)) + +export const blockUser = ({ id, expiresIn, credentials }) => { + const payload = {} + if (expiresIn) { + payload.duration = expiresIn + } + + return promisedRequest({ + url: MASTODON_BLOCK_USER_URL(id), + credentials, + method: 'POST', + payload, + }) +} + +export const unblockUser = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_UNBLOCK_USER_URL(id), + credentials, + method: 'POST', + }) + +export const reportUser = ({ + credentials, + userId, + statusIds, + comment, + forward, +}) => + promisedRequest({ + url: MASTODON_REPORT_USER_URL, + method: 'POST', + payload: { + account_id: userId, + status_ids: statusIds, + comment, + forward, + }, + credentials, + }) + +// #Domain mutes +export const fetchDomainMutes = ({ credentials }) => + promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials }) + +export const muteDomain = ({ domain, credentials }) => + promisedRequest({ + url: MASTODON_DOMAIN_BLOCKS_URL, + method: 'POST', + payload: { domain }, + credentials, + }) + +export const unmuteDomain = ({ domain, credentials }) => + promisedRequest({ + url: MASTODON_DOMAIN_BLOCKS_URL, + method: 'DELETE', + payload: { domain }, + credentials, + }) + +// #Backups +export const addBackup = ({ credentials }) => + promisedRequest({ + url: PLEROMA_BACKUP_URL, + method: 'POST', + credentials, + }) + +export const listBackups = ({ credentials }) => + promisedRequest({ + url: PLEROMA_BACKUP_URL, + method: 'GET', + credentials, + params: { + _cacheBooster: new Date().getTime(), + }, + }) + +// #OAuth +export const fetchOAuthTokens = ({ credentials }) => + promisedRequest({ + url: '/api/oauth_tokens.json', + credentials, + }) + +export const revokeOAuthToken = ({ id, credentials }) => + promisedRequest({ + url: `/api/oauth_tokens/${id}`, + credentials, + method: 'DELETE', + }) + +// #Lists +export const fetchLists = ({ credentials }) => + promisedRequest({ + url: MASTODON_LISTS_URL, + credentials, + }) + +export const createList = ({ title, credentials }) => + promisedRequest({ + url: MASTODON_LISTS_URL, + credentials, + method: 'POST', + payload: { title }, + }) + +export const getList = ({ listId, credentials }) => + promisedRequest({ + url: MASTODON_LIST_URL(listId), + credentials, + }) + +export const updateList = ({ listId, title, credentials }) => + promisedRequest({ + url: MASTODON_LIST_URL(listId), + + credentials, + method: 'PUT', + payload: { title }, + }) + +export const getListAccounts = ({ listId, credentials }) => + promisedRequest({ + url: MASTODON_LIST_ACCOUNTS_URL(listId), + credentials, + }).then((data) => data.map(({ id }) => id)) + +export const addAccountsToList = ({ listId, accountIds, credentials }) => + promisedRequest({ + url: MASTODON_LIST_ACCOUNTS_URL(listId), + credentials, + method: 'POST', + payload: { account_ids: accountIds }, + }) + +export const removeAccountsFromList = ({ listId, accountIds, credentials }) => + promisedRequest({ + url: MASTODON_LIST_ACCOUNTS_URL(listId), + credentials, + method: 'DELETE', + payload: { account_ids: accountIds }, + }) + +export const deleteList = ({ listId, credentials }) => + promisedRequest({ + url: MASTODON_LIST_URL(listId), + method: 'DELETE', + credentials, + }) + +// #Bookmarks +export const fetchBookmarkFolders = ({ credentials }) => + promisedRequest({ + url: PLEROMA_BOOKMARK_FOLDERS_URL, + credentials, + }) + +export const createBookmarkFolder = ({ name, emoji, credentials }) => + promisedRequest({ + url: PLEROMA_BOOKMARK_FOLDERS_URL, + credentials, + method: 'POST', + payload: { name, emoji }, + }) + +export const updateBookmarkFolder = ({ folderId, name, emoji, credentials }) => + promisedRequest({ + url: PLEROMA_BOOKMARK_FOLDER_URL(folderId), + credentials, + method: 'PATCH', + payload: { name, emoji }, + }) + +export const deleteBookmarkFolder = ({ folderId, credentials }) => + promisedRequest({ + url: PLEROMA_BOOKMARK_FOLDER_URL(folderId), + method: 'DELETE', + credentials, + }) + +// #So long and thanks for all the fish +export const deleteAccount = ({ credentials, password }) => { + const formData = new FormData() + + formData.append('password', password) + + return promisedRequest({ + url: DELETE_ACCOUNT_URL, + formData, + method: 'POST', + credentials, + }) +} diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index 1a3b2395c..d4af7f8c2 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -4,7 +4,7 @@ import { fetchUserRelationship, followUser, unfollowUser, -} from 'src/services/api/api.service.js' +} from 'src/services/api/user.js' const fetchRelationship = (attempt, userId, store) => new Promise((resolve, reject) => { diff --git a/src/services/follow_request_fetcher/follow_request_fetcher.service.js b/src/services/follow_request_fetcher/follow_request_fetcher.service.js index 015eb55ed..a6521601c 100644 --- a/src/services/follow_request_fetcher/follow_request_fetcher.service.js +++ b/src/services/follow_request_fetcher/follow_request_fetcher.service.js @@ -1,5 +1,5 @@ -import { fetchFollowRequests } from '../api/api.service.js' -import { promiseInterval } from '../promise_interval/promise_interval.js' +import { fetchFollowRequests } from 'src/services/api/user.js' +import { promiseInterval } from 'src/services/promise_interval/promise_interval.js' const fetchAndUpdate = ({ store, credentials }) => { return fetchFollowRequests({ credentials }) diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 1a814ec7c..8b50e0f35 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -5,7 +5,7 @@ import { postStatus as apiPostStatus, setMediaDescription as apiSetMediaDescription, uploadMedia as apiUploadMedia, -} from '../api/api.service.js' +} from 'src/services/api/user.js' const postStatus = ({ store, diff --git a/src/stores/announcements.js b/src/stores/announcements.js index 87d83813a..27a25d195 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -8,10 +8,8 @@ import { editAnnouncement, postAnnouncement, } from 'src/services/api/admin.js' -import { - dismissAnnouncement, - getAnnouncements, -} from 'src/services/api/api.service.js' +import { getAnnouncements } from 'src/services/api/api.service.js' +import { dismissAnnouncement } from 'src/services/api/user.js' const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5 diff --git a/src/stores/bookmark_folders.js b/src/stores/bookmark_folders.js index 3e16121b1..c5c884f82 100644 --- a/src/stores/bookmark_folders.js +++ b/src/stores/bookmark_folders.js @@ -8,7 +8,7 @@ import { deleteBookmarkFolder, fetchBookmarkFolders, updateBookmarkFolder, -} from 'src/services/api/api.service.js' +} from 'src/services/api/user.js' import { promiseInterval } from 'src/services/promise_interval/promise_interval.js' export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { diff --git a/src/stores/lists.js b/src/stores/lists.js index c361b6e5e..6431e1d2f 100644 --- a/src/stores/lists.js +++ b/src/stores/lists.js @@ -12,7 +12,7 @@ import { getListAccounts, removeAccountsFromList, updateList, -} from 'src/services/api/api.service.js' +} from 'src/services/api/user.js' import { promiseInterval } from 'src/services/promise_interval/promise_interval.js' export const useListsStore = defineStore('lists', { diff --git a/src/stores/oauth_tokens.js b/src/stores/oauth_tokens.js index 3f9138cec..55914386b 100644 --- a/src/stores/oauth_tokens.js +++ b/src/stores/oauth_tokens.js @@ -2,10 +2,7 @@ import { defineStore } from 'pinia' import { useOAuthStore } from 'src/stores/oauth.js' -import { - fetchOAuthTokens, - revokeOAuthToken, -} from 'src/services/api/api.service.js' +import { fetchOAuthTokens, revokeOAuthToken } from 'src/services/api/user.js' export const useOAuthTokensStore = defineStore('oauthTokens', { state: () => ({ diff --git a/src/stores/polls.js b/src/stores/polls.js index 78ccd059b..db09101ae 100644 --- a/src/stores/polls.js +++ b/src/stores/polls.js @@ -3,7 +3,8 @@ import { defineStore } from 'pinia' import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchPoll, vote } from 'src/services/api/api.service.js' +import { fetchPoll } from 'src/services/api/api.service.js' +import { vote } from 'src/services/api/user.js' export const usePollsStore = defineStore('polls', { state: () => ({ diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js index 348579b72..bdcc6cdb9 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -32,7 +32,7 @@ import { validateSetting, } from 'src/modules/default_config_state.js' import { oldDefaultConfigSync } from 'src/modules/old_default_config_state.js' -import { updateProfileJSON } from 'src/services/api/api.service.js' +import { updateProfileJSON } from 'src/services/api/user.js' export const VERSION = 2 export const NEW_USER_DATE = new Date('2026-03-16') // date of writing this, basically diff --git a/src/stores/user_highlight.js b/src/stores/user_highlight.js index b2cd680f5..08b739d09 100644 --- a/src/stores/user_highlight.js +++ b/src/stores/user_highlight.js @@ -17,7 +17,7 @@ import { toRaw } from 'vue' import { useOAuthStore } from 'src/stores/oauth.js' import { storage } from 'src/lib/storage.js' -import { updateProfileJSON } from 'src/services/api/api.service.js' +import { updateProfileJSON } from 'src/services/api/user.js' export const NEW_USER_DATE = new Date('2022-08-04') // date of writing this, basically From baf283123e83c74181fe7046982fba8f0b90f054 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 14:30:01 +0300 Subject: [PATCH 24/86] api.service.js -> public.js --- src/components/chat/chat.js | 2 +- src/components/conversation/conversation.js | 4 ++-- src/components/remote_user_resolver/remote_user_resolver.js | 2 +- src/components/who_to_follow/who_to_follow.js | 2 +- src/components/who_to_follow_panel/who_to_follow_panel.js | 2 +- src/modules/api.js | 4 ++-- src/modules/statuses.js | 2 +- src/modules/users.js | 4 ++-- src/services/api/{api.service.js => public.js} | 0 src/services/api/user.js | 2 +- .../notifications_fetcher/notifications_fetcher.service.js | 2 +- src/services/timeline_fetcher/timeline_fetcher.service.js | 2 +- src/stores/admin_settings.js | 2 +- src/stores/announcements.js | 2 +- src/stores/emoji.js | 2 +- src/stores/instance.js | 2 +- src/stores/polls.js | 2 +- 17 files changed, 19 insertions(+), 19 deletions(-) rename src/services/api/{api.service.js => public.js} (100%) diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index 3ea9e3fd6..4249d3f3f 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -5,7 +5,7 @@ import { mapGetters, mapState } from 'vuex' import ChatMessage from 'src/components/chat_message/chat_message.vue' import ChatTitle from 'src/components/chat_title/chat_title.vue' import PostStatusForm from 'src/components/post_status_form/post_status_form.vue' -import { WSConnectionStatus } from '../../services/api/api.service.js' +import { WSConnectionStatus } from '../../services/api/public.js' import chatService from '../../services/chat_service/chat_service.js' import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js' import { promiseInterval } from '../../services/promise_interval/promise_interval.js' diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 6bc0b7ad0..d3d0ac949 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -5,13 +5,13 @@ import { mapState } from 'vuex' import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filter_settings.vue' import QuickViewSettings from 'src/components/quick_view_settings/quick_view_settings.vue' import ThreadTree from 'src/components/thread_tree/thread_tree.vue' -import { WSConnectionStatus } from '../../services/api/api.service.js' +import { WSConnectionStatus } from '../../services/api/public.js' import { useInterfaceStore } from 'src/stores/interface' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchConversation, fetchStatus } from 'src/services/api/api.service.js' +import { fetchConversation, fetchStatus } from 'src/services/api/public.js' import { library } from '@fortawesome/fontawesome-svg-core' import { diff --git a/src/components/remote_user_resolver/remote_user_resolver.js b/src/components/remote_user_resolver/remote_user_resolver.js index bdef5b2a4..d68f6e608 100644 --- a/src/components/remote_user_resolver/remote_user_resolver.js +++ b/src/components/remote_user_resolver/remote_user_resolver.js @@ -1,6 +1,6 @@ import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchUser } from 'src/services/api/api.service.js' +import { fetchUser } from 'src/services/api/public.js' const RemoteUserResolver = { data: () => ({ diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js index b7bb24b45..026f682ae 100644 --- a/src/components/who_to_follow/who_to_follow.js +++ b/src/components/who_to_follow/who_to_follow.js @@ -3,7 +3,7 @@ import FollowCard from 'src/components/follow_card/follow_card.vue' import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchUser, suggestions } from 'src/services/api/api.service.js' +import { fetchUser, suggestions } from 'src/services/api/public.js' const WhoToFollow = { components: { diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js index fa1c12278..b8575396d 100644 --- a/src/components/who_to_follow_panel/who_to_follow_panel.js +++ b/src/components/who_to_follow_panel/who_to_follow_panel.js @@ -4,7 +4,7 @@ import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchUser, suggestions } from 'src/services/api/api.service.js' +import { fetchUser, suggestions } from 'src/services/api/public.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' function showWhoToFollow(panel, reply) { diff --git a/src/modules/api.js b/src/modules/api.js index c7de7a070..352509282 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -1,6 +1,6 @@ import { Socket } from 'phoenix' -import { WSConnectionStatus } from '../services/api/api.service.js' +import { WSConnectionStatus } from '../services/api/public.js' import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -13,7 +13,7 @@ import { fetchTimeline, getMastodonSocketURI, ProcessedWS, -} from 'src/services/api/api.service.js' +} from 'src/services/api/public.js' import followRequestFetcher from 'src/services/follow_request_fetcher/follow_request_fetcher.service' import notificationsFetcher from 'src/services/notifications_fetcher/notifications_fetcher.service.js' import timelineFetcher from 'src/services/timeline_fetcher/timeline_fetcher.service.js' diff --git a/src/modules/statuses.js b/src/modules/statuses.js index c0cffb7ed..f62ee6b86 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -27,7 +27,7 @@ import { fetchStatusHistory, fetchStatusSource, search2, -} from 'src/services/api/api.service.js' +} from 'src/services/api/public.js' import { bookmarkStatus, deleteStatus, diff --git a/src/modules/users.js b/src/modules/users.js index c4aa9a1c5..1cd5139eb 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -9,7 +9,7 @@ import { uniq, } from 'lodash' -import { register } from '../services/api/api.service.js' +import { register } from '../services/api/public.js' import oauthApi from '../services/new_api/oauth.js' import { registerPushNotifications, @@ -39,7 +39,7 @@ import { getCaptcha, searchUsers, verifyCredentials, -} from 'src/services/api/api.service.js' +} from 'src/services/api/public.js' import { fetchBlocks, fetchDomainMutes, diff --git a/src/services/api/api.service.js b/src/services/api/public.js similarity index 100% rename from src/services/api/api.service.js rename to src/services/api/public.js diff --git a/src/services/api/user.js b/src/services/api/user.js index bdfc8f295..28413e6c9 100644 --- a/src/services/api/user.js +++ b/src/services/api/user.js @@ -7,8 +7,8 @@ import { parseStatus, parseUser, } from '../entity_normalizer/entity_normalizer.service.js' -import { fetchFriends, MASTODON_STATUS_URL } from './api.service.js' import { paramsString, promisedRequest } from './helpers.js' +import { fetchFriends, MASTODON_STATUS_URL } from './public.js' const MUTES_IMPORT_URL = '/api/pleroma/mutes_import' const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import' diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index e8fa3d16d..a27bf6047 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -1,4 +1,4 @@ -import { fetchTimeline } from '../api/api.service.js' +import { fetchTimeline } from '../api/public.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useInstanceStore } from 'src/stores/instance.js' diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 510e97ab0..496b1a4ea 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -1,6 +1,6 @@ import { camelCase } from 'lodash' -import { fetchTimeline } from '../api/api.service.js' +import { fetchTimeline } from '../api/public.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useInstanceStore } from 'src/stores/instance.js' diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index 884ac37a1..88f655481 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -32,7 +32,7 @@ import { setUsersSuggestionStatus, setUsersTags, } from 'src/services/api/admin.js' -import { listEmojiPacks } from 'src/services/api/api.service.js' +import { listEmojiPacks } from 'src/services/api/public.js' import { parseStatus } from 'src/services/entity_normalizer/entity_normalizer.service.js' export const defaultState = { diff --git a/src/stores/announcements.js b/src/stores/announcements.js index 27a25d195..9a2560db5 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -8,7 +8,7 @@ import { editAnnouncement, postAnnouncement, } from 'src/services/api/admin.js' -import { getAnnouncements } from 'src/services/api/api.service.js' +import { getAnnouncements } from 'src/services/api/public.js' import { dismissAnnouncement } from 'src/services/api/user.js' const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5 diff --git a/src/stores/emoji.js b/src/stores/emoji.js index 079dedb9f..5fae78ce2 100644 --- a/src/stores/emoji.js +++ b/src/stores/emoji.js @@ -5,7 +5,7 @@ import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' import { ensureFinalFallback } from 'src/i18n/languages.js' -import { listEmojiPacks } from 'src/services/api/api.service.js' +import { listEmojiPacks } from 'src/services/api/public.js' import { annotationsLoader } from 'virtual:pleroma-fe/emoji-annotations' diff --git a/src/stores/instance.js b/src/stores/instance.js index 2f9bf3c44..17a13765a 100644 --- a/src/stores/instance.js +++ b/src/stores/instance.js @@ -11,7 +11,7 @@ import { LOCAL_DEFAULT_CONFIG_DEFINITIONS, validateSetting, } from '../modules/default_config_state.js' -import { fetchKnownDomains } from '../services/api/api.service.js' +import { fetchKnownDomains } from '../services/api/public.js' import { useInterfaceStore } from 'src/stores/interface.js' diff --git a/src/stores/polls.js b/src/stores/polls.js index db09101ae..74812a24e 100644 --- a/src/stores/polls.js +++ b/src/stores/polls.js @@ -3,7 +3,7 @@ import { defineStore } from 'pinia' import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchPoll } from 'src/services/api/api.service.js' +import { fetchPoll } from 'src/services/api/public.js' import { vote } from 'src/services/api/user.js' export const usePollsStore = defineStore('polls', { From c81813064b21caad6254a523da62d5706c987bd3 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 14:36:45 +0300 Subject: [PATCH 25/86] src/services/api -> src/api --- src/{services => }/api/admin.js | 0 src/{services => }/api/chats.js | 2 +- src/{services => }/api/helpers.js | 0 src/{services => }/api/public.js | 2 +- src/{services => }/api/user.js | 2 +- .../bookmark_folder_edit.js | 2 +- src/components/chat/chat.js | 4 ++-- src/components/chat_new/chat_new.js | 2 +- src/components/conversation/conversation.js | 6 +++--- .../follow_request_card.js | 2 +- src/components/notification/notification.js | 2 +- .../remote_user_resolver.js | 2 +- .../helpers/emoji_editing_popover.vue | 2 +- .../settings_modal/tabs/appearance_tab.js | 4 ++-- .../settings_modal/tabs/composing_tab.js | 4 ++-- .../tabs/data_import_export_tab.js | 2 +- .../settings_modal/tabs/general_tab.js | 4 ++-- .../tabs/mutes_and_blocks_tab.js | 2 +- .../settings_modal/tabs/notifications_tab.js | 2 +- .../settings_modal/tabs/profile_tab.js | 2 +- .../settings_modal/tabs/security_tab/mfa.js | 2 +- .../tabs/security_tab/mfa_totp.js | 2 +- .../tabs/security_tab/security_tab.js | 2 +- src/components/user_card/user_card.js | 2 +- .../user_reporting_modal.js | 2 +- src/components/who_to_follow/who_to_follow.js | 2 +- .../who_to_follow_panel.js | 2 +- src/modules/api.js | 4 ++-- src/modules/chats.js | 2 +- src/modules/notifications.js | 4 ++-- src/modules/profileConfig.js | 2 +- src/modules/statuses.js | 4 ++-- src/modules/users.js | 6 +++--- .../follow_manipulate/follow_manipulate.js | 2 +- .../follow_request_fetcher.service.js | 2 +- .../notifications_fetcher.service.js | 2 +- .../status_poster/status_poster.service.js | 2 +- .../timeline_fetcher.service.js | 2 +- src/stores/admin_settings.js | 6 +++--- src/stores/announcements.js | 6 +++--- src/stores/bookmark_folders.js | 2 +- src/stores/emoji.js | 20 +++++++++---------- src/stores/instance.js | 2 +- src/stores/lists.js | 2 +- src/stores/oauth_tokens.js | 2 +- src/stores/polls.js | 4 ++-- src/stores/reports.js | 2 +- src/stores/sync_config.js | 4 ++-- src/stores/user_highlight.js | 2 +- test/unit/specs/services/api/helpers.spec.js | 2 +- 50 files changed, 74 insertions(+), 74 deletions(-) rename src/{services => }/api/admin.js (100%) rename src/{services => }/api/chats.js (95%) rename src/{services => }/api/helpers.js (100%) rename src/{services => }/api/public.js (99%) rename src/{services => }/api/user.js (99%) diff --git a/src/services/api/admin.js b/src/api/admin.js similarity index 100% rename from src/services/api/admin.js rename to src/api/admin.js diff --git a/src/services/api/chats.js b/src/api/chats.js similarity index 95% rename from src/services/api/chats.js rename to src/api/chats.js index 61be62c52..4a0c55752 100644 --- a/src/services/api/chats.js +++ b/src/api/chats.js @@ -1,4 +1,4 @@ -import { parseChat } from '../entity_normalizer/entity_normalizer.service.js' +import { parseChat } from 'src/services/entity_normalizer/entity_normalizer.service.js' import { paramsString, promisedRequest } from './helpers.js' const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats' diff --git a/src/services/api/helpers.js b/src/api/helpers.js similarity index 100% rename from src/services/api/helpers.js rename to src/api/helpers.js diff --git a/src/services/api/public.js b/src/api/public.js similarity index 99% rename from src/services/api/public.js rename to src/api/public.js index 66847a32b..b0aa68140 100644 --- a/src/services/api/public.js +++ b/src/api/public.js @@ -8,7 +8,7 @@ import { parseSource, parseStatus, parseUser, -} from '../entity_normalizer/entity_normalizer.service.js' +} from 'src/services/entity_normalizer/entity_normalizer.service.js' import { paramsString, promisedRequest } from './helpers.js' import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' diff --git a/src/services/api/user.js b/src/api/user.js similarity index 99% rename from src/services/api/user.js rename to src/api/user.js index 28413e6c9..b2bffe057 100644 --- a/src/services/api/user.js +++ b/src/api/user.js @@ -6,7 +6,7 @@ import { parseSource, parseStatus, parseUser, -} from '../entity_normalizer/entity_normalizer.service.js' +} from 'src/services/entity_normalizer/entity_normalizer.service.js' import { paramsString, promisedRequest } from './helpers.js' import { fetchFriends, MASTODON_STATUS_URL } from './public.js' diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.js b/src/components/bookmark_folder_edit/bookmark_folder_edit.js index 74bcf3109..748070495 100644 --- a/src/components/bookmark_folder_edit/bookmark_folder_edit.js +++ b/src/components/bookmark_folder_edit/bookmark_folder_edit.js @@ -4,7 +4,7 @@ import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchBookmarkFolders } from 'src/services/api/user.js' +import { fetchBookmarkFolders } from 'src/api/user.js' const BookmarkFolderEdit = { data() { diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index 4249d3f3f..1b80b6321 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -5,7 +5,7 @@ import { mapGetters, mapState } from 'vuex' import ChatMessage from 'src/components/chat_message/chat_message.vue' import ChatTitle from 'src/components/chat_title/chat_title.vue' import PostStatusForm from 'src/components/post_status_form/post_status_form.vue' -import { WSConnectionStatus } from '../../services/api/public.js' +import { WSConnectionStatus } from 'src/api/public.js' import chatService from '../../services/chat_service/chat_service.js' import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js' import { promiseInterval } from '../../services/promise_interval/promise_interval.js' @@ -23,7 +23,7 @@ import { chatMessages, getOrCreateChat, sendChatMessage, -} from 'src/services/api/chats.js' +} from 'src/api/chats.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons' diff --git a/src/components/chat_new/chat_new.js b/src/components/chat_new/chat_new.js index 3e0cc8db3..ec2656e5c 100644 --- a/src/components/chat_new/chat_new.js +++ b/src/components/chat_new/chat_new.js @@ -5,7 +5,7 @@ import UserAvatar from 'src/components/user_avatar/user_avatar.vue' import { useOAuthStore } from 'src/stores/oauth.js' -import { chats } from 'src/services/api/chats.js' +import { chats } from 'src/api/chats.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons' diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index d3d0ac949..30c437459 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -5,13 +5,13 @@ import { mapState } from 'vuex' import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filter_settings.vue' import QuickViewSettings from 'src/components/quick_view_settings/quick_view_settings.vue' import ThreadTree from 'src/components/thread_tree/thread_tree.vue' -import { WSConnectionStatus } from '../../services/api/public.js' +import { WSConnectionStatus } from 'src/api/public.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { useInterfaceStore } from 'src/stores/interface' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchConversation, fetchStatus } from 'src/services/api/public.js' +import { fetchConversation, fetchStatus } from 'src/api/public.js' import { library } from '@fortawesome/fontawesome-svg-core' import { diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js index 1ee9b5c3b..c2e10c242 100644 --- a/src/components/follow_request_card/follow_request_card.js +++ b/src/components/follow_request_card/follow_request_card.js @@ -6,7 +6,7 @@ import BasicUserCard from '../basic_user_card/basic_user_card.vue' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { approveUser, denyUser } from 'src/services/api/user.js' +import { approveUser, denyUser } from 'src/api/user.js' const FollowRequestCard = { props: ['user'], diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 55311255e..b6ee27f9c 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -18,7 +18,7 @@ import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' -import { approveUser, denyUser } from 'src/services/api/user.js' +import { approveUser, denyUser } from 'src/api/user.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import { library } from '@fortawesome/fontawesome-svg-core' diff --git a/src/components/remote_user_resolver/remote_user_resolver.js b/src/components/remote_user_resolver/remote_user_resolver.js index d68f6e608..b44629fcf 100644 --- a/src/components/remote_user_resolver/remote_user_resolver.js +++ b/src/components/remote_user_resolver/remote_user_resolver.js @@ -1,6 +1,6 @@ import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchUser } from 'src/services/api/public.js' +import { fetchUser } from 'src/api/public.js' const RemoteUserResolver = { data: () => ({ diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue index 6cee8cebe..d3db657d3 100644 --- a/src/components/settings_modal/helpers/emoji_editing_popover.vue +++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue @@ -159,7 +159,7 @@ import { addNewEmojiFile, deleteEmojiFile, updateEmojiFile, -} from 'src/services/api/admin.js' +} from 'src/api/admin.js' export default { components: { diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js index 75f0d74aa..f159c6090 100644 --- a/src/components/settings_modal/tabs/appearance_tab.js +++ b/src/components/settings_modal/tabs/appearance_tab.js @@ -10,11 +10,11 @@ import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' import Preview from './old_theme_tab/theme_preview.vue' +import { useOAuthStore } from 'src/stores/oauth.js' import { useInstanceStore } from 'src/stores/instance.js' import { normalizeThemeData, useInterfaceStore } from 'src/stores/interface.js' -import { useOAuthStore } from 'src/stores/oauth.js' -import { updateProfileImages } from 'src/services/api/user.js' +import { updateProfileImages } from 'src/api/user.js' import { newImporter } from 'src/services/export_import/export_import.js' import { adoptStyleSheets, diff --git a/src/components/settings_modal/tabs/composing_tab.js b/src/components/settings_modal/tabs/composing_tab.js index 31d0a5706..0ad4f4cf0 100644 --- a/src/components/settings_modal/tabs/composing_tab.js +++ b/src/components/settings_modal/tabs/composing_tab.js @@ -11,13 +11,13 @@ import IntegerSetting from '../helpers/integer_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' +import { useOAuthStore } from 'src/stores/oauth.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' -import { updateProfile } from 'src/services/api/user.js' +import { updateProfile } from 'src/api/user.js' import localeService from 'src/services/locale/locale.service.js' import { cacheKey, clearCache, emojiCacheKey } from 'src/services/sw/sw.js' diff --git a/src/components/settings_modal/tabs/data_import_export_tab.js b/src/components/settings_modal/tabs/data_import_export_tab.js index 0329ff125..a89657658 100644 --- a/src/components/settings_modal/tabs/data_import_export_tab.js +++ b/src/components/settings_modal/tabs/data_import_export_tab.js @@ -16,7 +16,7 @@ import { importFollows, importMutes, listBackups, -} from 'src/services/api/user.js' +} from 'src/api/user.js' const DataImportExportTab = { data() { diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index 48bbd67c7..e2302c774 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -8,14 +8,14 @@ import FloatSetting from '../helpers/float_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' +import { useOAuthStore } from 'src/stores/oauth.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useLocalConfigStore } from 'src/stores/local_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' -import { updateProfile } from 'src/services/api/user.js' +import { updateProfile } from 'src/api/user.js' import localeService from 'src/services/locale/locale.service.js' const GeneralTab = { diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js index da9a8cbb7..cb945012f 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js @@ -13,7 +13,7 @@ import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthTokensStore } from 'src/stores/oauth_tokens.js' -import { importBlocks, importFollows } from 'src/services/api/user.js' +import { importBlocks, importFollows } from 'src/api/user.js' const MutesAndBlocks = { data() { diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js index 5fd1e8483..a666d42e3 100644 --- a/src/components/settings_modal/tabs/notifications_tab.js +++ b/src/components/settings_modal/tabs/notifications_tab.js @@ -3,7 +3,7 @@ import SharedComputedObject from '../helpers/shared_computed_object.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { updateNotificationSettings } from 'src/services/api/user.js' +import { updateNotificationSettings } from 'src/api/user.js' const NotificationsTab = { data() { diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index b03c2012f..d30a82bf9 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -5,7 +5,7 @@ import SharedComputedObject from '../helpers/shared_computed_object.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { updateProfile } from 'src/services/api/user.js' +import { updateProfile } from 'src/api/user.js' import { library } from '@fortawesome/fontawesome-svg-core' import { diff --git a/src/components/settings_modal/tabs/security_tab/mfa.js b/src/components/settings_modal/tabs/security_tab/mfa.js index 32ce9ed21..12c464719 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa.js +++ b/src/components/settings_modal/tabs/security_tab/mfa.js @@ -12,7 +12,7 @@ import { mfaConfirmOTP, mfaSetupOTP, settingsMFA, -} from 'src/services/api/user.js' +} from 'src/api/user.js' const Mfa = { data: () => ({ diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.js b/src/components/settings_modal/tabs/security_tab/mfa_totp.js index 393a36249..a22e88070 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa_totp.js +++ b/src/components/settings_modal/tabs/security_tab/mfa_totp.js @@ -4,7 +4,7 @@ import Confirm from './confirm.vue' import { useOAuthStore } from 'src/stores/oauth.js' -import { mfaDisableOTP } from 'src/services/api/user.js' +import { mfaDisableOTP } from 'src/api/user.js' export default { props: ['settings'], diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js index 35576e0d0..30a8b580e 100644 --- a/src/components/settings_modal/tabs/security_tab/security_tab.js +++ b/src/components/settings_modal/tabs/security_tab/security_tab.js @@ -15,7 +15,7 @@ import { deleteAlias, listAliases, moveAccount, -} from 'src/services/api/user.js' +} from 'src/api/user.js' import localeService from 'src/services/locale/locale.service.js' const SecurityTab = { diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 0ae234afe..2f10c5ebc 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -26,7 +26,7 @@ import { useOAuthStore } from 'src/stores/oauth.js' import { usePostStatusStore } from 'src/stores/post_status' import { useUserHighlightStore } from 'src/stores/user_highlight.js' -import { updateProfile } from 'src/services/api/user.js' +import { updateProfile } from 'src/api/user.js' import { propsToNative } from 'src/services/attributes_helper/attributes_helper.service.js' import localeService from 'src/services/locale/locale.service.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js index 68e30903e..9659022d4 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.js +++ b/src/components/user_reporting_modal/user_reporting_modal.js @@ -8,7 +8,7 @@ import UserLink from 'src/components/user_link/user_link.vue' import { useOAuthStore } from 'src/stores/oauth.js' import { useReportsStore } from 'src/stores/reports.js' -import { reportUser } from 'src/services/api/user.js' +import { reportUser } from 'src/api/user.js' const UserReportingModal = { components: { diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js index 026f682ae..3c0b53b0d 100644 --- a/src/components/who_to_follow/who_to_follow.js +++ b/src/components/who_to_follow/who_to_follow.js @@ -3,7 +3,7 @@ import FollowCard from 'src/components/follow_card/follow_card.vue' import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchUser, suggestions } from 'src/services/api/public.js' +import { fetchUser, suggestions } from 'src/api/public.js' const WhoToFollow = { components: { diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js index b8575396d..32f9dc918 100644 --- a/src/components/who_to_follow_panel/who_to_follow_panel.js +++ b/src/components/who_to_follow_panel/who_to_follow_panel.js @@ -4,7 +4,7 @@ import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchUser, suggestions } from 'src/services/api/public.js' +import { fetchUser, suggestions } from 'src/api/public.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' function showWhoToFollow(panel, reply) { diff --git a/src/modules/api.js b/src/modules/api.js index 352509282..2dd5d23df 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -1,6 +1,6 @@ import { Socket } from 'phoenix' -import { WSConnectionStatus } from '../services/api/public.js' +import { WSConnectionStatus } from 'src/api/public.js' import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -13,7 +13,7 @@ import { fetchTimeline, getMastodonSocketURI, ProcessedWS, -} from 'src/services/api/public.js' +} from 'src/api/public.js' import followRequestFetcher from 'src/services/follow_request_fetcher/follow_request_fetcher.service' import notificationsFetcher from 'src/services/notifications_fetcher/notifications_fetcher.service.js' import timelineFetcher from 'src/services/timeline_fetcher/timeline_fetcher.service.js' diff --git a/src/modules/chats.js b/src/modules/chats.js index 8bd353a70..abc035f09 100644 --- a/src/modules/chats.js +++ b/src/modules/chats.js @@ -11,7 +11,7 @@ import { promiseInterval } from '../services/promise_interval/promise_interval.j import { useOAuthStore } from 'src/stores/oauth.js' -import { chats, deleteChatMessage, readChat } from 'src/services/api/chats.js' +import { chats, deleteChatMessage, readChat } from 'src/api/chats.js' const emptyChatList = () => ({ data: [], diff --git a/src/modules/notifications.js b/src/modules/notifications.js index d759be83b..d89cc79ba 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -1,4 +1,4 @@ -import { markNotificationsAsSeen } from '../services/api/user.js' +import { markNotificationsAsSeen } from 'src/api/user.js' import { closeAllDesktopNotifications, closeDesktopNotification, @@ -15,7 +15,7 @@ import { useOAuthStore } from 'src/stores/oauth.js' import { useReportsStore } from 'src/stores/reports.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' -import { dismissNotification } from 'src/services/api/user.js' +import { dismissNotification } from 'src/api/user.js' const emptyNotifications = () => ({ desktopNotificationSilence: true, diff --git a/src/modules/profileConfig.js b/src/modules/profileConfig.js index a7f3e73d8..28536a2f4 100644 --- a/src/modules/profileConfig.js +++ b/src/modules/profileConfig.js @@ -5,7 +5,7 @@ import { useOAuthStore } from 'src/stores/oauth.js' import { updateNotificationSettings, updateProfile, -} from 'src/services/api/user.js' +} from 'src/api/user.js' const defaultApi = ({ rootState, commit }, { path, value }) => { const params = {} diff --git a/src/modules/statuses.js b/src/modules/statuses.js index f62ee6b86..a8cecbecc 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -27,7 +27,7 @@ import { fetchStatusHistory, fetchStatusSource, search2, -} from 'src/services/api/public.js' +} from 'src/api/public.js' import { bookmarkStatus, deleteStatus, @@ -42,7 +42,7 @@ import { unpinOwnStatus, unreactWithEmoji, unretweet, -} from 'src/services/api/user.js' +} from 'src/api/user.js' const emptyTl = (userId = 0) => ({ statuses: [], diff --git a/src/modules/users.js b/src/modules/users.js index 1cd5139eb..0dc1f6aeb 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -9,7 +9,7 @@ import { uniq, } from 'lodash' -import { register } from '../services/api/public.js' +import { register } from 'src/api/public.js' import oauthApi from '../services/new_api/oauth.js' import { registerPushNotifications, @@ -39,7 +39,7 @@ import { getCaptcha, searchUsers, verifyCredentials, -} from 'src/services/api/public.js' +} from 'src/api/public.js' import { fetchBlocks, fetchDomainMutes, @@ -48,7 +48,7 @@ import { fetchUserRelationship, followUser, muteUser, -} from 'src/services/api/user.js' +} from 'src/api/user.js' // TODO: Unify with mergeOrAdd in statuses.js export const mergeOrAdd = (arr, obj, item) => { diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index d4af7f8c2..31b1686e9 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -4,7 +4,7 @@ import { fetchUserRelationship, followUser, unfollowUser, -} from 'src/services/api/user.js' +} from 'src/api/user.js' const fetchRelationship = (attempt, userId, store) => new Promise((resolve, reject) => { diff --git a/src/services/follow_request_fetcher/follow_request_fetcher.service.js b/src/services/follow_request_fetcher/follow_request_fetcher.service.js index a6521601c..ecb6cb7dd 100644 --- a/src/services/follow_request_fetcher/follow_request_fetcher.service.js +++ b/src/services/follow_request_fetcher/follow_request_fetcher.service.js @@ -1,4 +1,4 @@ -import { fetchFollowRequests } from 'src/services/api/user.js' +import { fetchFollowRequests } from 'src/api/user.js' import { promiseInterval } from 'src/services/promise_interval/promise_interval.js' const fetchAndUpdate = ({ store, credentials }) => { diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index a27bf6047..6258b0ebe 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -1,4 +1,4 @@ -import { fetchTimeline } from '../api/public.js' +import { fetchTimeline } from 'src/api/public.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useInstanceStore } from 'src/stores/instance.js' diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 8b50e0f35..ddc4a2f9a 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -5,7 +5,7 @@ import { postStatus as apiPostStatus, setMediaDescription as apiSetMediaDescription, uploadMedia as apiUploadMedia, -} from 'src/services/api/user.js' +} from 'src/api/user.js' const postStatus = ({ store, diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 496b1a4ea..b2ed40e47 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -1,6 +1,6 @@ import { camelCase } from 'lodash' -import { fetchTimeline } from '../api/public.js' +import { fetchTimeline } from 'src/api/public.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useInstanceStore } from 'src/stores/instance.js' diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index 88f655481..a437d56ca 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -31,8 +31,8 @@ import { setUsersRight, setUsersSuggestionStatus, setUsersTags, -} from 'src/services/api/admin.js' -import { listEmojiPacks } from 'src/services/api/public.js' +} from 'src/api/admin.js' +import { listEmojiPacks } from 'src/api/public.js' import { parseStatus } from 'src/services/entity_normalizer/entity_normalizer.service.js' export const defaultState = { @@ -551,7 +551,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { listEmojiPacks(params) { return listEmojiPacks({ ...params, - credentials: useOAuthStore().token, + credentials: useOAuthStore().token }) }, listRemoteEmojiPacks(params) { diff --git a/src/stores/announcements.js b/src/stores/announcements.js index 9a2560db5..2b738b905 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -7,9 +7,9 @@ import { deleteAnnouncement, editAnnouncement, postAnnouncement, -} from 'src/services/api/admin.js' -import { getAnnouncements } from 'src/services/api/public.js' -import { dismissAnnouncement } from 'src/services/api/user.js' +} from 'src/api/admin.js' +import { getAnnouncements } from 'src/api/public.js' +import { dismissAnnouncement } from 'src/api/user.js' const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5 diff --git a/src/stores/bookmark_folders.js b/src/stores/bookmark_folders.js index c5c884f82..928ad13a6 100644 --- a/src/stores/bookmark_folders.js +++ b/src/stores/bookmark_folders.js @@ -8,7 +8,7 @@ import { deleteBookmarkFolder, fetchBookmarkFolders, updateBookmarkFolder, -} from 'src/services/api/user.js' +} from 'src/api/user.js' import { promiseInterval } from 'src/services/promise_interval/promise_interval.js' export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { diff --git a/src/stores/emoji.js b/src/stores/emoji.js index 5fae78ce2..aefa79342 100644 --- a/src/stores/emoji.js +++ b/src/stores/emoji.js @@ -1,11 +1,11 @@ import { merge } from 'lodash' import { defineStore } from 'pinia' -import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' +import { useInstanceStore } from 'src/stores/instance.js' import { ensureFinalFallback } from 'src/i18n/languages.js' -import { listEmojiPacks } from 'src/services/api/public.js' +import { listEmojiPacks } from 'src/api/public.js' import { annotationsLoader } from 'virtual:pleroma-fe/emoji-annotations' @@ -188,8 +188,7 @@ export const useEmojiStore = defineStore('emoji', { this.adminPacksLocalLoading = true this.adminPacksLocal = await this.getAdminPacks( useInstanceStore().server, - (params) => - listEmojiPacks({ + (params) => listEmojiPacks({ ...params, credentials: useOAuthStore().token, }), @@ -222,13 +221,14 @@ export const useEmojiStore = defineStore('emoji', { instance, page: i, pageSize, - }).then((pageData) => { - if (pageData.error !== undefined) { - return Promise.reject(pageData.error) - } + }) + .then((pageData) => { + if (pageData.error !== undefined) { + return Promise.reject(pageData.error) + } - return pageData.packs - }), + return pageData.packs + }), ) } diff --git a/src/stores/instance.js b/src/stores/instance.js index 17a13765a..8abb8078a 100644 --- a/src/stores/instance.js +++ b/src/stores/instance.js @@ -11,7 +11,7 @@ import { LOCAL_DEFAULT_CONFIG_DEFINITIONS, validateSetting, } from '../modules/default_config_state.js' -import { fetchKnownDomains } from '../services/api/public.js' +import { fetchKnownDomains } from 'src/api/public.js' import { useInterfaceStore } from 'src/stores/interface.js' diff --git a/src/stores/lists.js b/src/stores/lists.js index 6431e1d2f..20529f06b 100644 --- a/src/stores/lists.js +++ b/src/stores/lists.js @@ -12,7 +12,7 @@ import { getListAccounts, removeAccountsFromList, updateList, -} from 'src/services/api/user.js' +} from 'src/api/user.js' import { promiseInterval } from 'src/services/promise_interval/promise_interval.js' export const useListsStore = defineStore('lists', { diff --git a/src/stores/oauth_tokens.js b/src/stores/oauth_tokens.js index 55914386b..bb9bbccbf 100644 --- a/src/stores/oauth_tokens.js +++ b/src/stores/oauth_tokens.js @@ -2,7 +2,7 @@ import { defineStore } from 'pinia' import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchOAuthTokens, revokeOAuthToken } from 'src/services/api/user.js' +import { fetchOAuthTokens, revokeOAuthToken } from 'src/api/user.js' export const useOAuthTokensStore = defineStore('oauthTokens', { state: () => ({ diff --git a/src/stores/polls.js b/src/stores/polls.js index 74812a24e..f2eafacfb 100644 --- a/src/stores/polls.js +++ b/src/stores/polls.js @@ -3,8 +3,8 @@ import { defineStore } from 'pinia' import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchPoll } from 'src/services/api/public.js' -import { vote } from 'src/services/api/user.js' +import { fetchPoll } from 'src/api/public.js' +import { vote } from 'src/api/user.js' export const usePollsStore = defineStore('polls', { state: () => ({ diff --git a/src/stores/reports.js b/src/stores/reports.js index 2fea2e8e6..7d319d8e3 100644 --- a/src/stores/reports.js +++ b/src/stores/reports.js @@ -4,7 +4,7 @@ import { defineStore } from 'pinia' import { useInterfaceStore } from 'src/stores/interface.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { setReportState } from 'src/services/api/admin.js' +import { setReportState } from 'src/api/admin.js' export const useReportsStore = defineStore('reports', { state: () => ({ diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js index bdcc6cdb9..5ff42adaf 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -20,9 +20,9 @@ import { toRaw } from 'vue' import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { useInstanceStore } from 'src/stores/instance.js' import { useLocalConfigStore } from 'src/stores/local_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { storage } from 'src/lib/storage.js' import { @@ -32,7 +32,7 @@ import { validateSetting, } from 'src/modules/default_config_state.js' import { oldDefaultConfigSync } from 'src/modules/old_default_config_state.js' -import { updateProfileJSON } from 'src/services/api/user.js' +import { updateProfileJSON } from 'src/api/user.js' export const VERSION = 2 export const NEW_USER_DATE = new Date('2026-03-16') // date of writing this, basically diff --git a/src/stores/user_highlight.js b/src/stores/user_highlight.js index 08b739d09..41570521f 100644 --- a/src/stores/user_highlight.js +++ b/src/stores/user_highlight.js @@ -17,7 +17,7 @@ import { toRaw } from 'vue' import { useOAuthStore } from 'src/stores/oauth.js' import { storage } from 'src/lib/storage.js' -import { updateProfileJSON } from 'src/services/api/user.js' +import { updateProfileJSON } from 'src/api/user.js' export const NEW_USER_DATE = new Date('2022-08-04') // date of writing this, basically diff --git a/test/unit/specs/services/api/helpers.spec.js b/test/unit/specs/services/api/helpers.spec.js index 3ffb83c70..b1219bd46 100644 --- a/test/unit/specs/services/api/helpers.spec.js +++ b/test/unit/specs/services/api/helpers.spec.js @@ -1,4 +1,4 @@ -import { paramsString } from 'src/services/api/helpers.js' +import { paramsString } from 'src/api/helpers.js' describe('API Helpers', () => { describe.only('paramsString', () => { From 1ca0ffb1f0c84977a26afc81a2a666e6050b0d16 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 17:58:14 +0300 Subject: [PATCH 26/86] refactor promisedRequest to always return whole response --- src/api/chats.js | 5 +- src/api/helpers.js | 37 ++- src/api/public.js | 57 ++-- src/api/user.js | 52 ++-- src/components/chat/chat.js | 2 +- src/components/conversation/conversation.js | 9 +- .../settings_modal/tabs/appearance_tab.js | 2 +- .../settings_modal/tabs/composing_tab.js | 2 +- .../settings_modal/tabs/general_tab.js | 2 +- src/modules/api.js | 2 +- src/modules/notifications.js | 3 +- src/modules/profileConfig.js | 5 +- src/modules/statuses.js | 44 +-- src/modules/users.js | 284 +++++++++--------- .../notifications_fetcher.service.js | 4 +- .../status_poster/status_poster.service.js | 44 +-- .../timeline_fetcher.service.js | 3 +- src/stores/admin_settings.js | 72 +++-- src/stores/announcements.js | 7 +- src/stores/bookmark_folders.js | 9 +- src/stores/emoji.js | 20 +- src/stores/instance.js | 3 +- src/stores/lists.js | 5 +- src/stores/sync_config.js | 4 +- src/stores/user_highlight.js | 2 +- 25 files changed, 352 insertions(+), 327 deletions(-) diff --git a/src/api/chats.js b/src/api/chats.js index 4a0c55752..51e83c74f 100644 --- a/src/api/chats.js +++ b/src/api/chats.js @@ -1,6 +1,7 @@ -import { parseChat } from 'src/services/entity_normalizer/entity_normalizer.service.js' import { paramsString, promisedRequest } from './helpers.js' +import { parseChat } from 'src/services/entity_normalizer/entity_normalizer.service.js' + const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats' const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}` const PLEROMA_CHAT_MESSAGES_URL = (id, { maxId, sinceId, limit }) => @@ -13,7 +14,7 @@ export const chats = ({ credentials }) => promisedRequest({ url: PLEROMA_CHATS_URL, credentials, - }).then((data) => ({ + }).then(({ data }) => ({ chatList: data.map(parseChat).filter((c) => c), })) diff --git a/src/api/helpers.js b/src/api/helpers.js index 1e8f257fb..842efe9aa 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -98,6 +98,7 @@ export const promisedRequest = async ({ ) .join('&') } + if (formData || payload) { options.body = formData || JSON.stringify(payload) } @@ -109,33 +110,37 @@ export const promisedRequest = async ({ } } - const response = await fetch(url, options) - - // 204 is "No content", which fails to parse json (as you'd might think) - if (response.ok && response.status === 204) return { _response: response } - + let response = null try { - const json = await response.json() + response = await fetch(url, options) + const data = await (async () => { + const [contentType] = response.headers + .get('content-type') + .split(';') + .map((x) => x.toLowerCase().trim()) - if (typeof json !== 'object') { - return { - _response: response, - _value: json, + switch (contentType) { + case 'text/plain': + return await response.text() + case 'application/json': + return await response.json() + default: + return await response.bytes() } - } + })() - json._response = response + const { ok, status } = response - if (!response.ok) { + if (ok) { + return { response, status, data } + } else { throw new StatusCodeError( response.status, - json, + data, { url, options }, response, ) } - - return json } catch (error) { throw new StatusCodeError( response.status, diff --git a/src/api/public.js b/src/api/public.js index b0aa68140..573f97c7c 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -1,5 +1,7 @@ import { concat, each, last, map } from 'lodash' +import { paramsString, promisedRequest } from './helpers.js' + import { parseAttachment, parseChat, @@ -9,8 +11,6 @@ import { parseStatus, parseUser, } from 'src/services/entity_normalizer/entity_normalizer.service.js' -import { paramsString, promisedRequest } from './helpers.js' - import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' const SUGGESTIONS_URL = '/api/v1/suggestions' @@ -102,7 +102,7 @@ export const fetchUser = ({ id, credentials }) => promisedRequest({ url: `${MASTODON_USER_URL}/${id}`, credentials, - }).then((data) => parseUser(data)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) })) export const fetchUserByName = ({ name, credentials }) => promisedRequest({ @@ -126,7 +126,7 @@ export const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => promisedRequest({ url: MASTODON_FOLLOWING_URL(id, { maxId, sinceId, limit }), credentials, - }).then((data) => data.map(parseUser)) + }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) })) export const fetchFollowers = ({ id, @@ -143,16 +143,20 @@ export const fetchFollowers = ({ withRelationships: true, }), credentials, - }).then((data) => data.map(parseUser)) + }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) })) export const fetchConversation = ({ id, credentials }) => promisedRequest({ url: MASTODON_STATUS_CONTEXT_URL(id), credentials, }) - .then(({ ancestors, descendants }) => ({ - ancestors: ancestors.map(parseStatus), - descendants: descendants.map(parseStatus), + .then((result) => ({ + ...result, + data: { + ...result.data, + ancestors: result.data.ancestors.map(parseStatus), + descendants: result.data.descendants.map(parseStatus), + }, })) .catch((error) => { throw new Error('Error fetching timeline', error) @@ -163,7 +167,7 @@ export const fetchStatus = ({ id, credentials }) => url: MASTODON_STATUS_URL(id), credentials, }) - .then((data) => parseStatus(data)) + .then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) .catch((error) => { throw new Error('Error fetching timeline', error) }) @@ -173,7 +177,7 @@ export const fetchStatusSource = ({ id, credentials }) => url: MASTODON_STATUS_SOURCE_URL(id), credentials, }) - .then((data) => parseSource(data)) + .then(({ data, ...rest }) => ({ ...rest, data: parseSource(data) })) .catch((error) => { throw new Error('Error fetching timeline', error) }) @@ -182,9 +186,8 @@ export const fetchStatusHistory = ({ status, credentials }) => promisedRequest({ url: MASTODON_STATUS_HISTORY_URL(status.id), credentials, - }).then((data) => { - data.reverse() - return data.map((item) => { + }).then(({ data }) => { + return [...data].reverse().map((item) => { item.originalStatus = status return parseStatus(item) }) @@ -277,16 +280,17 @@ export const fetchTimeline = ({ return promisedRequest({ url: url + paramsString(params), credentials, - }).then(async (data) => { + }).then(async (result) => { const pagination = parseLinkHeaderPagination( - data._response.headers.get('Link'), + result.response.headers.get('Link'), { flakeId: timeline !== 'bookmarks' && timeline !== 'notifications', }, ) return { - data: data.map(isNotifications ? parseNotification : parseStatus), + ...result, + data: result.data.map(isNotifications ? parseNotification : parseStatus), pagination, } }) @@ -301,13 +305,13 @@ export const fetchPinnedStatuses = ({ id, credentials }) => promisedRequest({ url: MASTODON_USER_TIMELINE_URL(id) + '?pinned=true', credentials, - }).then((data) => data.map(parseStatus)) + }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseStatus) })) export const verifyCredentials = ({ credentials }) => promisedRequest({ url: MASTODON_LOGIN_URL, credentials, - }).then((data) => (data.error ? data : parseUser(data))) + }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) })) export const suggestions = ({ credentials }) => promisedRequest({ @@ -327,25 +331,26 @@ export const fetchFavoritedByUsers = ({ id, credentials }) => url: MASTODON_STATUS_FAVORITEDBY_URL(id), method: 'GET', credentials, - }).then((users) => users.map(parseUser)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) })) export const fetchRebloggedByUsers = ({ id, credentials }) => promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id), method: 'GET', credentials, - }).then((users) => users.map(parseUser)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) })) export const fetchEmojiReactions = ({ id, credentials }) => promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id), credentials, - }).then((reactions) => - reactions.map((r) => { + }).then(({ data, ...rest }) => ({ + ...rest, + data: data.map((r) => { r.accounts = r.accounts.map(parseUser) return r }), - ) + })) export const searchUsers = ({ credentials, query }) => promisedRequest({ @@ -355,7 +360,7 @@ export const searchUsers = ({ credentials, query }) => resolve: true, }, credentials, - }).then((data) => data.map(parseUser)) + }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) })) export const search2 = ({ credentials, @@ -404,10 +409,10 @@ export const search2 = ({ url, credentials, }) - .then((data) => { + .then(({ data, ...rest }) => { data.accounts = data.accounts.slice(0, limit).map((u) => parseUser(u)) data.statuses = data.statuses.slice(0, limit).map((s) => parseStatus(s)) - return data + return { ...rest, data } }) .catch((error) => { throw new Error('Error fetching timeline', error) diff --git a/src/api/user.js b/src/api/user.js index b2bffe057..ebbd8b473 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -1,14 +1,13 @@ import { concat, each, last, map } from 'lodash' +import { paramsString, promisedRequest } from './helpers.js' +import { fetchFriends, MASTODON_STATUS_URL } from './public.js' + import { parseAttachment, - parseNotification, - parseSource, parseStatus, parseUser, } from 'src/services/entity_normalizer/entity_normalizer.service.js' -import { paramsString, promisedRequest } from './helpers.js' -import { fetchFriends, MASTODON_STATUS_URL } from './public.js' const MUTES_IMPORT_URL = '/api/pleroma/mutes_import' const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import' @@ -99,42 +98,42 @@ export const favorite = ({ id, credentials }) => url: MASTODON_FAVORITE_URL(id), method: 'POST', credentials, - }).then((data) => parseStatus(data)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) export const unfavorite = ({ id, credentials }) => promisedRequest({ url: MASTODON_UNFAVORITE_URL(id), method: 'POST', credentials, - }).then((data) => parseStatus(data)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) export const retweet = ({ id, credentials }) => promisedRequest({ url: MASTODON_RETWEET_URL(id), method: 'POST', credentials, - }).then((data) => parseStatus(data)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) export const unretweet = ({ id, credentials }) => promisedRequest({ url: MASTODON_UNRETWEET_URL(id), method: 'POST', credentials, - }).then((data) => parseStatus(data)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) export const reactWithEmoji = ({ id, emoji, credentials }) => promisedRequest({ url: PLEROMA_EMOJI_REACT_URL(id, emoji), method: 'PUT', credentials, - }).then(parseStatus) + }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) export const unreactWithEmoji = ({ id, emoji, credentials }) => promisedRequest({ url: PLEROMA_EMOJI_UNREACT_URL(id, emoji), method: 'DELETE', credentials, - }).then(parseStatus) + }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) export const bookmarkStatus = ({ id, credentials, ...options }) => promisedRequest({ @@ -158,28 +157,28 @@ export const pinOwnStatus = ({ id, credentials }) => url: MASTODON_PIN_OWN_STATUS(id), credentials, method: 'POST', - }).then((data) => parseStatus(data)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) export const unpinOwnStatus = ({ id, credentials }) => promisedRequest({ url: MASTODON_UNPIN_OWN_STATUS(id), credentials, method: 'POST', - }).then((data) => parseStatus(data)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) export const muteConversation = ({ id, credentials }) => promisedRequest({ url: MASTODON_MUTE_CONVERSATION(id), credentials, method: 'POST', - }).then((data) => parseStatus(data)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) export const unmuteConversation = ({ id, credentials }) => promisedRequest({ url: MASTODON_UNMUTE_CONVERSATION(id), credentials, method: 'POST', - }).then((data) => parseStatus(data)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) export const vote = ({ pollId, choices, credentials }) => { const form = new FormData() @@ -256,7 +255,7 @@ export const postStatus = ({ method: 'POST', credentials, headers, - }).then((data) => (data.error ? data : parseStatus(data))) + }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) } export const editStatus = ({ @@ -299,7 +298,7 @@ export const editStatus = ({ formData: form, method: 'PUT', credentials, - }).then((data) => (data.error ? data : parseStatus(data))) + }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) } export const deleteStatus = ({ id, credentials }) => @@ -315,7 +314,7 @@ export const uploadMedia = ({ formData, credentials }) => formData, method: 'POST', credentials, - }).then((data) => parseAttachment(data)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseAttachment(data) })) export const setMediaDescription = ({ id, description, credentials }) => promisedRequest({ @@ -325,7 +324,7 @@ export const setMediaDescription = ({ id, description, credentials }) => payload: { description, }, - }).then((data) => parseAttachment(data)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseAttachment(data) })) // #Notifications export const dismissNotification = ({ credentials, id }) => @@ -456,12 +455,7 @@ export const updateProfileImages = ({ credentials, method: 'PATCH', formData: form, - }).then((data) => { - if (data.error) { - throw new Error(data.error) - } - return parseUser(data) - }) + }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) })) } export const updateProfile = ({ credentials, params }) => { @@ -489,7 +483,7 @@ export const updateProfile = ({ credentials, params }) => { credentials, method: 'PATCH', formData, - }).then((data) => parseUser(data)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) })) } export const updateProfileJSON = ({ credentials, params }) => @@ -498,7 +492,7 @@ export const updateProfileJSON = ({ credentials, params }) => credentials, payload: params, method: 'PATCH', - }).then((data) => parseUser(data)) + }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) })) export const changeEmail = ({ credentials, email, password }) => { const form = new FormData() @@ -671,7 +665,7 @@ export const fetchFollowRequests = ({ credentials }) => promisedRequest({ url: MASTODON_FOLLOW_REQUESTS_URL, credentials, - }).then((data) => data.map(parseUser)) + }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) })) export const approveUser = ({ id, credentials }) => promisedRequest({ @@ -701,7 +695,7 @@ export const fetchMutes = ({ maxId, credentials }) => promisedRequest({ url: MASTODON_USER_MUTES_URL({ maxId, withRelationships: true }), credentials, - }).then((users) => users.map(parseUser)) + }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) })) export const muteUser = ({ id, expiresIn, credentials }) => { const payload = {} @@ -728,7 +722,7 @@ export const fetchBlocks = ({ maxId, credentials }) => promisedRequest({ url: MASTODON_USER_BLOCKS_URL({ maxId, withRelationships: true }), credentials, - }).then((users) => users.map(parseUser)) + }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) })) export const blockUser = ({ id, expiresIn, credentials }) => { const payload = {} diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index 1b80b6321..b03bc0256 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -5,7 +5,6 @@ import { mapGetters, mapState } from 'vuex' import ChatMessage from 'src/components/chat_message/chat_message.vue' import ChatTitle from 'src/components/chat_title/chat_title.vue' import PostStatusForm from 'src/components/post_status_form/post_status_form.vue' -import { WSConnectionStatus } from 'src/api/public.js' import chatService from '../../services/chat_service/chat_service.js' import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js' import { promiseInterval } from '../../services/promise_interval/promise_interval.js' @@ -24,6 +23,7 @@ import { getOrCreateChat, sendChatMessage, } from 'src/api/chats.js' +import { WSConnectionStatus } from 'src/api/public.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons' diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 30c437459..27584d945 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -5,13 +5,16 @@ import { mapState } from 'vuex' import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filter_settings.vue' import QuickViewSettings from 'src/components/quick_view_settings/quick_view_settings.vue' import ThreadTree from 'src/components/thread_tree/thread_tree.vue' -import { WSConnectionStatus } from 'src/api/public.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { useInterfaceStore } from 'src/stores/interface' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchConversation, fetchStatus } from 'src/api/public.js' +import { + fetchConversation, + fetchStatus, + WSConnectionStatus, +} from 'src/api/public.js' import { library } from '@fortawesome/fontawesome-svg-core' import { diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js index f159c6090..5dbd79de4 100644 --- a/src/components/settings_modal/tabs/appearance_tab.js +++ b/src/components/settings_modal/tabs/appearance_tab.js @@ -10,9 +10,9 @@ import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' import Preview from './old_theme_tab/theme_preview.vue' -import { useOAuthStore } from 'src/stores/oauth.js' import { useInstanceStore } from 'src/stores/instance.js' import { normalizeThemeData, useInterfaceStore } from 'src/stores/interface.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { updateProfileImages } from 'src/api/user.js' import { newImporter } from 'src/services/export_import/export_import.js' diff --git a/src/components/settings_modal/tabs/composing_tab.js b/src/components/settings_modal/tabs/composing_tab.js index 0ad4f4cf0..3a2384832 100644 --- a/src/components/settings_modal/tabs/composing_tab.js +++ b/src/components/settings_modal/tabs/composing_tab.js @@ -11,10 +11,10 @@ import IntegerSetting from '../helpers/integer_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' -import { useOAuthStore } from 'src/stores/oauth.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { updateProfile } from 'src/api/user.js' diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index e2302c774..e40bb993e 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -8,11 +8,11 @@ import FloatSetting from '../helpers/float_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' -import { useOAuthStore } from 'src/stores/oauth.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useLocalConfigStore } from 'src/stores/local_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { updateProfile } from 'src/api/user.js' diff --git a/src/modules/api.js b/src/modules/api.js index 2dd5d23df..a36e21c05 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -1,6 +1,5 @@ import { Socket } from 'phoenix' -import { WSConnectionStatus } from 'src/api/public.js' import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -13,6 +12,7 @@ import { fetchTimeline, getMastodonSocketURI, ProcessedWS, + WSConnectionStatus, } from 'src/api/public.js' import followRequestFetcher from 'src/services/follow_request_fetcher/follow_request_fetcher.service' import notificationsFetcher from 'src/services/notifications_fetcher/notifications_fetcher.service.js' diff --git a/src/modules/notifications.js b/src/modules/notifications.js index d89cc79ba..b77683811 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -1,4 +1,3 @@ -import { markNotificationsAsSeen } from 'src/api/user.js' import { closeAllDesktopNotifications, closeDesktopNotification, @@ -15,7 +14,7 @@ import { useOAuthStore } from 'src/stores/oauth.js' import { useReportsStore } from 'src/stores/reports.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' -import { dismissNotification } from 'src/api/user.js' +import { dismissNotification, markNotificationsAsSeen } from 'src/api/user.js' const emptyNotifications = () => ({ desktopNotificationSilence: true, diff --git a/src/modules/profileConfig.js b/src/modules/profileConfig.js index 28536a2f4..80f881a25 100644 --- a/src/modules/profileConfig.js +++ b/src/modules/profileConfig.js @@ -2,10 +2,7 @@ import { get, set } from 'lodash' import { useOAuthStore } from 'src/stores/oauth.js' -import { - updateNotificationSettings, - updateProfile, -} from 'src/api/user.js' +import { updateNotificationSettings, updateProfile } from 'src/api/user.js' const defaultApi = ({ rootState, commit }, { path, value }) => { const params = {} diff --git a/src/modules/statuses.js b/src/modules/statuses.js index a8cecbecc..66bcda13d 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -158,7 +158,7 @@ const getLatestScrobble = (state, user) => { state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000 if (!scrobblesSupport) return fetchScrobbles({ accountId: user.id }) - .then((scrobbles) => { + .then(({ data: scrobbles }) => { if (scrobbles?.error) { useInstanceCapabilitiesStore().set('pleromaScrobblesAvailable', false) return @@ -627,7 +627,7 @@ const statuses = { }) }, fetchStatus({ rootState, dispatch }, id) { - return fetchStatus({ id }).then((status) => + return fetchStatus({ id }).then(({ data: status }) => dispatch('addNewStatuses', { statuses: [status] }), ) }, @@ -635,10 +635,10 @@ const statuses = { return fetchStatusSource({ id: status.id, credentials: useOAuthStore().token, - }) + }).then(({ data }) => data) }, fetchStatusHistory(_, status) { - return fetchStatusHistory({ status }) + return fetchStatusHistory({ status }).then(({ data }) => data) }, deleteStatus({ rootState, commit }, status) { deleteStatus({ @@ -670,7 +670,7 @@ const statuses = { favorite({ id: status.id, credentials: useOAuthStore().token, - }).then((status) => + }).then(({ data: status }) => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser, @@ -683,7 +683,7 @@ const statuses = { unfavorite({ id: status.id, credentials: useOAuthStore().token, - }).then((status) => + }).then(({ data: status }) => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser, @@ -694,7 +694,7 @@ const statuses = { fetchPinnedStatuses({ id: userId, credentials: useOAuthStore().token, - }).then((statuses) => + }).then(({ data: statuses }) => dispatch('addNewStatuses', { statuses, timeline: 'user', @@ -708,25 +708,29 @@ const statuses = { return pinOwnStatus({ id: statusId, credentials: useOAuthStore().token, - }).then((status) => dispatch('addNewStatuses', { statuses: [status] })) + }).then(({ data: status }) => + dispatch('addNewStatuses', { statuses: [status] }), + ) }, unpinStatus({ rootState, dispatch }, statusId) { return unpinOwnStatus({ id: statusId, credentials: useOAuthStore().token, - }).then((status) => dispatch('addNewStatuses', { statuses: [status] })) + }).then(({ data: status }) => + dispatch('addNewStatuses', { statuses: [status] }), + ) }, muteConversation({ rootState, commit }, { id: statusId }) { return muteConversation({ id: statusId, credentials: useOAuthStore().token, - }).then((status) => commit('setMutedStatus', status)) + }).then(({ data: status }) => commit('setMutedStatus', status)) }, unmuteConversation({ rootState, commit }, { id: statusId }) { return unmuteConversation({ id: statusId, credentials: useOAuthStore().token, - }).then((status) => commit('setMutedStatus', status)) + }).then(({ data: status }) => commit('setMutedStatus', status)) }, retweet({ rootState, commit }, status) { // Optimistic retweeting... @@ -734,7 +738,7 @@ const statuses = { retweet({ id: status.id, credentials: useOAuthStore().token, - }).then((status) => + }).then(({ data: status }) => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser, @@ -747,7 +751,7 @@ const statuses = { unretweet({ id: status.id, credentials: useOAuthStore().token, - }).then((status) => + }).then(({ data: status }) => commit('setRetweetedConfirm', { status, user: rootState.users.currentUser, @@ -760,7 +764,7 @@ const statuses = { id: status.id, folder_id: status.bookmark_folder_id, credentials: useOAuthStore().token, - }).then((status) => { + }).then(({ data: status }) => { commit('setBookmarkedConfirm', { status }) }) }, @@ -769,7 +773,7 @@ const statuses = { unbookmarkStatus({ id: status.id, credentials: useOAuthStore().token, - }).then((status) => { + }).then(({ data: status }) => { commit('setBookmarkedConfirm', { status }) }) }, @@ -789,7 +793,7 @@ const statuses = { id, credentials: useOAuthStore().token, }), - ]).then(([favoritedByUsers, rebloggedByUsers]) => { + ]).then(([{ data: favoritedByUsers }, { data: rebloggedByUsers }]) => { commit('addFavs', { id, favoritedByUsers, @@ -832,7 +836,7 @@ const statuses = { return fetchEmojiReactions({ id, credentials: useOAuthStore().token, - }).then((emojiReactions) => { + }).then(({ data: emojiReactions }) => { commit('addEmojiReactionsBy', { id, emojiReactions, @@ -844,7 +848,7 @@ const statuses = { fetchFavoritedByUsers({ id, credentials: useOAuthStore().token, - }).then((favoritedByUsers) => + }).then(({ data: favoritedByUsers }) => commit('addFavs', { id, favoritedByUsers, @@ -856,7 +860,7 @@ const statuses = { fetchRebloggedByUsers({ id, credentials: useOAuthStore().token, - }).then((rebloggedByUsers) => + }).then(({ data: rebloggedByUsers }) => commit('addRepeats', { id, rebloggedByUsers, @@ -873,7 +877,7 @@ const statuses = { following, type, credentials: useOAuthStore().token, - }).then((data) => { + }).then(({ data }) => { store.commit('addNewUsers', data.accounts) store.commit( 'addNewUsers', diff --git a/src/modules/users.js b/src/modules/users.js index 0dc1f6aeb..2eee21333 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -9,7 +9,6 @@ import { uniq, } from 'lodash' -import { register } from 'src/api/public.js' import oauthApi from '../services/new_api/oauth.js' import { registerPushNotifications, @@ -37,17 +36,21 @@ import { fetchUser, fetchUserByName, getCaptcha, + register, searchUsers, verifyCredentials, } from 'src/api/public.js' import { + blockUser as apiBlockUser, + muteUser as apiMuteUser, + unblockUser as apiUnblockUser, + unmuteUser as apiUnmuteUser, fetchBlocks, fetchDomainMutes, fetchMutes, fetchUserInLists, fetchUserRelationship, followUser, - muteUser, } from 'src/api/user.js' // TODO: Unify with mergeOrAdd in statuses.js @@ -92,7 +95,7 @@ const blockUser = (store, args) => { store.commit('updateUserRelationship', [predictedRelationship]) store.commit('addBlockId', id) - return blockUser({ id, expiresIn }).then((relationship) => { + return apiBlockUser({ id, expiresIn }).then(({ data: relationship }) => { store.commit('updateUserRelationship', [relationship]) store.commit('addBlockId', id) @@ -106,7 +109,7 @@ const blockUser = (store, args) => { } const unblockUser = (store, id) => { - return unblockUser({ id }).then((relationship) => + return apiUnblockUser({ id }).then(({ data: relationship }) => store.commit('updateUserRelationship', [relationship]), ) } @@ -123,7 +126,7 @@ const editUserNote = (store, { id, comment }) => { ) } -const localMuteUser = (store, args) => { +const muteUser = (store, args) => { const id = typeof args === 'object' ? args.id : args const expiresIn = typeof args === 'object' ? args.expiresIn : 0 @@ -131,11 +134,11 @@ const localMuteUser = (store, args) => { store.commit('updateUserRelationship', [predictedRelationship]) store.commit('addMuteId', id) - return muteUser({ + return apiMuteUser({ id, expiresIn, credentials: useOAuthStore().token, - }).then((relationship) => { + }).then(({ data: relationship }) => { store.commit('updateUserRelationship', [relationship]) store.commit('addMuteId', id) }) @@ -146,7 +149,7 @@ const unmuteUser = (store, id) => { predictedRelationship.muting = false store.commit('updateUserRelationship', [predictedRelationship]) - return unmuteUser({ id }).then((relationship) => + return apiUnmuteUser({ id }).then(({ data: relationship }) => store.commit('updateUserRelationship', [relationship]), ) } @@ -156,9 +159,9 @@ const hideReblogs = (store, userId) => { id: userId, reblogs: false, credentials: useOAuthStore().token, - }).then((relationship) => { - store.commit('updateUserRelationship', [relationship]) - }) + }).then(({ data: relationship }) => + store.commit('updateUserRelationship', [relationship]), + ) } const showReblogs = (store, userId) => { @@ -166,7 +169,7 @@ const showReblogs = (store, userId) => { id: userId, reblogs: true, credentials: useOAuthStore().token, - }).then((relationship) => + }).then(({ data: relationship }) => store.commit('updateUserRelationship', [relationship]), ) } @@ -406,16 +409,24 @@ const users = { return fetchUser({ id, credentials: useOAuthStore().token, - }).then((user) => { - store.commit('addNewUsers', [user]) - return user }) + .then(({ data: user }) => { + store.commit('addNewUsers', [user]) + return user + }) + .catch((error) => { + if (error.statusCode === 404) { + console.warn(`User ${id} not found`) + } else { + throw error + } + }) }, fetchUserByName(store, name) { return fetchUserByName({ name, credentials: useOAuthStore().token, - }).then((user) => { + }).then(({ data: user }) => { store.commit('addNewUsers', [user]) return user }) @@ -425,7 +436,7 @@ const users = { fetchUserRelationship({ id, credentials: useOAuthStore().token, - }).then((relationships) => + }).then(({ data: relationships }) => store.commit('updateUserRelationship', relationships), ) } @@ -435,7 +446,9 @@ const users = { fetchUserInLists({ id, credentials: useOAuthStore().token, - }).then((inLists) => store.commit('updateUserInLists', { id, inLists })) + }).then(({ data: inLists }) => + store.commit('updateUserInLists', { id, inLists }), + ) } }, fetchBlocks(store, args) { @@ -445,7 +458,7 @@ const users = { return fetchBlocks({ maxId, credentials: useOAuthStore().token, - }).then((blocks) => { + }).then(({ data: blocks }) => { if (reset) { store.commit('saveBlockIds', map(blocks, 'id')) } else { @@ -483,7 +496,7 @@ const users = { return fetchMutes({ maxId, credentials: useOAuthStore().token, - }).then((mutes) => { + }).then(({ data: mutes }) => { if (reset) { store.commit('saveMuteIds', map(mutes, 'id')) } else { @@ -497,7 +510,7 @@ const users = { }) }, muteUser(store, data) { - return localMuteUser(store, data) + return muteUser(store, data) }, unmuteUser(store, id) { return unmuteUser(store, id) @@ -509,7 +522,7 @@ const users = { return showReblogs(store, id) }, muteUsers(store, data = []) { - return Promise.all(data.map((d) => localMuteUser(store, d))) + return Promise.all(data.map((d) => muteUser(store, d))) }, unmuteUsers(store, ids = []) { return Promise.all(ids.map((d) => unmuteUser(store, d))) @@ -517,7 +530,7 @@ const users = { fetchDomainMutes(store) { return fetchDomainMutes({ credentials: useOAuthStore().token, - }).then((domainMutes) => { + }).then(({ data: domainMutes }) => { store.commit('saveDomainMutes', domainMutes) return domainMutes }) @@ -541,7 +554,7 @@ const users = { id, maxId, credentials: useOAuthStore().token, - }).then((friends) => { + }).then(({ data: friends }) => { commit('addNewUsers', friends) commit('saveFriendIds', { id, friendIds: map(friends, 'id') }) return friends @@ -554,7 +567,7 @@ const users = { id, maxId, credentials: useOAuthStore().token, - }).then((followers) => { + }).then(({ data: followers }) => { commit('addNewUsers', followers) commit('saveFollowerIds', { id, followerIds: map(followers, 'id') }) return followers @@ -571,7 +584,7 @@ const users = { id, notify: true, credentials: useOAuthStore().token, - }).then((relationship) => + }).then(({ data: relationship }) => commit('updateUserRelationship', [relationship]), ) }, @@ -580,7 +593,7 @@ const users = { id, notify: false, credentials: useOAuthStore().token, - }).then((relationship) => + }).then(({ data: relationship }) => commit('updateUserRelationship', [relationship]), ) }, @@ -646,7 +659,7 @@ const users = { return searchUsers({ query, credentials: useOAuthStore().token, - }).then((users) => { + }).then(({ data: users }) => { commit('addNewUsers', users) return users }) @@ -657,7 +670,7 @@ const users = { try { const token = await oauthStore.ensureAppToken() - const data = await register({ + const { data } = await register({ credentials: token, params: { ...userInfo }, }) @@ -681,7 +694,7 @@ const users = { getCaptcha(store) { return getCaptcha({ credentials: useOAuthStore().token, - }) + }).then(({ data }) => data) }, logout(store) { @@ -727,135 +740,128 @@ const users = { verifyCredentials({ credentials: useOAuthStore().token, }) - .then((data) => { - if (!data.error) { - const user = data - // user.credentials = userCredentials - user.credentials = accessToken - user.blockIds = [] - user.muteIds = [] - user.domainMutes = [] - commit('setCurrentUser', user) + .then(({ data: user }) => { + // user.credentials = userCredentials + user.credentials = accessToken + user.blockIds = [] + user.muteIds = [] + user.domainMutes = [] + commit('setCurrentUser', user) - useSyncConfigStore() - .initSyncConfig(user) - .then(() => { - useInterfaceStore() - .applyTheme() - .catch((e) => { - console.error('Error setting theme', e) - }) - }) - useUserHighlightStore().initUserHighlight(user) - commit('addNewUsers', [user]) - - useEmojiStore().fetchEmoji() - - getNotificationPermission().then((permission) => - useInterfaceStore().setNotificationPermission(permission), - ) - - // Do server-side storage migrations - - // Debug snippet to clean up storage and reset migrations - /* - // Reset wordfilter - Object.keys( - useSyncConfigStore().prefsStorage.simple.muteFilters - ).forEach(key => { - useSyncConfigStore().unsetSimplePrefAndSave({ path: 'muteFilters.' + key, value: null }) + useSyncConfigStore() + .initSyncConfig(user) + .then(() => { + useInterfaceStore() + .applyTheme() + .catch((e) => { + console.error('Error setting theme', e) + }) }) + useUserHighlightStore().initUserHighlight(user) + commit('addNewUsers', [user]) - // Reset flag to 0 to re-run migrations - useSyncConfigStore().setFlag({ flag: 'configMigration', value: 0 }) - /**/ + useEmojiStore().fetchEmoji() - if (user.token) { - dispatch('setWsToken', user.token) + getNotificationPermission().then((permission) => + useInterfaceStore().setNotificationPermission(permission), + ) - // Initialize the shout socket. - dispatch('initializeSocket') - } + // Do server-side storage migrations - const startPolling = () => { - // Start getting fresh posts. - dispatch('startFetchingTimeline', { timeline: 'friends' }) + // Debug snippet to clean up storage and reset migrations + /* + // Reset wordfilter + Object.keys( + useSyncConfigStore().prefsStorage.simple.muteFilters + ).forEach(key => { + useSyncConfigStore().unsetSimplePrefAndSave({ path: 'muteFilters.' + key, value: null }) + }) - // Start fetching notifications - dispatch('startFetchingNotifications') + // Reset flag to 0 to re-run migrations + useSyncConfigStore().setFlag({ flag: 'configMigration', value: 0 }) + /**/ - if ( - useInstanceCapabilitiesStore().pleromaChatMessagesAvailable - ) { - // Start fetching chats - dispatch('startFetchingChats') - } - } + if (user.token) { + dispatch('setWsToken', user.token) - useListsStore().startFetching() - useBookmarkFoldersStore().startFetching() + // Initialize the shout socket. + dispatch('initializeSocket') + } - if (user.locked) { - dispatch('startFetchingFollowRequests') - } + const startPolling = () => { + // Start getting fresh posts. + dispatch('startFetchingTimeline', { timeline: 'friends' }) - if (useMergedConfigStore().mergedConfig.useStreamingApi) { - dispatch('fetchTimeline', { - timeline: 'friends', - sinceId: null, - }) - dispatch('fetchNotifications', { sinceId: null }) - dispatch('enableMastoSockets', true) - .catch((error) => { - console.error( - 'Failed initializing MastoAPI Streaming socket', - error, - ) - }) - .then(() => { - dispatch('fetchChats', { latest: true }) - setTimeout( - () => dispatch('setNotificationsSilence', false), - 10000, - ) - }) - } else { - startPolling() - } + // Start fetching notifications + dispatch('startFetchingNotifications') - // Get user mutes - dispatch('fetchMutes') - - useInterfaceStore().setLayoutWidth(windowWidth()) - useInterfaceStore().setLayoutHeight(windowHeight()) - - // Fetch our friends - fetchFriends({ id: user.id }).then((friends) => - commit('addNewUsers', friends), - ) - } else { - const response = data.error - // Authentication failed - commit('endLogin') - - // remove authentication token on client/authentication errors - if ([400, 401, 403, 422].includes(response.status)) { - useOAuthStore().clearToken() - } - - if (response.status === 401) { - reject(new Error('Wrong username or password')) - } else { - reject(new Error('An error occurred, please try again')) + if (useInstanceCapabilitiesStore().pleromaChatMessagesAvailable) { + // Start fetching chats + dispatch('startFetchingChats') } } + + useListsStore().startFetching() + useBookmarkFoldersStore().startFetching() + + if (user.locked) { + dispatch('startFetchingFollowRequests') + } + + if (useMergedConfigStore().mergedConfig.useStreamingApi) { + dispatch('fetchTimeline', { + timeline: 'friends', + sinceId: null, + }) + dispatch('fetchNotifications', { sinceId: null }) + dispatch('enableMastoSockets', true) + .catch((error) => { + console.error( + 'Failed initializing MastoAPI Streaming socket', + error, + ) + }) + .then(() => { + dispatch('fetchChats', { latest: true }) + setTimeout( + () => dispatch('setNotificationsSilence', false), + 10000, + ) + }) + } else { + startPolling() + } + + // Get user mutes + dispatch('fetchMutes') + + useInterfaceStore().setLayoutWidth(windowWidth()) + useInterfaceStore().setLayoutHeight(windowHeight()) + + // Fetch our friends + fetchFriends({ id: user.id }).then(({ data: friends }) => + commit('addNewUsers', friends), + ) commit('endLogin') resolve() }) .catch((error) => { console.error(error) + + // Authentication failed commit('endLogin') - reject(new Error('Failed to connect to server, try again')) + + // remove authentication token on client/authentication errors + if ([400, 401, 403, 422].includes(error.statusCode)) { + useOAuthStore().clearToken() + } + + commit('endLogin') + if (error.tatusCode === 401) { + throw new Error('Wrong username or password', error) + } else { + throw new Error('An error occurred, please try again', error) + } }) }) }, diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index 6258b0ebe..bb94599eb 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -1,4 +1,3 @@ -import { fetchTimeline } from 'src/api/public.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -6,6 +5,8 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j import { useInterfaceStore } from 'src/stores/interface.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { fetchTimeline } from 'src/api/public.js' + const update = ({ store, notifications, older }) => { store.dispatch('addNewNotifications', { notifications, older }) } @@ -97,6 +98,7 @@ const fetchNotifications = ({ store, args, older }) => { throw new Error(`${response.status} ${response.statusText}`) } } + const notifications = response.data update({ store, notifications, older }) return notifications diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index ddc4a2f9a..7fa67e338 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -37,15 +37,16 @@ const postStatus = ({ preview, idempotencyKey, }) - .then((data) => { - if (!data.error && !preview) { - store.dispatch('addNewStatuses', { - statuses: [data], - timeline: 'friends', - showImmediately: true, - noIdUpdate: true, // To prevent missing notices on next pull. - }) - } + .then(({ data }) => { + if (preview) return data + + store.dispatch('addNewStatuses', { + statuses: [data], + timeline: 'friends', + showImmediately: true, + noIdUpdate: true, // To prevent missing notices on next pull. + }) + return data }) .catch((err) => { @@ -67,7 +68,7 @@ const editStatus = ({ }) => { const mediaIds = map(media, 'id') - return editStatus({ + return apiEditStatus({ id: statusId, credentials: store.state.users.currentUser.credentials, status, @@ -77,15 +78,14 @@ const editStatus = ({ mediaIds, contentType, }) - .then((data) => { - if (!data.error) { - store.dispatch('addNewStatuses', { - statuses: [data], - timeline: 'friends', - showImmediately: true, - noIdUpdate: true, // To prevent missing notices on next pull. - }) - } + .then(({ data }) => { + store.dispatch('addNewStatuses', { + statuses: [data], + timeline: 'friends', + showImmediately: true, + noIdUpdate: true, // To prevent missing notices on next pull. + }) + return data }) .catch((err) => { @@ -98,12 +98,14 @@ const editStatus = ({ const uploadMedia = ({ store, formData }) => { const credentials = store.state.users.currentUser.credentials - return apiUploadMedia({ credentials, formData }) + return apiUploadMedia({ credentials, formData }).then(({ data }) => data) } const setMediaDescription = ({ store, id, description }) => { const credentials = store.state.users.currentUser.credentials - return apiSetMediaDescription({ credentials, id, description }) + return apiSetMediaDescription({ credentials, id, description }).then( + ({ data }) => data, + ) } const statusPosterService = { diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index b2ed40e47..efa06258d 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -1,6 +1,5 @@ import { camelCase } from 'lodash' -import { fetchTimeline } from 'src/api/public.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -8,6 +7,8 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j import { useInterfaceStore } from 'src/stores/interface.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { fetchTimeline } from 'src/api/public.js' + const update = ({ store, statuses, diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index a437d56ca..9c9e69d8b 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -87,7 +87,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { loadAdminStuff() { getInstanceDBConfig({ credentials: useOAuthStore().token, - }).then((backendDbConfig) => { + }).then(({ data: backendDbConfig }) => { if (backendDbConfig.error) { if (backendDbConfig.error.status === 400) { backendDbConfig.error.json().then((errorJson) => { @@ -106,7 +106,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { if (this.descriptions === null) { getInstanceConfigDescriptions({ credentials: useOAuthStore().token, - }).then((backendDescriptions) => + }).then(({ data: backendDescriptions }) => this.setInstanceAdminDescriptions({ credentials: useOAuthStore().token, backendDescriptions, @@ -251,7 +251,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { .then(() => getInstanceDBConfig({ credentials: useOAuthStore().token, - }), + }).then(({ data }) => data), ) .then((backendDbConfig) => this.setInstanceAdminSettings({ @@ -294,7 +294,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { .then(() => getInstanceDBConfig({ credentials: useOAuthStore().token, - }), + }).then(({ data }) => data), ) .then((backendDbConfig) => this.setInstanceAdminSettings({ @@ -326,7 +326,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { .then(() => getInstanceDBConfig({ credentials: useOAuthStore().token, - }), + }).then(({ data }) => data), ) .then((backendDbConfig) => this.setInstanceAdminSettings({ backendDbConfig }), @@ -337,7 +337,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { loadFrontendsStuff() { getAvailableFrontends({ credentials: useOAuthStore().token, - }).then((frontends) => this.setAvailableFrontends({ frontends })) + }).then(({ data: frontends }) => + this.setAvailableFrontends({ frontends }), + ) }, setAvailableFrontends({ frontends }) { @@ -355,12 +357,14 @@ export const useAdminSettingsStore = defineStore('adminSettings', { installFrontend() { return installFrontend({ credentials: useOAuthStore().token, - }) + }).then(({ data }) => data) }, // Statuses stuff async fetchStatuses(opts) { - const { total, activities } = await listStatuses({ + const { + data: { total, activities }, + } = await listStatuses({ credentials: useOAuthStore().token, opts, }) @@ -375,20 +379,21 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } }, async changeStatusScope(opts) { - const raw = await changeStatusScope({ + const { data } = await changeStatusScope({ credentials: useOAuthStore().token, opts, }) - const status = parseStatus(raw) + const status = parseStatus(data) await window.vuex.dispatch('addNewStatuses', { statuses: [status] }) }, // Users stuff async fetchUsers(opts) { - const { users, count } = await listUsers({ + const { + data: { users, count }, + } = await listUsers({ credentials: useOAuthStore().token, - opts, }) @@ -412,7 +417,8 @@ export const useAdminSettingsStore = defineStore('adminSettings', { credentials: useOAuthStore().token, screen_name, }) - window.vuex.commit('updateUserAdminData', { user: result }) + + window.vuex.commit('updateUserAdminData', { user: result.data }) }, async deleteUsers({ users }) { const screen_names = users.map((u) => u.screen_name) @@ -423,7 +429,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { screen_names, }) - resultUserIds.forEach((userId) => { + resultUserIds.data.forEach((userId) => { window.vuex.dispatch( 'markStatusesAsDeleted', (status) => userId === status.user.id, @@ -439,7 +445,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { return resendConfirmationEmail({ credentials: useOAuthStore().token, screen_names, - }) + }).then(({ data }) => data) }, requirePasswordChange({ users }) { const screen_names = users.map((u) => u.screen_name) @@ -447,7 +453,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { return requirePasswordChange({ credentials: useOAuthStore().token, screen_names, - }) + }).then(({ data }) => data) }, // Singular only! disableMFA({ user }) { @@ -456,7 +462,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { return disableMFA({ credentials: useOAuthStore().token, screen_name, - }) + }).then(({ data }) => data) }, async setUsersTags({ users, tags, value }) { const screen_names = users.map((u) => u.screen_name) @@ -498,7 +504,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { value, }) - resultUsers.forEach((user) => { + resultUsers.data.forEach((user) => { window.vuex.commit('updateUserAdminData', { user }) }) }, @@ -512,7 +518,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { value, }) - resultUsers.forEach((user) => { + resultUsers.data.forEach((user) => { window.vuex.commit('updateUserAdminData', { user }) }) }, @@ -538,27 +544,31 @@ export const useAdminSettingsStore = defineStore('adminSettings', { screen_names, }) - resultUsers.forEach((user) => { + resultUsers.data.forEach((user) => { window.vuex.commit('updateUserAdminData', { user }) }) }, reloadEmoji() { - return reloadEmoji({ credentials: useOAuthStore().token }) + return reloadEmoji({ credentials: useOAuthStore().token }).then( + ({ data }) => data, + ) }, importEmojiFromFS() { - return importEmojiFromFS({ credentials: useOAuthStore().token }) + return importEmojiFromFS({ credentials: useOAuthStore().token }).then( + ({ data }) => data, + ) }, listEmojiPacks(params) { return listEmojiPacks({ ...params, - credentials: useOAuthStore().token - }) + credentials: useOAuthStore().token, + }).then(({ data }) => data) }, listRemoteEmojiPacks(params) { return listRemoteEmojiPacks({ ...params, credentials: useOAuthStore().token, - }) + }).then(({ data }) => data) }, addNewEmojiFile({ packName, file, shortcode, filename }) { return addNewEmojiFile({ @@ -567,7 +577,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { shortcode, filename, credentials: useOAuthStore().token, - }) + }).then(({ data }) => data) }, downloadRemoteEmojiPack({ instance, packName, as }) { return downloadRemoteEmojiPack({ @@ -575,33 +585,33 @@ export const useAdminSettingsStore = defineStore('adminSettings', { packName, as, credentials: useOAuthStore().token, - }) + }).then(({ data }) => data) }, downloadRemoteEmojiPackZIP({ url, packName }) { return downloadRemoteEmojiPackZIP({ url, packName, credentials: useOAuthStore().token, - }) + }).then(({ data }) => data) }, createEmojiPack({ name }) { return createEmojiPack({ name, credentials: useOAuthStore().token, - }) + }).then(({ data }) => data) }, deleteEmojiPack({ name }) { return createEmojiPack({ name, credentials: useOAuthStore().token, - }) + }).then(({ data }) => data) }, saveEmojiPackMetadata({ name, newData }) { return createEmojiPack({ name, newData, credentials: useOAuthStore().token, - }) + }).then(({ data }) => data) }, }, }) diff --git a/src/stores/announcements.js b/src/stores/announcements.js index 2b738b905..f739ae726 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -44,15 +44,16 @@ export const useAnnouncementsStore = defineStore('announcements', { const fetchAnnouncements = async () => { if (!isAdmin) { - return getAnnouncements({ + const result = await getAnnouncements({ credentials: useOAuthStore().token, }) + return result.data } - const all = await adminGetAnnouncements({ + const { data: all } = await adminGetAnnouncements({ credentials: useOAuthStore().token, }) - const visible = await getAnnouncements({ + const { data: visible } = await getAnnouncements({ credentials: useOAuthStore().token, }) const visibleObject = visible.reduce((a, c) => { diff --git a/src/stores/bookmark_folders.js b/src/stores/bookmark_folders.js index 928ad13a6..6ffec807b 100644 --- a/src/stores/bookmark_folders.js +++ b/src/stores/bookmark_folders.js @@ -31,10 +31,7 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { this.fetcher = fetchBookmarkFolders({ credentials: useOAuthStore().token, }) - .then( - (folders) => this.setBookmarkFolders(folders), - (rej) => console.error(rej), - ) + .then(({ data: folders }) => this.setBookmarkFolders(folders)) .catch((e) => { console.error(e) }) @@ -61,7 +58,7 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { name, emoji, credentials: useOAuthStore().token, - }).then((folder) => { + }).then(({ data: folder }) => { this.setBookmarkFolder(folder) return folder }) @@ -72,7 +69,7 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { folderId, name, emoji, - }).then((folder) => { + }).then(({ data: folder }) => { this.setBookmarkFolder(folder) return folder }) diff --git a/src/stores/emoji.js b/src/stores/emoji.js index aefa79342..6ea8d1032 100644 --- a/src/stores/emoji.js +++ b/src/stores/emoji.js @@ -1,11 +1,11 @@ import { merge } from 'lodash' import { defineStore } from 'pinia' -import { useOAuthStore } from 'src/stores/oauth.js' import { useInstanceStore } from 'src/stores/instance.js' +import { useOAuthStore } from 'src/stores/oauth.js' -import { ensureFinalFallback } from 'src/i18n/languages.js' import { listEmojiPacks } from 'src/api/public.js' +import { ensureFinalFallback } from 'src/i18n/languages.js' import { annotationsLoader } from 'virtual:pleroma-fe/emoji-annotations' @@ -188,7 +188,8 @@ export const useEmojiStore = defineStore('emoji', { this.adminPacksLocalLoading = true this.adminPacksLocal = await this.getAdminPacks( useInstanceStore().server, - (params) => listEmojiPacks({ + (params) => + listEmojiPacks({ ...params, credentials: useOAuthStore().token, }), @@ -221,14 +222,13 @@ export const useEmojiStore = defineStore('emoji', { instance, page: i, pageSize, - }) - .then((pageData) => { - if (pageData.error !== undefined) { - return Promise.reject(pageData.error) - } + }).then((pageData) => { + if (pageData.error !== undefined) { + return Promise.reject(pageData.error) + } - return pageData.packs - }), + return pageData.packs + }), ) } diff --git a/src/stores/instance.js b/src/stores/instance.js index 8abb8078a..08bcde1ae 100644 --- a/src/stores/instance.js +++ b/src/stores/instance.js @@ -11,10 +11,11 @@ import { LOCAL_DEFAULT_CONFIG_DEFINITIONS, validateSetting, } from '../modules/default_config_state.js' -import { fetchKnownDomains } from 'src/api/public.js' import { useInterfaceStore } from 'src/stores/interface.js' +import { fetchKnownDomains } from 'src/api/public.js' + const REMOTE_INTERACTION_URL = '/main/ostatus' const ROOT_STATE_DEFINITIONS = { diff --git a/src/stores/lists.js b/src/stores/lists.js index 20529f06b..b612b403a 100644 --- a/src/stores/lists.js +++ b/src/stores/lists.js @@ -38,10 +38,7 @@ export const useListsStore = defineStore('lists', { this.fetcher = fetchLists({ credentials: useOAuthStore().token, }) - .then( - (lists) => this.setLists(lists), - (rej) => console.error(rej), - ) + .then(({ data: lists }) => this.setLists(lists)) .catch((e) => { console.error(e) }) diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js index 5ff42adaf..f022ffe86 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -20,10 +20,11 @@ import { toRaw } from 'vue' import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { useInstanceStore } from 'src/stores/instance.js' import { useLocalConfigStore } from 'src/stores/local_config.js' +import { useOAuthStore } from 'src/stores/oauth.js' +import { updateProfileJSON } from 'src/api/user.js' import { storage } from 'src/lib/storage.js' import { makeUndefined, @@ -32,7 +33,6 @@ import { validateSetting, } from 'src/modules/default_config_state.js' import { oldDefaultConfigSync } from 'src/modules/old_default_config_state.js' -import { updateProfileJSON } from 'src/api/user.js' export const VERSION = 2 export const NEW_USER_DATE = new Date('2026-03-16') // date of writing this, basically diff --git a/src/stores/user_highlight.js b/src/stores/user_highlight.js index 41570521f..ca334f579 100644 --- a/src/stores/user_highlight.js +++ b/src/stores/user_highlight.js @@ -16,8 +16,8 @@ import { toRaw } from 'vue' import { useOAuthStore } from 'src/stores/oauth.js' -import { storage } from 'src/lib/storage.js' import { updateProfileJSON } from 'src/api/user.js' +import { storage } from 'src/lib/storage.js' export const NEW_USER_DATE = new Date('2022-08-04') // date of writing this, basically From 72f41d78ff1d5c2eb61f7b9fd888ceeecdcfb706 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 18:15:41 +0300 Subject: [PATCH 27/86] fix --- src/api/public.js | 2 +- src/services/follow_manipulate/follow_manipulate.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index 573f97c7c..0acd95a18 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -110,7 +110,7 @@ export const fetchUserByName = ({ name, credentials }) => credentials, params: { acct: name }, }) - .then((data) => data.id) + .then(({ data }) => data.id) .catch((error) => { if (error && error.statusCode === 404) { // Either the backend does not support lookup endpoint, diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index 31b1686e9..759ba67d9 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -13,7 +13,7 @@ const fetchRelationship = (attempt, userId, store) => id: userId, credentials: useOAuthStore().token, }) - .then((relationship) => { + .then(({ data: relationship }) => { store.commit('updateUserRelationship', [relationship]) return relationship }) @@ -36,7 +36,7 @@ const fetchRelationship = (attempt, userId, store) => }) export const requestFollow = async (userId, store) => { - const updated = await followUser({ + const { data: updated } = await followUser({ id: userId, credentials: useOAuthStore().token, }) @@ -58,7 +58,7 @@ export const requestFollow = async (userId, store) => { } export const requestUnfollow = async (userId, store) => { - const updated = await unfollowUser({ + const { data: updated } = await unfollowUser({ id: userId, credentials: useOAuthStore().token, }) From 50044efaf9412708959296583341c3c150885208 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 18:15:54 +0300 Subject: [PATCH 28/86] load admin annoucements actions code on-demand --- src/stores/announcements.js | 108 ++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/src/stores/announcements.js b/src/stores/announcements.js index f739ae726..52a21182b 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -2,12 +2,6 @@ import { defineStore } from 'pinia' import { useOAuthStore } from 'src/stores/oauth.js' -import { - getAnnouncements as adminGetAnnouncements, - deleteAnnouncement, - editAnnouncement, - postAnnouncement, -} from 'src/api/admin.js' import { getAnnouncements } from 'src/api/public.js' import { dismissAnnouncement } from 'src/api/user.js' @@ -18,6 +12,7 @@ export const useAnnouncementsStore = defineStore('announcements', { announcements: [], supportsAnnouncements: true, fetchAnnouncementsTimer: undefined, + adminActions: {}, }), getters: { unreadAnnouncementCount() { @@ -32,34 +27,37 @@ export const useAnnouncementsStore = defineStore('announcements', { }, }, actions: { - fetchAnnouncements() { - if (!this.supportsAnnouncements) { - return Promise.resolve() - } + async fetchAnnouncements() { + if (!this.supportsAnnouncements) return const currentUser = window.vuex.state.users.currentUser const isAdmin = currentUser && currentUser.privileges.has('announcements_manage_announcements') - const fetchAnnouncements = async () => { - if (!isAdmin) { - const result = await getAnnouncements({ + try { + if (isAdmin) { + this.adminActions = await import('src/api/admin.js') + } else { + const all = await getAnnouncements({ credentials: useOAuthStore().token, }) - return result.data + return all.data } - const { data: all } = await adminGetAnnouncements({ + const { data: all } = await this.adminActions.getAnnouncements({ credentials: useOAuthStore().token, }) + const { data: visible } = await getAnnouncements({ credentials: useOAuthStore().token, }) + const visibleObject = visible.reduce((a, c) => { a[c.id] = c return a }, {}) + const getWithinVisible = (announcement) => visibleObject[announcement.id] @@ -72,22 +70,16 @@ export const useAnnouncementsStore = defineStore('announcements', { } }) - return all + this.announcements = all + } catch (error) { + // If and only if backend does not support announcements, it would return 404. + // In this case, silently ignores it. + if (error && error.statusCode === 404) { + this.supportsAnnouncements = false + } else { + throw error + } } - - return fetchAnnouncements() - .then((announcements) => { - this.announcements = announcements - }) - .catch((error) => { - // If and only if backend does not support announcements, it would return 404. - // In this case, silently ignores it. - if (error && error.statusCode === 404) { - this.supportsAnnouncements = false - } else { - throw error - } - }) }, markAnnouncementAsRead(id) { return dismissAnnouncement({ @@ -122,35 +114,41 @@ export const useAnnouncementsStore = defineStore('announcements', { clearInterval(interval) }, postAnnouncement({ content, startsAt, endsAt, allDay }) { - return postAnnouncement({ - credentials: useOAuthStore().token, - content, - startsAt, - endsAt, - allDay, - }).then(() => { - return this.fetchAnnouncements() - }) + return this.adminActions + .postAnnouncement({ + credentials: useOAuthStore().token, + content, + startsAt, + endsAt, + allDay, + }) + .then(() => { + return this.fetchAnnouncements() + }) }, editAnnouncement({ id, content, startsAt, endsAt, allDay }) { - return editAnnouncement({ - id, - content, - startsAt, - endsAt, - allDay, - credentials: useOAuthStore().token, - }).then(() => { - return this.fetchAnnouncements() - }) + return this.adminActions + .editAnnouncement({ + id, + content, + startsAt, + endsAt, + allDay, + credentials: useOAuthStore().token, + }) + .then(() => { + return this.fetchAnnouncements() + }) }, deleteAnnouncement(id) { - return deleteAnnouncement({ - id, - credentials: useOAuthStore().token, - }).then(() => { - return this.fetchAnnouncements() - }) + return this.adminActions + .deleteAnnouncement({ + id, + credentials: useOAuthStore().token, + }) + .then(() => { + return this.fetchAnnouncements() + }) }, }, }) From 61f93ad9557d72cc4ac46daeb9fa296f595cda2e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 18:19:44 +0300 Subject: [PATCH 29/86] do the same with user actions --- src/stores/announcements.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/stores/announcements.js b/src/stores/announcements.js index 52a21182b..f780f6dd7 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -3,7 +3,6 @@ import { defineStore } from 'pinia' import { useOAuthStore } from 'src/stores/oauth.js' import { getAnnouncements } from 'src/api/public.js' -import { dismissAnnouncement } from 'src/api/user.js' const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5 @@ -13,6 +12,7 @@ export const useAnnouncementsStore = defineStore('announcements', { supportsAnnouncements: true, fetchAnnouncementsTimer: undefined, adminActions: {}, + userActions: {}, }), getters: { unreadAnnouncementCount() { @@ -36,6 +36,10 @@ export const useAnnouncementsStore = defineStore('announcements', { currentUser.privileges.has('announcements_manage_announcements') try { + if (currentUser) { + this.userActions = await import('src/api/user.js') + } + if (isAdmin) { this.adminActions = await import('src/api/admin.js') } else { @@ -82,7 +86,7 @@ export const useAnnouncementsStore = defineStore('announcements', { } }, markAnnouncementAsRead(id) { - return dismissAnnouncement({ + return this.userActions.dismissAnnouncement({ id, credentials: useOAuthStore().token, }).then(() => { From 7a02ae396e3a577a00200d6b33d3531ac7bb2a70 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 18:30:53 +0300 Subject: [PATCH 30/86] move mfa.js to the rest of the API --- src/api/mfa.js | 46 ++++++++++++++++++++ src/components/mfa_form/recovery_form.js | 18 ++++---- src/components/mfa_form/totp_form.js | 18 ++++---- src/services/new_api/mfa.js | 54 ------------------------ 4 files changed, 64 insertions(+), 72 deletions(-) create mode 100644 src/api/mfa.js delete mode 100644 src/services/new_api/mfa.js diff --git a/src/api/mfa.js b/src/api/mfa.js new file mode 100644 index 000000000..ff5df6560 --- /dev/null +++ b/src/api/mfa.js @@ -0,0 +1,46 @@ +import { promisedRequest } from './helpers.js' + +export const verifyOTPCode = ({ + clientId, + clientSecret, + instance, + mfaToken, + code, +}) => { + const formData = new window.FormData() + + formData.append('client_id', clientId) + formData.append('client_secret', clientSecret) + formData.append('mfa_token', mfaToken) + formData.append('code', code) + formData.append('challenge_type', 'totp') + + return promisedRequest({ + url: '/oauth/mfa/challenge' + method: 'POST', + formData, + }) +} + +export const verifyRecoveryCode = ({ + clientId, + clientSecret, + instance, + mfaToken, + code, +}) => { + const url = `${instance}` + const formData = new window.FormData() + + formData.append('client_id', clientId) + formData.append('client_secret', clientSecret) + formData.append('mfa_token', mfaToken) + formData.append('code', code) + formData.append('challenge_type', 'recovery') + + return promisedRequest({ + url: '/oauth/mfa/challenge' + method: 'POST', + formData, + }) +} diff --git a/src/components/mfa_form/recovery_form.js b/src/components/mfa_form/recovery_form.js index f4ddcadd0..46e38d29c 100644 --- a/src/components/mfa_form/recovery_form.js +++ b/src/components/mfa_form/recovery_form.js @@ -1,6 +1,6 @@ import { mapActions, mapState, mapStores } from 'pinia' -import mfaApi from '../../services/new_api/mfa.js' +import { verifyRecoveryCode } from 'src/api/mfa.js' import { useAuthFlowStore } from 'src/stores/auth_flow.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -43,18 +43,18 @@ export default { code: this.code, } - mfaApi.verifyRecoveryCode(data).then((result) => { - if (result.error) { - this.error = result.error + verifyRecoveryCode(data) + .then((result) => { + this.login(result).then(() => { + this.$router.push({ name: 'friends' }) + }) + }) + .catch((error) => { + this.error = error this.code = null this.focusOnCodeInput() return - } - - this.login(result).then(() => { - this.$router.push({ name: 'friends' }) }) - }) }, }, } diff --git a/src/components/mfa_form/totp_form.js b/src/components/mfa_form/totp_form.js index 6d51cba94..4cd635550 100644 --- a/src/components/mfa_form/totp_form.js +++ b/src/components/mfa_form/totp_form.js @@ -1,6 +1,6 @@ import { mapActions, mapState, mapStores } from 'pinia' -import mfaApi from '../../services/new_api/mfa.js' +import { verifyOTPCode } from 'src/api/mfa.js' import { useAuthFlowStore } from 'src/stores/auth_flow.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -46,18 +46,18 @@ export default { code: this.code, } - mfaApi.verifyOTPCode(data).then((result) => { - if (result.error) { - this.error = result.error + verifyOTPCode(data) + .then(({ data: result }) => { + this.login(result).then(() => { + this.$router.push({ name: 'friends' }) + }) + }) + .catch((error) => { + this.error = error this.code = null this.focusOnCodeInput() return - } - - this.login(result).then(() => { - this.$router.push({ name: 'friends' }) }) - }) }, }, } diff --git a/src/services/new_api/mfa.js b/src/services/new_api/mfa.js deleted file mode 100644 index 1d35b65a8..000000000 --- a/src/services/new_api/mfa.js +++ /dev/null @@ -1,54 +0,0 @@ -const verifyOTPCode = ({ - clientId, - clientSecret, - instance, - mfaToken, - code, -}) => { - const url = `${instance}/oauth/mfa/challenge` - const form = new window.FormData() - - form.append('client_id', clientId) - form.append('client_secret', clientSecret) - form.append('mfa_token', mfaToken) - form.append('code', code) - form.append('challenge_type', 'totp') - - return window - .fetch(url, { - method: 'POST', - body: form, - }) - .then((data) => data.json()) -} - -const verifyRecoveryCode = ({ - clientId, - clientSecret, - instance, - mfaToken, - code, -}) => { - const url = `${instance}/oauth/mfa/challenge` - const form = new window.FormData() - - form.append('client_id', clientId) - form.append('client_secret', clientSecret) - form.append('mfa_token', mfaToken) - form.append('code', code) - form.append('challenge_type', 'recovery') - - return window - .fetch(url, { - method: 'POST', - body: form, - }) - .then((data) => data.json()) -} - -const mfa = { - verifyOTPCode, - verifyRecoveryCode, -} - -export default mfa From d2dcdfbd80268532f093268f39a2baf9e711ea3a Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 19:04:05 +0300 Subject: [PATCH 31/86] move oauth api --- src/api/mfa.js | 5 +- src/api/oauth.js | 138 ++++++++++++ src/components/login_form/login_form.js | 64 +++--- src/components/mfa_form/recovery_form.js | 4 +- src/components/mfa_form/totp_form.js | 4 +- .../oauth_callback/oauth_callback.js | 26 ++- src/modules/users.js | 4 +- src/services/new_api/oauth.js | 198 ------------------ src/stores/announcements.js | 22 +- src/stores/oauth.js | 20 +- 10 files changed, 204 insertions(+), 281 deletions(-) create mode 100644 src/api/oauth.js delete mode 100644 src/services/new_api/oauth.js diff --git a/src/api/mfa.js b/src/api/mfa.js index ff5df6560..8c1677fbe 100644 --- a/src/api/mfa.js +++ b/src/api/mfa.js @@ -16,7 +16,7 @@ export const verifyOTPCode = ({ formData.append('challenge_type', 'totp') return promisedRequest({ - url: '/oauth/mfa/challenge' + url: '/oauth/mfa/challenge', method: 'POST', formData, }) @@ -29,7 +29,6 @@ export const verifyRecoveryCode = ({ mfaToken, code, }) => { - const url = `${instance}` const formData = new window.FormData() formData.append('client_id', clientId) @@ -39,7 +38,7 @@ export const verifyRecoveryCode = ({ formData.append('challenge_type', 'recovery') return promisedRequest({ - url: '/oauth/mfa/challenge' + url: `${instance}/oauth/mfa/challenge`, method: 'POST', formData, }) diff --git a/src/api/oauth.js b/src/api/oauth.js new file mode 100644 index 000000000..ce4c2f6fc --- /dev/null +++ b/src/api/oauth.js @@ -0,0 +1,138 @@ +import { reduce } from 'lodash' + +import { paramsString, promisedRequest } from './helpers.js' + +import { StatusCodeError } from 'src/services/errors/errors.js' + +const REDIRECT_URI = `${window.location.origin}/oauth-callback` + +export const createApp = ({ instance }) => { + const formData = new window.FormData() + + formData.append('client_name', 'PleromaFE') + formData.append('website', 'https://pleroma.social') + formData.append('redirect_uris', REDIRECT_URI) + formData.append('scopes', 'read write follow push admin') + + return promisedRequest({ + url: `${instance}/api/v1/apps`, + method: 'POST', + formData, + }).then(({ data, ...rest }) => ({ + ...rest, + data: { + ...data, + clientId: data.client_id, + clientSecret: data.client_secret, + }, + })) +} + +export const getLoginUrl = ({ instance, clientId }) => { + const data = { + responseType: 'code', + clientId, + redirectUri: REDIRECT_URI, + scope: 'read write follow push admin', + } + + return `${instance}/oauth/authorize${paramsString(data)}` +} + +export const getTokenWithCredentials = ({ + clientId, + clientSecret, + instance, + username, + password, +}) => { + const formData = new window.FormData() + + formData.append('client_id', clientId) + formData.append('client_secret', clientSecret) + formData.append('grant_type', 'password') + formData.append('username', username) + formData.append('password', password) + + return promisedRequest({ + url: `${instance}/oauth/token`, + method: 'POST', + formData, + }) +} + +export const getToken = ({ clientId, clientSecret, instance, code }) => { + const formData = new window.FormData() + + formData.append('client_id', clientId) + formData.append('client_secret', clientSecret) + formData.append('grant_type', 'authorization_code') + formData.append('code', code) + formData.append('redirect_uri', `${window.location.origin}/oauth-callback`) + + return promisedRequest({ + url: `${instance}/oauth/token`, + method: 'POST', + formData, + }) +} + +export const getClientToken = ({ clientId, clientSecret, instance }) => { + const formData = new window.FormData() + + formData.append('client_id', clientId) + formData.append('client_secret', clientSecret) + formData.append('grant_type', 'client_credentials') + formData.append('redirect_uri', `${window.location.origin}/oauth-callback`) + + return promisedRequest({ + url: `${instance}/oauth/token`, + method: 'POST', + formData, + }) +} +export const verifyOTPCode = ({ app, instance, mfaToken, code }) => { + const formData = new window.FormData() + + formData.append('client_id', app.client_id) + formData.append('client_secret', app.client_secret) + formData.append('mfa_token', mfaToken) + formData.append('code', code) + formData.append('challenge_type', 'totp') + + return promisedRequest({ + url: `${instance}/oauth/mfa/challenge`, + method: 'POST', + formData, + }) +} + +export const verifyRecoveryCode = ({ app, instance, mfaToken, code }) => { + const formData = new window.FormData() + + formData.append('client_id', app.client_id) + formData.append('client_secret', app.client_secret) + formData.append('mfa_token', mfaToken) + formData.append('code', code) + formData.append('challenge_type', 'recovery') + + return promisedRequest({ + url: `${instance}/oauth/mfa/challenge`, + method: 'POST', + formData, + }) +} + +export const revokeToken = ({ app, instance, token }) => { + const formData = new window.FormData() + + formData.append('client_id', app.clientId) + formData.append('client_secret', app.clientSecret) + formData.append('token', token) + + return promisedRequest({ + url: `${instance}/oauth/revoke`, + method: 'POST', + formData, + }) +} diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index b16fb8d2f..8f705f269 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -1,12 +1,12 @@ import { mapActions, mapState as mapPiniaState, mapStores } from 'pinia' import { mapState } from 'vuex' -import oauthApi from '../../services/new_api/oauth.js' - import { useAuthFlowStore } from 'src/stores/auth_flow.js' import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' +import { getLoginUrl, getTokenWithCredentials } from 'src/api/oauth.js' + import { library } from '@fortawesome/fontawesome-svg-core' import { faTimes } from '@fortawesome/free-solid-svg-icons' @@ -35,19 +35,13 @@ const LoginForm = { this.isTokenAuth ? this.submitToken() : this.submitPassword() }, submitToken() { - const data = { - instance: this.server, - commit: this.$store.commit, - } - // NOTE: we do not really need the app token, but obtaining a token and // calling verify_credentials is the only way to ensure the app still works. this.ensureAppToken().then(() => { - const app = { + window.location.href = getLoginUrl({ clientId: this.clientId, - clientSecret: this.clientSecret, - } - oauthApi.login({ ...app, ...data }) + instance: this.server, + }) }) }, submitPassword() { @@ -56,37 +50,31 @@ const LoginForm = { // NOTE: we do not really need the app token, but obtaining a token and // calling verify_credentials is the only way to ensure the app still works. this.ensureAppToken().then(() => { - const app = { + getTokenWithCredentials({ clientId: this.clientId, clientSecret: this.clientSecret, - } - - oauthApi - .getTokenWithCredentials({ - ...app, - instance: this.server, - username: this.user.username, - password: this.user.password, - }) - .then((result) => { - if (result.error) { - if (result.error === 'mfa_required') { - this.requireMFA({ settings: result }) - } else if (result.identifier === 'password_reset_required') { - this.$router.push({ - name: 'password-reset', - params: { passwordResetRequested: true }, - }) - } else { - this.error = result.error - this.focusOnPasswordInput() - } - return + instance: this.server, + username: this.user.username, + password: this.user.password, + }).then((result) => { + if (result.error) { + if (result.error === 'mfa_required') { + this.requireMFA({ settings: result }) + } else if (result.identifier === 'password_reset_required') { + this.$router.push({ + name: 'password-reset', + params: { passwordResetRequested: true }, + }) + } else { + this.error = result.error + this.focusOnPasswordInput() } - this.login(result).then(() => { - this.$router.push({ name: 'friends' }) - }) + return + } + this.login(result).then(() => { + this.$router.push({ name: 'friends' }) }) + }) }) }, clearError() { diff --git a/src/components/mfa_form/recovery_form.js b/src/components/mfa_form/recovery_form.js index 46e38d29c..e04218bd8 100644 --- a/src/components/mfa_form/recovery_form.js +++ b/src/components/mfa_form/recovery_form.js @@ -1,11 +1,11 @@ import { mapActions, mapState, mapStores } from 'pinia' -import { verifyRecoveryCode } from 'src/api/mfa.js' - import { useAuthFlowStore } from 'src/stores/auth_flow.js' import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' +import { verifyRecoveryCode } from 'src/api/mfa.js' + import { library } from '@fortawesome/fontawesome-svg-core' import { faTimes } from '@fortawesome/free-solid-svg-icons' diff --git a/src/components/mfa_form/totp_form.js b/src/components/mfa_form/totp_form.js index 4cd635550..056098c25 100644 --- a/src/components/mfa_form/totp_form.js +++ b/src/components/mfa_form/totp_form.js @@ -1,11 +1,11 @@ import { mapActions, mapState, mapStores } from 'pinia' -import { verifyOTPCode } from 'src/api/mfa.js' - import { useAuthFlowStore } from 'src/stores/auth_flow.js' import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' +import { verifyOTPCode } from 'src/api/mfa.js' + import { library } from '@fortawesome/fontawesome-svg-core' import { faTimes } from '@fortawesome/free-solid-svg-icons' diff --git a/src/components/oauth_callback/oauth_callback.js b/src/components/oauth_callback/oauth_callback.js index 4bcb9803e..04f79425e 100644 --- a/src/components/oauth_callback/oauth_callback.js +++ b/src/components/oauth_callback/oauth_callback.js @@ -1,8 +1,8 @@ -import oauth from '../../services/new_api/oauth.js' - import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' +import { getToken } from 'src/api/oauth.js' + const oac = { props: ['code'], mounted() { @@ -10,18 +10,16 @@ const oac = { const oauthStore = useOAuthStore() const { clientId, clientSecret } = oauthStore - oauth - .getToken({ - clientId, - clientSecret, - instance: useInstanceStore().server, - code: this.code, - }) - .then((result) => { - oauthStore.setToken(result.access_token) - this.$store.dispatch('loginUser', result.access_token) - this.$router.push({ name: 'friends' }) - }) + getToken({ + clientId, + clientSecret, + instance: useInstanceStore().server, + code: this.code, + }).then(({ data: result }) => { + oauthStore.setToken(result.access_token) + this.$store.dispatch('loginUser', result.access_token) + this.$router.push({ name: 'friends' }) + }) } }, } diff --git a/src/modules/users.js b/src/modules/users.js index 2eee21333..5573928b4 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -9,7 +9,6 @@ import { uniq, } from 'lodash' -import oauthApi from '../services/new_api/oauth.js' import { registerPushNotifications, unregisterPushNotifications, @@ -30,6 +29,7 @@ import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' +import { revokeToken } from 'src/api/oauth.js' import { fetchFollowers, fetchFriends, @@ -711,7 +711,7 @@ const users = { token: oauth.userToken, } - return oauthApi.revokeToken(params) + return revokeToken(params) }) .then(() => { store.commit('clearCurrentUser') diff --git a/src/services/new_api/oauth.js b/src/services/new_api/oauth.js deleted file mode 100644 index b803e2146..000000000 --- a/src/services/new_api/oauth.js +++ /dev/null @@ -1,198 +0,0 @@ -import { reduce } from 'lodash' - -import { StatusCodeError } from 'src/services/errors/errors.js' - -const REDIRECT_URI = `${window.location.origin}/oauth-callback` - -export const getJsonOrError = async (response) => { - if (response.ok) { - return response.json().catch((error) => { - throw new StatusCodeError(response.status, error, {}, response) - }) - } else { - throw new StatusCodeError( - response.status, - await response.text(), - {}, - response, - ) - } -} - -export const createApp = (instance) => { - const url = `${instance}/api/v1/apps` - const form = new window.FormData() - - form.append('client_name', 'PleromaFE') - form.append('website', 'https://pleroma.social') - form.append('redirect_uris', REDIRECT_URI) - form.append('scopes', 'read write follow push admin') - - return window - .fetch(url, { - method: 'POST', - body: form, - }) - .then(getJsonOrError) - .then((app) => ({ - clientId: app.client_id, - clientSecret: app.client_secret, - })) -} - -export const verifyAppToken = ({ instance, appToken }) => { - return window - .fetch(`${instance}/api/v1/apps/verify_credentials`, { - method: 'GET', - headers: { Authorization: `Bearer ${appToken}` }, - }) - .then(getJsonOrError) -} - -const login = ({ instance, clientId }) => { - const data = { - response_type: 'code', - client_id: clientId, - redirect_uri: REDIRECT_URI, - scope: 'read write follow push admin', - } - - const dataString = reduce( - data, - (acc, v, k) => { - const encoded = `${k}=${encodeURIComponent(v)}` - if (!acc) { - return encoded - } else { - return `${acc}&${encoded}` - } - }, - false, - ) - - // Do the redirect... - const url = `${instance}/oauth/authorize?${dataString}` - - window.location.href = url -} - -const getTokenWithCredentials = ({ - clientId, - clientSecret, - instance, - username, - password, -}) => { - const url = `${instance}/oauth/token` - const form = new window.FormData() - - form.append('client_id', clientId) - form.append('client_secret', clientSecret) - form.append('grant_type', 'password') - form.append('username', username) - form.append('password', password) - - return window - .fetch(url, { - method: 'POST', - body: form, - }) - .then((data) => data.json()) -} - -const getToken = ({ clientId, clientSecret, instance, code }) => { - const url = `${instance}/oauth/token` - const form = new window.FormData() - - form.append('client_id', clientId) - form.append('client_secret', clientSecret) - form.append('grant_type', 'authorization_code') - form.append('code', code) - form.append('redirect_uri', `${window.location.origin}/oauth-callback`) - - return window - .fetch(url, { - method: 'POST', - body: form, - }) - .then((data) => data.json()) -} - -export const getClientToken = ({ clientId, clientSecret, instance }) => { - const url = `${instance}/oauth/token` - const form = new window.FormData() - - form.append('client_id', clientId) - form.append('client_secret', clientSecret) - form.append('grant_type', 'client_credentials') - form.append('redirect_uri', `${window.location.origin}/oauth-callback`) - - return window - .fetch(url, { - method: 'POST', - body: form, - }) - .then(getJsonOrError) -} -const verifyOTPCode = ({ app, instance, mfaToken, code }) => { - const url = `${instance}/oauth/mfa/challenge` - const form = new window.FormData() - - form.append('client_id', app.client_id) - form.append('client_secret', app.client_secret) - form.append('mfa_token', mfaToken) - form.append('code', code) - form.append('challenge_type', 'totp') - - return window - .fetch(url, { - method: 'POST', - body: form, - }) - .then((data) => data.json()) -} - -const verifyRecoveryCode = ({ app, instance, mfaToken, code }) => { - const url = `${instance}/oauth/mfa/challenge` - const form = new window.FormData() - - form.append('client_id', app.client_id) - form.append('client_secret', app.client_secret) - form.append('mfa_token', mfaToken) - form.append('code', code) - form.append('challenge_type', 'recovery') - - return window - .fetch(url, { - method: 'POST', - body: form, - }) - .then((data) => data.json()) -} - -const revokeToken = ({ app, instance, token }) => { - const url = `${instance}/oauth/revoke` - const form = new window.FormData() - - form.append('client_id', app.clientId) - form.append('client_secret', app.clientSecret) - form.append('token', token) - - return window - .fetch(url, { - method: 'POST', - body: form, - }) - .then((data) => data.json()) -} - -const oauth = { - login, - getToken, - getTokenWithCredentials, - verifyOTPCode, - verifyRecoveryCode, - revokeToken, -} - -export default oauth diff --git a/src/stores/announcements.js b/src/stores/announcements.js index f780f6dd7..4668b286a 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -86,18 +86,20 @@ export const useAnnouncementsStore = defineStore('announcements', { } }, markAnnouncementAsRead(id) { - return this.userActions.dismissAnnouncement({ - id, - credentials: useOAuthStore().token, - }).then(() => { - const index = this.announcements.findIndex((a) => a.id === id) + return this.userActions + .dismissAnnouncement({ + id, + credentials: useOAuthStore().token, + }) + .then(() => { + const index = this.announcements.findIndex((a) => a.id === id) - if (index < 0) { - return - } + if (index < 0) { + return + } - this.announcements[index].read = true - }) + this.announcements[index].read = true + }) }, startFetchingAnnouncements() { if (this.fetchAnnouncementsTimer) { diff --git a/src/stores/oauth.js b/src/stores/oauth.js index 116169b81..55e8e759c 100644 --- a/src/stores/oauth.js +++ b/src/stores/oauth.js @@ -2,11 +2,8 @@ import { defineStore } from 'pinia' import { useInstanceStore } from 'src/stores/instance.js' -import { - createApp, - getClientToken, - verifyAppToken, -} from 'src/services/new_api/oauth.js' +import { createApp, getClientToken } from 'src/api/oauth.js' +import { verifyCredentials } from 'src/api/public.js' // status codes about verifyAppToken (GET /api/v1/apps/verify_credentials) const isAppTokenRejected = (error) => @@ -61,9 +58,9 @@ export const useOAuthStore = defineStore('oauth', { }, async createApp() { const instance = useInstanceStore().server - const app = await createApp(instance) - this.setClientData(app) - return app + const app = await createApp({ instance }) + this.setClientData(app.data) + return app.data }, /// Use this if you want to get the client id and secret but are not interested /// in whether they are valid. @@ -85,7 +82,7 @@ export const useOAuthStore = defineStore('oauth', { clientSecret: this.clientSecret, instance, }) - this.setAppToken(res.access_token) + this.setAppToken(res.data.access_token) return res.access_token }, /// Use this if you want to ensure the app is still valid to use. @@ -93,9 +90,8 @@ export const useOAuthStore = defineStore('oauth', { async ensureAppToken() { if (this.appToken) { try { - await verifyAppToken({ - instance: useInstanceStore().server, - appToken: this.appToken, + await verifyCredentials({ + credentials: this.appToken, }) return this.appToken } catch (e) { From ecfea5c5e3e5633f29d916028c59b4f0c7bd5965 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 19:09:55 +0300 Subject: [PATCH 32/86] remove password reset --- src/api/public.js | 11 ++++++++++- src/services/new_api/password_reset.js | 22 ---------------------- 2 files changed, 10 insertions(+), 23 deletions(-) delete mode 100644 src/services/new_api/password_reset.js diff --git a/src/api/public.js b/src/api/public.js index 0acd95a18..f2fc4e071 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -14,9 +14,11 @@ import { import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' const SUGGESTIONS_URL = '/api/v1/suggestions' -/* eslint-env browser */ const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' const MASTODON_REGISTRATION_URL = '/api/v1/accounts' +const MASTODON_PASSWORD_RESET_URL = ({ email }) => + `/auth/password${paramsString({ email })}` + const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications' const MASTODON_FOLLOWING_URL = ( @@ -313,6 +315,13 @@ export const verifyCredentials = ({ credentials }) => credentials, }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) })) +export const resetPassword = ({ instance, email }) => { + return promisedRequest({ + url: MASTODON_PASSWORD_RESET_URL({ email }), + method: 'POST', + }) +} + export const suggestions = ({ credentials }) => promisedRequest({ url: SUGGESTIONS_URL, diff --git a/src/services/new_api/password_reset.js b/src/services/new_api/password_reset.js deleted file mode 100644 index 65342c04b..000000000 --- a/src/services/new_api/password_reset.js +++ /dev/null @@ -1,22 +0,0 @@ -import { reduce } from 'lodash' - -const MASTODON_PASSWORD_RESET_URL = '/auth/password' - -const resetPassword = ({ instance, email }) => { - const params = { email } - const query = reduce( - params, - (acc, v, k) => { - const encoded = `${k}=${encodeURIComponent(v)}` - return `${acc}&${encoded}` - }, - '', - ) - const url = `${instance}${MASTODON_PASSWORD_RESET_URL}?${query}` - - return window.fetch(url, { - method: 'POST', - }) -} - -export default resetPassword From d9d50de3f6549f0424526967cf0b1748519bc690 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 19:26:13 +0300 Subject: [PATCH 33/86] turns out announcements require login --- src/api/oauth.js | 1 + src/api/user.js | 20 +++++++++++++------- src/boot/after_store.js | 4 ---- src/modules/users.js | 6 +++++- src/stores/announcements.js | 9 ++------- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/api/oauth.js b/src/api/oauth.js index ce4c2f6fc..2c3c446e5 100644 --- a/src/api/oauth.js +++ b/src/api/oauth.js @@ -91,6 +91,7 @@ export const getClientToken = ({ clientId, clientSecret, instance }) => { formData, }) } + export const verifyOTPCode = ({ app, instance, mfaToken, code }) => { const formData = new window.FormData() diff --git a/src/api/user.js b/src/api/user.js index ebbd8b473..5adc53cda 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -81,6 +81,7 @@ const MASTODON_MUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/mute` const MASTODON_UNMUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/unmute` const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks' const MASTODON_LISTS_URL = '/api/v1/lists' +const MASTODON_ANNOUNCEMENTS_URL = '/api/v1/announcements' const MASTODON_ANNOUNCEMENTS_DISMISS_URL = (id) => `/api/v1/announcements/${id}/dismiss` const PLEROMA_EMOJI_REACT_URL = (id, emoji) => @@ -335,13 +336,6 @@ export const dismissNotification = ({ credentials, id }) => credentials, }) -export const dismissAnnouncement = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_ANNOUNCEMENTS_DISMISS_URL(id), - credentials, - method: 'POST', - }) - export const markNotificationsAsSeen = ({ id, credentials, @@ -363,6 +357,18 @@ export const markNotificationsAsSeen = ({ }) } +// #Announcements +export const getAnnouncements = ({ credentials }) => + promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials }) + +export const dismissAnnouncement = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_ANNOUNCEMENTS_DISMISS_URL(id), + credentials, + method: 'POST', + }) + + // #Imports export const importMutes = ({ file, credentials }) => { const formData = new FormData() diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 895fe910c..95fb3e9ea 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -566,10 +566,6 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { getInstanceConfig({ store }), ]).catch((e) => Promise.reject(e)) - // Start fetching things that don't need to block the UI - store.dispatch('fetchMutes') - store.dispatch('loadDrafts') - useAnnouncementsStore().startFetchingAnnouncements() getTOS({ store }) getStickers({ store }) diff --git a/src/modules/users.js b/src/modules/users.js index 5573928b4..d743324b0 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -28,6 +28,7 @@ import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' +import { useAnnouncementsStore } from 'src/stores/announcements.js' import { revokeToken } from 'src/api/oauth.js' import { @@ -832,8 +833,11 @@ const users = { startPolling() } - // Get user mutes + // Start fetching things that don't need to block the UI + useAnnouncementsStore().startFetchingAnnouncements() + dispatch('fetchMutes') + dispatch('loadDrafts') useInterfaceStore().setLayoutWidth(windowWidth()) useInterfaceStore().setLayoutHeight(windowHeight()) diff --git a/src/stores/announcements.js b/src/stores/announcements.js index 4668b286a..6339bba52 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -2,7 +2,7 @@ import { defineStore } from 'pinia' import { useOAuthStore } from 'src/stores/oauth.js' -import { getAnnouncements } from 'src/api/public.js' +import { getAnnouncements, dismissAnnouncement } from 'src/api/user.js' const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5 @@ -36,10 +36,6 @@ export const useAnnouncementsStore = defineStore('announcements', { currentUser.privileges.has('announcements_manage_announcements') try { - if (currentUser) { - this.userActions = await import('src/api/user.js') - } - if (isAdmin) { this.adminActions = await import('src/api/admin.js') } else { @@ -86,8 +82,7 @@ export const useAnnouncementsStore = defineStore('announcements', { } }, markAnnouncementAsRead(id) { - return this.userActions - .dismissAnnouncement({ + return dismissAnnouncement({ id, credentials: useOAuthStore().token, }) From 07a1d3b9c4752f4086557e2a7e8de245b1958cc8 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 19:41:37 +0300 Subject: [PATCH 34/86] fix login --- src/api/oauth.js | 34 ++++++++++++++++++++-------------- src/api/public.js | 4 ---- src/api/user.js | 1 - src/modules/users.js | 2 +- src/stores/announcements.js | 21 ++++++++++----------- src/stores/oauth.js | 7 +++---- 6 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/api/oauth.js b/src/api/oauth.js index 2c3c446e5..b547e9c9b 100644 --- a/src/api/oauth.js +++ b/src/api/oauth.js @@ -5,8 +5,9 @@ import { paramsString, promisedRequest } from './helpers.js' import { StatusCodeError } from 'src/services/errors/errors.js' const REDIRECT_URI = `${window.location.origin}/oauth-callback` +const MASTODON_APP_VERIFY_URL = '/api/v1/apps/verify_credentials' -export const createApp = ({ instance }) => { +export const createApp = () => { const formData = new window.FormData() formData.append('client_name', 'PleromaFE') @@ -15,7 +16,7 @@ export const createApp = ({ instance }) => { formData.append('scopes', 'read write follow push admin') return promisedRequest({ - url: `${instance}/api/v1/apps`, + url: '/api/v1/apps', method: 'POST', formData, }).then(({ data, ...rest }) => ({ @@ -28,6 +29,12 @@ export const createApp = ({ instance }) => { })) } +export const verifyAppToken = ({ credentials }) => + promisedRequest({ + url: MASTODON_APP_VERIFY_URL, + credentials, + }) + export const getLoginUrl = ({ instance, clientId }) => { const data = { responseType: 'code', @@ -42,7 +49,6 @@ export const getLoginUrl = ({ instance, clientId }) => { export const getTokenWithCredentials = ({ clientId, clientSecret, - instance, username, password, }) => { @@ -55,13 +61,13 @@ export const getTokenWithCredentials = ({ formData.append('password', password) return promisedRequest({ - url: `${instance}/oauth/token`, + url: '/oauth/token', method: 'POST', formData, }) } -export const getToken = ({ clientId, clientSecret, instance, code }) => { +export const getToken = ({ clientId, clientSecret, code }) => { const formData = new window.FormData() formData.append('client_id', clientId) @@ -71,13 +77,13 @@ export const getToken = ({ clientId, clientSecret, instance, code }) => { formData.append('redirect_uri', `${window.location.origin}/oauth-callback`) return promisedRequest({ - url: `${instance}/oauth/token`, + url: '/oauth/token', method: 'POST', formData, }) } -export const getClientToken = ({ clientId, clientSecret, instance }) => { +export const getClientToken = ({ clientId, clientSecret }) => { const formData = new window.FormData() formData.append('client_id', clientId) @@ -86,13 +92,13 @@ export const getClientToken = ({ clientId, clientSecret, instance }) => { formData.append('redirect_uri', `${window.location.origin}/oauth-callback`) return promisedRequest({ - url: `${instance}/oauth/token`, + url: '/oauth/token', method: 'POST', formData, }) } -export const verifyOTPCode = ({ app, instance, mfaToken, code }) => { +export const verifyOTPCode = ({ app, mfaToken, code }) => { const formData = new window.FormData() formData.append('client_id', app.client_id) @@ -102,13 +108,13 @@ export const verifyOTPCode = ({ app, instance, mfaToken, code }) => { formData.append('challenge_type', 'totp') return promisedRequest({ - url: `${instance}/oauth/mfa/challenge`, + url: '/oauth/mfa/challenge', method: 'POST', formData, }) } -export const verifyRecoveryCode = ({ app, instance, mfaToken, code }) => { +export const verifyRecoveryCode = ({ app, mfaToken, code }) => { const formData = new window.FormData() formData.append('client_id', app.client_id) @@ -118,13 +124,13 @@ export const verifyRecoveryCode = ({ app, instance, mfaToken, code }) => { formData.append('challenge_type', 'recovery') return promisedRequest({ - url: `${instance}/oauth/mfa/challenge`, + url: '/oauth/mfa/challenge', method: 'POST', formData, }) } -export const revokeToken = ({ app, instance, token }) => { +export const revokeToken = ({ app, token }) => { const formData = new window.FormData() formData.append('client_id', app.clientId) @@ -132,7 +138,7 @@ export const revokeToken = ({ app, instance, token }) => { formData.append('token', token) return promisedRequest({ - url: `${instance}/oauth/revoke`, + url: '/oauth/revoke', method: 'POST', formData, }) diff --git a/src/api/public.js b/src/api/public.js index f2fc4e071..a7e73e165 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -54,7 +54,6 @@ const MASTODON_SEARCH_2 = '/api/v2/search' const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' const MASTODON_STREAMING = '/api/v1/streaming' const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers' -const MASTODON_ANNOUNCEMENTS_URL = '/api/v1/announcements' const PLEROMA_EMOJI_REACTIONS_URL = (id) => `/api/v1/pleroma/statuses/${id}/reactions` const PLEROMA_SCROBBLES_URL = (id, { maxId, sinceId, minId, limit, offset }) => @@ -431,9 +430,6 @@ export const search2 = ({ export const fetchKnownDomains = ({ credentials }) => promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials }) -export const getAnnouncements = ({ credentials }) => - promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials }) - export const getMastodonSocketURI = ( { credentials, stream, args = {} }, base, diff --git a/src/api/user.js b/src/api/user.js index 5adc53cda..d4811ca4d 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -368,7 +368,6 @@ export const dismissAnnouncement = ({ id, credentials }) => method: 'POST', }) - // #Imports export const importMutes = ({ file, credentials }) => { const formData = new FormData() diff --git a/src/modules/users.js b/src/modules/users.js index d743324b0..d4efec2be 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -18,6 +18,7 @@ import { windowWidth, } from '../services/window_utils/window_utils' +import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' import { useEmojiStore } from 'src/stores/emoji.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -28,7 +29,6 @@ import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' -import { useAnnouncementsStore } from 'src/stores/announcements.js' import { revokeToken } from 'src/api/oauth.js' import { diff --git a/src/stores/announcements.js b/src/stores/announcements.js index 6339bba52..a5f3e4d8e 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -2,7 +2,7 @@ import { defineStore } from 'pinia' import { useOAuthStore } from 'src/stores/oauth.js' -import { getAnnouncements, dismissAnnouncement } from 'src/api/user.js' +import { dismissAnnouncement, getAnnouncements } from 'src/api/user.js' const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5 @@ -83,18 +83,17 @@ export const useAnnouncementsStore = defineStore('announcements', { }, markAnnouncementAsRead(id) { return dismissAnnouncement({ - id, - credentials: useOAuthStore().token, - }) - .then(() => { - const index = this.announcements.findIndex((a) => a.id === id) + id, + credentials: useOAuthStore().token, + }).then(() => { + const index = this.announcements.findIndex((a) => a.id === id) - if (index < 0) { - return - } + if (index < 0) { + return + } - this.announcements[index].read = true - }) + this.announcements[index].read = true + }) }, startFetchingAnnouncements() { if (this.fetchAnnouncementsTimer) { diff --git a/src/stores/oauth.js b/src/stores/oauth.js index 55e8e759c..83a7d246a 100644 --- a/src/stores/oauth.js +++ b/src/stores/oauth.js @@ -2,8 +2,7 @@ import { defineStore } from 'pinia' import { useInstanceStore } from 'src/stores/instance.js' -import { createApp, getClientToken } from 'src/api/oauth.js' -import { verifyCredentials } from 'src/api/public.js' +import { createApp, getClientToken, verifyAppToken } from 'src/api/oauth.js' // status codes about verifyAppToken (GET /api/v1/apps/verify_credentials) const isAppTokenRejected = (error) => @@ -39,7 +38,7 @@ export const useOAuthStore = defineStore('oauth', { }), getters: { token() { - return this.userToken || this.appToken + return this.userToken }, }, actions: { @@ -90,7 +89,7 @@ export const useOAuthStore = defineStore('oauth', { async ensureAppToken() { if (this.appToken) { try { - await verifyCredentials({ + await verifyAppToken({ credentials: this.appToken, }) return this.appToken From bf37842b5fc214ae92917e6767f73e2f1245c09e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 19:45:26 +0300 Subject: [PATCH 35/86] fix password reset component --- src/api/public.js | 2 +- src/components/password_reset/password_reset.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index a7e73e165..570e84dff 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -314,7 +314,7 @@ export const verifyCredentials = ({ credentials }) => credentials, }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) })) -export const resetPassword = ({ instance, email }) => { +export const resetPassword = ({ email }) => { return promisedRequest({ url: MASTODON_PASSWORD_RESET_URL({ email }), method: 'POST', diff --git a/src/components/password_reset/password_reset.js b/src/components/password_reset/password_reset.js index 5a54d846b..2f6caafb3 100644 --- a/src/components/password_reset/password_reset.js +++ b/src/components/password_reset/password_reset.js @@ -1,7 +1,7 @@ import { mapState as mapPiniaState } from 'pinia' import { mapState } from 'vuex' -import passwordResetApi from '../../services/new_api/password_reset.js' +import { resetPassword } from 'src/api/public.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -46,7 +46,7 @@ const passwordReset = { const email = this.user.email const server = this.server - passwordResetApi({ server, email }) + resetPassword({ email }) .then(({ status }) => { this.isPending = false this.user.email = '' From 20ff0c00910a2cb67547880bff9dd1fec3b51636 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 19:46:29 +0300 Subject: [PATCH 36/86] add proxy to /auth for dev server --- vite.config.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vite.config.js b/vite.config.js index 5b7c22d57..f8d3288c8 100644 --- a/vite.config.js +++ b/vite.config.js @@ -80,6 +80,12 @@ export default defineConfig(async ({ mode, command }) => { cookieDomainRewrite: 'localhost', ws: true, }, + '/auth': { // Mastodon password reset lives here + target, + changeOrigin: true, + cookieDomainRewrite: 'localhost', + ws: true, + }, '/nodeinfo': { target, changeOrigin: true, From 4643669702c6a3f2706a7328dfe966a98c285094 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 20:11:23 +0300 Subject: [PATCH 37/86] lint --- src/components/password_reset/password_reset.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/password_reset/password_reset.js b/src/components/password_reset/password_reset.js index 2f6caafb3..b2e6e1093 100644 --- a/src/components/password_reset/password_reset.js +++ b/src/components/password_reset/password_reset.js @@ -1,10 +1,10 @@ import { mapState as mapPiniaState } from 'pinia' import { mapState } from 'vuex' -import { resetPassword } from 'src/api/public.js' - import { useInstanceStore } from 'src/stores/instance.js' +import { resetPassword } from 'src/api/public.js' + import { library } from '@fortawesome/fontawesome-svg-core' import { faTimes } from '@fortawesome/free-solid-svg-icons' @@ -24,7 +24,7 @@ const passwordReset = { ...mapState({ signedIn: (state) => !!state.users.currentUser, }), - ...mapPiniaState(useInstanceStore, ['server', 'mailerEnabled']), + ...mapPiniaState(useInstanceStore, ['mailerEnabled']), }, created() { if (this.signedIn) { @@ -44,7 +44,6 @@ const passwordReset = { submit() { this.isPending = true const email = this.user.email - const server = this.server resetPassword({ email }) .then(({ status }) => { From 0c3646b456e849d70eb1b423a8cae97050e6ef38 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 20:11:32 +0300 Subject: [PATCH 38/86] lint --- vite.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vite.config.js b/vite.config.js index f8d3288c8..3abc24292 100644 --- a/vite.config.js +++ b/vite.config.js @@ -80,7 +80,8 @@ export default defineConfig(async ({ mode, command }) => { cookieDomainRewrite: 'localhost', ws: true, }, - '/auth': { // Mastodon password reset lives here + '/auth': { + // Mastodon password reset lives here target, changeOrigin: true, cookieDomainRewrite: 'localhost', From 945b618d2c42f7938f3516f7e11f262ab362a4cd Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 20:12:43 +0300 Subject: [PATCH 39/86] lack of changelog --- changelog.d/api-refactor.skip | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/api-refactor.skip diff --git a/changelog.d/api-refactor.skip b/changelog.d/api-refactor.skip new file mode 100644 index 000000000..e69de29bb From 6066e80718f6f4ef0102f7b2c7391350af46bdf2 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 20:13:48 +0300 Subject: [PATCH 40/86] remove .only --- test/unit/specs/services/api/helpers.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/specs/services/api/helpers.spec.js b/test/unit/specs/services/api/helpers.spec.js index b1219bd46..11874790a 100644 --- a/test/unit/specs/services/api/helpers.spec.js +++ b/test/unit/specs/services/api/helpers.spec.js @@ -1,7 +1,7 @@ import { paramsString } from 'src/api/helpers.js' describe('API Helpers', () => { - describe.only('paramsString', () => { + describe('paramsString', () => { it('should return empty string when given empty object', () => { const string = paramsString({}) From f535a14dfcef50b4e430534359d70e37d815d80f Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 17 Jun 2026 21:05:54 +0300 Subject: [PATCH 41/86] fix some tests --- src/api/helpers.js | 12 ------------ src/stores/oauth.js | 2 +- test/fixtures/mock_api.js | 10 +++++----- test/unit/specs/stores/oauth.spec.js | 15 ++++++--------- 4 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/api/helpers.js b/src/api/helpers.js index 842efe9aa..687ed9628 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -69,7 +69,6 @@ export const paramsString = (params = {}) => { export const promisedRequest = async ({ method, url, - params, payload, formData, credentials, @@ -88,17 +87,6 @@ export const promisedRequest = async ({ options.headers['Content-Type'] = 'application/json' } - if (params) { - url += - '?' + - Object.entries(params) - .map( - ([key, value]) => - encodeURIComponent(key) + '=' + encodeURIComponent(value), - ) - .join('&') - } - if (formData || payload) { options.body = formData || JSON.stringify(payload) } diff --git a/src/stores/oauth.js b/src/stores/oauth.js index 83a7d246a..35cd67afb 100644 --- a/src/stores/oauth.js +++ b/src/stores/oauth.js @@ -82,7 +82,7 @@ export const useOAuthStore = defineStore('oauth', { instance, }) this.setAppToken(res.data.access_token) - return res.access_token + return res.data.access_token }, /// Use this if you want to ensure the app is still valid to use. /// @return {string} The access token to the app (not attached to any user) diff --git a/test/fixtures/mock_api.js b/test/fixtures/mock_api.js index 03fb01a64..68390d6a2 100644 --- a/test/fixtures/mock_api.js +++ b/test/fixtures/mock_api.js @@ -2,6 +2,8 @@ import { HttpResponse, http } from 'msw' import { setupWorker } from 'msw/browser' import { test as testBase } from 'vitest' +export const testServer = '' + // https://mswjs.io/docs/recipes/vitest-browser-mode export const injectMswToTest = (defaultHandlers) => { const worker = setupWorker(...defaultHandlers) @@ -24,16 +26,14 @@ export const injectMswToTest = (defaultHandlers) => { }) } -export const testServer = 'https://test.server.example' - export const authApis = [ - http.post(`${testServer}/api/v1/apps`, () => { + http.post('/api/v1/apps', () => { return HttpResponse.json({ client_id: 'test-id', client_secret: 'test-secret', }) }), - http.get(`${testServer}/api/v1/apps/verify_credentials`, ({ request }) => { + http.get('/api/v1/apps/verify_credentials', ({ request }) => { const authHeader = request.headers.get('Authorization') if ( authHeader === 'Bearer test-app-token' || @@ -50,7 +50,7 @@ export const authApis = [ ) } }), - http.post(`${testServer}/oauth/token`, async ({ request }) => { + http.post('/oauth/token', async ({ request }) => { const data = await request.formData() if ( diff --git a/test/unit/specs/stores/oauth.spec.js b/test/unit/specs/stores/oauth.spec.js index 4664bba02..3ae8da7c9 100644 --- a/test/unit/specs/stores/oauth.spec.js +++ b/test/unit/specs/stores/oauth.spec.js @@ -5,10 +5,8 @@ import { createPinia, setActivePinia } from 'pinia' import { authApis, injectMswToTest, - testServer, } from '/test/fixtures/mock_api.js' -import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' const test = injectMswToTest(authApis) @@ -16,7 +14,6 @@ const test = injectMswToTest(authApis) describe('oauth store', () => { beforeEach(() => { setActivePinia(createTestingPinia({ stubActions: false })) - useInstanceStore().server = testServer }) describe('createApp', () => { @@ -31,7 +28,7 @@ describe('oauth store', () => { test('it should throw and not update if failed', async ({ worker }) => { worker.use( - http.post(`${testServer}/api/v1/apps`, () => { + http.post('/api/v1/apps', () => { return HttpResponse.text('Throttled', { status: 429 }) }), ) @@ -56,7 +53,7 @@ describe('oauth store', () => { test('it should not create an app if it exists', async ({ worker }) => { worker.use( - http.post(`${testServer}/api/v1/apps`, () => { + http.post('/api/v1/apps', () => { return HttpResponse.text('Should not call this API', { status: 400 }) }), ) @@ -115,7 +112,7 @@ describe('oauth store', () => { worker, }) => { worker.use( - http.post(`${testServer}/api/v1/apps`, () => { + http.post('/api/v1/apps', () => { return HttpResponse.text('Should not call this API', { status: 400 }) }), ) @@ -133,7 +130,7 @@ describe('oauth store', () => { worker, }) => { worker.use( - http.post(`${testServer}/api/v1/apps`, () => { + http.post('/api/v1/apps', () => { return HttpResponse.text('Should not call this API', { status: 400 }) }), ) @@ -173,7 +170,7 @@ describe('oauth store', () => { test('it should throw if we cannot create an app', async ({ worker }) => { worker.use( - http.post(`${testServer}/api/v1/apps`, () => { + http.post('/api/v1/apps', () => { return HttpResponse.text('Throttled', { status: 429 }) }), ) @@ -186,7 +183,7 @@ describe('oauth store', () => { worker, }) => { worker.use( - http.post(`${testServer}/oauth/token`, () => { + http.post('/oauth/token', () => { return HttpResponse.text('Throttled', { status: 429 }) }), ) From 887ed66ea773c467d3b3ccfbf9d35f4a393d6f5d Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 18 Jun 2026 13:52:06 +0300 Subject: [PATCH 42/86] lint --- test/unit/specs/stores/oauth.spec.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/unit/specs/stores/oauth.spec.js b/test/unit/specs/stores/oauth.spec.js index 3ae8da7c9..1081276d3 100644 --- a/test/unit/specs/stores/oauth.spec.js +++ b/test/unit/specs/stores/oauth.spec.js @@ -2,10 +2,7 @@ import { createTestingPinia } from '@pinia/testing' import { HttpResponse, http } from 'msw' import { createPinia, setActivePinia } from 'pinia' -import { - authApis, - injectMswToTest, -} from '/test/fixtures/mock_api.js' +import { authApis, injectMswToTest } from '/test/fixtures/mock_api.js' import { useOAuthStore } from 'src/stores/oauth.js' From 38898ba571570799362f9b90a8973dac76d330cb Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 18 Jun 2026 13:57:16 +0300 Subject: [PATCH 43/86] fix test? --- test/unit/specs/stores/lists.spec.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/unit/specs/stores/lists.spec.js b/test/unit/specs/stores/lists.spec.js index 083730856..dd900dabd 100644 --- a/test/unit/specs/stores/lists.spec.js +++ b/test/unit/specs/stores/lists.spec.js @@ -3,15 +3,8 @@ import { createStore } from 'vuex' import { useListsStore } from 'src/stores/lists.js' -import apiModule from 'src/modules/api.js' - setActivePinia(createPinia()) const store = useListsStore() -window.vuex = createStore({ - modules: { - api: apiModule, - }, -}) describe('The lists store', () => { describe('actions', () => { From f3c77afff15256431892df3e6d6aec2be74f0c4d Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 18 Jun 2026 14:17:13 +0300 Subject: [PATCH 44/86] fix? --- test/unit/specs/stores/lists.spec.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/test/unit/specs/stores/lists.spec.js b/test/unit/specs/stores/lists.spec.js index dd900dabd..cf8866541 100644 --- a/test/unit/specs/stores/lists.spec.js +++ b/test/unit/specs/stores/lists.spec.js @@ -1,10 +1,22 @@ -import { createPinia, setActivePinia } from 'pinia' -import { createStore } from 'vuex' +import { createTestingPinia } from '@pinia/testing' +import { HttpResponse, http } from 'msw' +import { setActivePinia } from 'pinia' + +import { injectMswToTest } from '/test/fixtures/mock_api.js' import { useListsStore } from 'src/stores/lists.js' -setActivePinia(createPinia()) +setActivePinia(createTestingPinia({ stubActions: false })) const store = useListsStore() +const it = injectMswToTest([ + http.get('/api/v1/lists/:id', () => HttpResponse.json({ ok: true })), + http.put('/api/v1/lists/:id', () => HttpResponse.json({ ok: true })), + http.post('/api/v1/lists/:id', () => HttpResponse.json({ ok: true })), + http.delete('/api/v1/lists/:id', () => HttpResponse.json({ ok: true })), + http.get('/api/v1/lists/:id/accounts', () => HttpResponse.json({ ok: true })), + http.post('/api/v1/lists/:id/accounts', () => HttpResponse.json({ ok: true })), + http.delete('/api/v1/lists/:id/accounts', () => HttpResponse.json({ ok: true })), +]) describe('The lists store', () => { describe('actions', () => { From 9d24782cd82f2d4e29543c1c6e26a4e4f56da8c0 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 18 Jun 2026 19:58:50 +0300 Subject: [PATCH 45/86] fix tests. msw has issues on firefox with vitest isolation. --- package.json | 9 +- src/api/oauth.js | 21 +- src/api/user.js | 10 +- src/stores/lists.js | 56 +- src/stores/sync_config.js | 28 +- test/fixtures/mock_api.js | 82 +-- test/fixtures/worker.js | 8 + test/unit/specs/stores/lists.spec.js | 132 ++-- test/unit/specs/stores/oauth.spec.js | 121 +++- vite.config.js | 7 +- yarn.lock | 981 +++++++++------------------ 11 files changed, 592 insertions(+), 863 deletions(-) create mode 100644 test/fixtures/worker.js diff --git a/package.json b/package.json index c51d98b81..cdeefc30c 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,9 @@ "@vitejs/devtools": "^0.3.1", "@vitejs/plugin-vue": "^6.0.7", "@vitejs/plugin-vue-jsx": "^5.1.5", - "@vitest/browser": "^3.0.7", - "@vitest/ui": "^3.0.7", + "@vitest/browser-playwright": "^4.1.7", + "@vitest/browser": "^4.1.7", + "@vitest/ui": "^4.1.7", "@vue/babel-helper-vue-jsx-merge-props": "1.4.0", "@vue/babel-plugin-jsx": "1.5.0", "@vue/compiler-sfc": "3.5.22", @@ -95,7 +96,7 @@ "http-proxy-middleware": "3.0.5", "iso-639-1": "3.1.5", "lodash": "4.17.21", - "msw": "2.10.5", + "msw": "2.14.6", "nightwatch": "3.12.2", "oxc": "^1.0.1", "playwright": "1.57.0", @@ -118,7 +119,7 @@ "vite": "^8.0.0", "vite-plugin-eslint2": "^5.1.0", "vite-plugin-stylelint": "^6.1.0", - "vitest": "^3.0.7", + "vitest": "^4.1.7", "vue-eslint-parser": "10.2.0" }, "type": "module", diff --git a/src/api/oauth.js b/src/api/oauth.js index b547e9c9b..7ecb0cc3f 100644 --- a/src/api/oauth.js +++ b/src/api/oauth.js @@ -5,7 +5,12 @@ import { paramsString, promisedRequest } from './helpers.js' import { StatusCodeError } from 'src/services/errors/errors.js' const REDIRECT_URI = `${window.location.origin}/oauth-callback` -const MASTODON_APP_VERIFY_URL = '/api/v1/apps/verify_credentials' + +export const MASTODON_APP_VERIFY_URL = '/api/v1/apps/verify_credentials' +export const MASTODON_APP_URL = '/api/v1/apps' +export const OAUTH_TOKEN_URL = '/oauth/token' +export const OAUTH_MFA_CHALLENGE_URL = '/oauth/mfa/challenge' +export const OAUTH_REVOKE_URL = '/oauth/revoke' export const createApp = () => { const formData = new window.FormData() @@ -16,8 +21,8 @@ export const createApp = () => { formData.append('scopes', 'read write follow push admin') return promisedRequest({ - url: '/api/v1/apps', method: 'POST', + url: MASTODON_APP_URL, formData, }).then(({ data, ...rest }) => ({ ...rest, @@ -61,7 +66,7 @@ export const getTokenWithCredentials = ({ formData.append('password', password) return promisedRequest({ - url: '/oauth/token', + url: OAUTH_TOKEN_URL, method: 'POST', formData, }) @@ -77,7 +82,7 @@ export const getToken = ({ clientId, clientSecret, code }) => { formData.append('redirect_uri', `${window.location.origin}/oauth-callback`) return promisedRequest({ - url: '/oauth/token', + url: OAUTH_TOKEN_URL, method: 'POST', formData, }) @@ -92,7 +97,7 @@ export const getClientToken = ({ clientId, clientSecret }) => { formData.append('redirect_uri', `${window.location.origin}/oauth-callback`) return promisedRequest({ - url: '/oauth/token', + url: OAUTH_TOKEN_URL, method: 'POST', formData, }) @@ -108,7 +113,7 @@ export const verifyOTPCode = ({ app, mfaToken, code }) => { formData.append('challenge_type', 'totp') return promisedRequest({ - url: '/oauth/mfa/challenge', + url: OAUTH_MFA_CHALLENGE_URL, method: 'POST', formData, }) @@ -124,7 +129,7 @@ export const verifyRecoveryCode = ({ app, mfaToken, code }) => { formData.append('challenge_type', 'recovery') return promisedRequest({ - url: '/oauth/mfa/challenge', + url: OAUTH_MFA_CHALLENGE_URL, method: 'POST', formData, }) @@ -138,7 +143,7 @@ export const revokeToken = ({ app, token }) => { formData.append('token', token) return promisedRequest({ - url: '/oauth/revoke', + url: OAUTH_REVOKE_URL, method: 'POST', formData, }) diff --git a/src/api/user.js b/src/api/user.js index d4811ca4d..f7228d84f 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -44,8 +44,8 @@ const MASTODON_DENY_USER_URL = (id) => `/api/v1/follow_requests/${id}/reject` const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) => `/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}` const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists` -const MASTODON_LIST_URL = (id) => `/api/v1/lists/${id}` -const MASTODON_LIST_ACCOUNTS_URL = (id) => `/api/v1/lists/${id}/accounts` +export const MASTODON_LIST_URL = (id) => `/api/v1/lists/${id}` +export const MASTODON_LIST_ACCOUNTS_URL = (id) => `/api/v1/lists/${id}/accounts` const MASTODON_USER_BLOCKS_URL = ({ maxId, sinceId, @@ -80,7 +80,6 @@ const MASTODON_UNPIN_OWN_STATUS = (id) => `/api/v1/statuses/${id}/unpin` const MASTODON_MUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/mute` const MASTODON_UNMUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/unmute` const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks' -const MASTODON_LISTS_URL = '/api/v1/lists' const MASTODON_ANNOUNCEMENTS_URL = '/api/v1/announcements' const MASTODON_ANNOUNCEMENTS_DISMISS_URL = (id) => `/api/v1/announcements/${id}/dismiss` @@ -824,13 +823,13 @@ export const revokeOAuthToken = ({ id, credentials }) => // #Lists export const fetchLists = ({ credentials }) => promisedRequest({ - url: MASTODON_LISTS_URL, + url: MASTODON_LIST_URL(), credentials, }) export const createList = ({ title, credentials }) => promisedRequest({ - url: MASTODON_LISTS_URL, + url: MASTODON_LIST_URL(), credentials, method: 'POST', payload: { title }, @@ -843,6 +842,7 @@ export const getList = ({ listId, credentials }) => }) export const updateList = ({ listId, title, credentials }) => + console.log('PUT', MASTODON_LIST_URL(listId)) || promisedRequest({ url: MASTODON_LIST_URL(listId), diff --git a/src/stores/lists.js b/src/stores/lists.js index b612b403a..6bafbde7a 100644 --- a/src/stores/lists.js +++ b/src/stores/lists.js @@ -50,8 +50,8 @@ export const useListsStore = defineStore('lists', { setLists(value) { this.allLists = value }, - createList({ title }) { - return createList({ + async createList({ title }) { + return await createList({ title, credentials: useOAuthStore().token, }).then((list) => { @@ -59,14 +59,14 @@ export const useListsStore = defineStore('lists', { return list }) }, - fetchList({ listId }) { - return getList({ + async fetchList({ listId }) { + return await getList({ listId, credentials: useOAuthStore().token, }).then((list) => this.setList({ listId: list.id, title: list.title })) }, - fetchListAccounts({ listId }) { - return getListAccounts({ + async fetchListAccounts({ listId }) { + return await getListAccounts({ listId, credentials: useOAuthStore().token, }).then((accountIds) => { @@ -76,8 +76,8 @@ export const useListsStore = defineStore('lists', { this.allListsObject[listId].accountIds = accountIds }) }, - setList({ listId, title }) { - updateList({ + async setList({ listId, title }) { + await updateList({ listId, title, credentials: useOAuthStore().token, @@ -95,7 +95,7 @@ export const useListsStore = defineStore('lists', { entry.title = title } }, - setListAccounts({ listId, accountIds }) { + async setListAccounts({ listId, accountIds }) { const saved = this.allListsObject[listId]?.accountIds || [] const added = accountIds.filter((id) => !saved.includes(id)) const removed = saved.filter((id) => !accountIds.includes(id)) @@ -103,23 +103,29 @@ export const useListsStore = defineStore('lists', { this.allListsObject[listId] = { accountIds: [] } } this.allListsObject[listId].accountIds = accountIds + const promises = [] if (added.length > 0) { - addAccountsToList({ - listId, - accountIds: added, - credentials: useOAuthStore().token, - }) + promises.push( + addAccountsToList({ + listId, + accountIds: added, + credentials: useOAuthStore().token, + }), + ) } if (removed.length > 0) { - removeAccountsFromList({ - listId, - accountIds: removed, - credentials: useOAuthStore().token, - }) + promises.push( + removeAccountsFromList({ + listId, + accountIds: removed, + credentials: useOAuthStore().token, + }), + ) } + await Promise.all(promises) }, - addListAccount({ listId, accountId }) { - return addAccountsToList({ + async addListAccount({ listId, accountId }) { + return await addAccountsToList({ listId, accountIds: [accountId], credentials: useOAuthStore().token, @@ -131,8 +137,8 @@ export const useListsStore = defineStore('lists', { return result }) }, - removeListAccount({ listId, accountId }) { - return removeAccountsFromList({ + async removeListAccount({ listId, accountId }) { + return await removeAccountsFromList({ listId, accountIds: [accountId], credentials: useOAuthStore().token, @@ -148,8 +154,8 @@ export const useListsStore = defineStore('lists', { return result }) }, - deleteList({ listId }) { - deleteList({ + async deleteList({ listId }) { + await deleteList({ listId, credentials: useOAuthStore().token, }) diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js index f022ffe86..f42d068d7 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -233,9 +233,18 @@ export const _mergeJournal = (...journals) => { Object.hasOwn(entry, 'timestamp'), ) const grouped = groupBy(allJournals, 'path') - const trimmedGrouped = Object.entries(grouped).map(([path, journal]) => { - // side effect - journal.sort((a, b) => (a.timestamp > b.timestamp ? 1 : -1)) + const trimmedGrouped = Object.entries(grouped).map(([path, rawJournal]) => { + const journal = rawJournal + .map((data, index) => ({ data, index })) + .toSorted(({ data: a, index: ai }, { data: b, index: bi }) => { + if (a.timestamp === b.timestamp) { + return ai - bi + } else { + return a.timestamp > b.timestamp ? 1 : -1 + } + }) + .map((x) => x.data) + console.log(journal) if (path.startsWith('collections')) { const lastRemoveIndex = findLastIndex( @@ -270,9 +279,16 @@ export const _mergeJournal = (...journals) => { } }) - const flat = flatten(trimmedGrouped).sort((a, b) => - a.timestamp > b.timestamp ? 1 : -1, - ) + const flat = flatten(trimmedGrouped) + .map((data, index) => ({ data, index })) + .toSorted(({ data: a, index: ai }, { data: b, index: bi }) => { + if (a.timestamp === b.timestamp) { + return ai - bi + } else { + return a.timestamp > b.timestamp ? 1 : -1 + } + }) + .map((x) => x.data) return take(flat, 500) } diff --git a/test/fixtures/mock_api.js b/test/fixtures/mock_api.js index 68390d6a2..6fabe6356 100644 --- a/test/fixtures/mock_api.js +++ b/test/fixtures/mock_api.js @@ -1,73 +1,19 @@ -import { HttpResponse, http } from 'msw' -import { setupWorker } from 'msw/browser' import { test as testBase } from 'vitest' -export const testServer = '' +import { worker } from './worker.js' -// https://mswjs.io/docs/recipes/vitest-browser-mode -export const injectMswToTest = (defaultHandlers) => { - const worker = setupWorker(...defaultHandlers) +export const test = testBase.extend({ + worker: [ + // biome-ignore lint: required by vitest + async ({}, use) => { + await worker.start() - return testBase.extend({ - worker: [ - // biome-ignore lint: required by vitest - async ({}, use) => { - await worker.start() + await use(worker) - await use(worker) - - worker.resetHandlers() - worker.stop() - }, - { - auto: true, - }, - ], - }) -} - -export const authApis = [ - http.post('/api/v1/apps', () => { - return HttpResponse.json({ - client_id: 'test-id', - client_secret: 'test-secret', - }) - }), - http.get('/api/v1/apps/verify_credentials', ({ request }) => { - const authHeader = request.headers.get('Authorization') - if ( - authHeader === 'Bearer test-app-token' || - authHeader === 'Bearer also-good-app-token' - ) { - return HttpResponse.json({}) - } else { - // Pleroma 2.9.0 gives the following respoonse upon error - return HttpResponse.json( - { error: { detail: 'Internal server error' } }, - { - status: 400, - }, - ) - } - }), - http.post('/oauth/token', async ({ request }) => { - const data = await request.formData() - - if ( - data.get('client_id') === 'test-id' && - data.get('client_secret') === 'test-secret' && - data.get('grant_type') === 'client_credentials' && - data.has('redirect_uri') - ) { - return HttpResponse.json({ access_token: 'test-app-token' }) - } else { - // Pleroma 2.9.0 gives the following respoonse upon error - return HttpResponse.json( - { error: 'Invalid credentials' }, - { - status: 400, - }, - ) - } - }), -] + worker.resetHandlers() + }, + { + auto: true, + }, + ], +}) diff --git a/test/fixtures/worker.js b/test/fixtures/worker.js new file mode 100644 index 000000000..e0db13ffc --- /dev/null +++ b/test/fixtures/worker.js @@ -0,0 +1,8 @@ +import { setupWorker } from 'msw/browser' + +export const worker = setupWorker() + +console.log('=============== TEST ===============') +console.log(window.__test__) +console.log('=============== TEST ===============') +window.__test__ = window.__test__ || 'TEST' diff --git a/test/unit/specs/stores/lists.spec.js b/test/unit/specs/stores/lists.spec.js index cf8866541..9c0092522 100644 --- a/test/unit/specs/stores/lists.spec.js +++ b/test/unit/specs/stores/lists.spec.js @@ -2,26 +2,22 @@ import { createTestingPinia } from '@pinia/testing' import { HttpResponse, http } from 'msw' import { setActivePinia } from 'pinia' -import { injectMswToTest } from '/test/fixtures/mock_api.js' +import { test as mockedIt } from '/test/fixtures/mock_api.js' import { useListsStore } from 'src/stores/lists.js' -setActivePinia(createTestingPinia({ stubActions: false })) -const store = useListsStore() -const it = injectMswToTest([ - http.get('/api/v1/lists/:id', () => HttpResponse.json({ ok: true })), - http.put('/api/v1/lists/:id', () => HttpResponse.json({ ok: true })), - http.post('/api/v1/lists/:id', () => HttpResponse.json({ ok: true })), - http.delete('/api/v1/lists/:id', () => HttpResponse.json({ ok: true })), - http.get('/api/v1/lists/:id/accounts', () => HttpResponse.json({ ok: true })), - http.post('/api/v1/lists/:id/accounts', () => HttpResponse.json({ ok: true })), - http.delete('/api/v1/lists/:id/accounts', () => HttpResponse.json({ ok: true })), -]) +import { MASTODON_LIST_ACCOUNTS_URL, MASTODON_LIST_URL } from 'src/api/user.js' describe('The lists store', () => { + let store + + beforeEach(() => { + createTestingPinia({ stubActions: false }) + store = useListsStore() + }) + describe('actions', () => { - it('updates array of all lists', () => { - store.$reset() + mockedIt('updates array of all lists', () => { const list = { id: '1', title: 'testList' } store.setLists([list]) @@ -29,46 +25,74 @@ describe('The lists store', () => { expect(store.allLists).to.eql([list]) }) - it('adds a new list with a title, updating the title for existing lists', () => { - store.$reset() - const list = { id: '1', title: 'testList' } - const modList = { id: '1', title: 'anotherTestTitle' } + mockedIt( + 'adds a new list with a title, updating the title for existing lists', + async ({ worker }) => { + const list = { id: '1', title: 'testList' } + const modList = { id: '1', title: 'anotherTestTitle' } - store.setList({ listId: list.id, title: list.title }) - expect(store.allListsObject[list.id]).to.eql({ - title: list.title, - accountIds: [], - }) - expect(store.allLists).to.have.length(1) - expect(store.allLists[0]).to.eql(list) + worker.use( + http.put(MASTODON_LIST_URL(':id'), () => + HttpResponse.json({ ok: true }), + ), + ) + console.log('1 =========', worker.listHandlers()) - store.setList({ listId: modList.id, title: modList.title }) - expect(store.allListsObject[modList.id]).to.eql({ - title: modList.title, - accountIds: [], - }) - expect(store.allLists).to.have.length(1) - expect(store.allLists[0]).to.eql(modList) - }) + await store.setList({ listId: list.id, title: list.title }) + expect(store.allListsObject[list.id]).to.eql({ + title: list.title, + accountIds: [], + }) + expect(store.allLists).to.have.length(1) + expect(store.allLists[0]).to.eql(list) - it('adds a new list with an array of IDs, updating the IDs for existing lists', () => { - store.$reset() - const list = { id: '1', accountIds: ['1', '2', '3'] } - const modList = { id: '1', accountIds: ['3', '4', '5'] } + console.log('2 =========', worker.listHandlers()) - store.setListAccounts({ listId: list.id, accountIds: list.accountIds }) - expect(store.allListsObject[list.id].accountIds).to.eql(list.accountIds) + await store.setList({ listId: modList.id, title: modList.title }) + expect(store.allListsObject[modList.id]).to.eql({ + title: modList.title, + accountIds: [], + }) + expect(store.allLists).to.have.length(1) + expect(store.allLists[0]).to.eql(modList) - store.setListAccounts({ - listId: modList.id, - accountIds: modList.accountIds, - }) - expect(store.allListsObject[modList.id].accountIds).to.eql( - modList.accountIds, - ) - }) + console.log('3 =========', worker.listHandlers()) + }, + ) - it('deletes a list', () => { + mockedIt( + 'adds a new list with an array of IDs, updating the IDs for existing lists', + async ({ worker }) => { + const list = { id: '1', accountIds: ['1', '2', '3'] } + const modList = { id: '1', accountIds: ['3', '4', '5'] } + + worker.use( + http.post(MASTODON_LIST_ACCOUNTS_URL(':id'), () => + HttpResponse.json({ ok: true }), + ), + http.delete(MASTODON_LIST_ACCOUNTS_URL(':id'), () => + HttpResponse.json({ ok: true }), + ), + ) + + await store.setListAccounts({ + listId: list.id, + accountIds: list.accountIds, + }) + expect(store.allListsObject[list.id].accountIds).to.eql(list.accountIds) + + await store.setListAccounts({ + listId: modList.id, + accountIds: modList.accountIds, + }) + + expect(store.allListsObject[modList.id].accountIds).to.eql( + modList.accountIds, + ) + }, + ) + + mockedIt('deletes a list', async ({ worker }) => { store.$patch({ allLists: [{ id: '1', title: 'testList' }], allListsObject: { @@ -77,14 +101,20 @@ describe('The lists store', () => { }) const listId = '1' - store.deleteList({ listId }) + worker.use( + http.delete(MASTODON_LIST_URL(':id'), () => + HttpResponse.json({ ok: true }), + ), + ) + + await store.deleteList({ listId }) expect(store.allLists).to.have.length(0) expect(store.allListsObject).to.eql({}) }) }) describe('getters', () => { - it('returns list title', () => { + mockedIt('returns list title', () => { store.$patch({ allLists: [{ id: '1', title: 'testList' }], allListsObject: { @@ -96,7 +126,7 @@ describe('The lists store', () => { expect(store.findListTitle(id)).to.eql('testList') }) - it('returns list accounts', () => { + mockedIt('returns list accounts', () => { store.$patch({ allLists: [{ id: '1', title: 'testList' }], allListsObject: { diff --git a/test/unit/specs/stores/oauth.spec.js b/test/unit/specs/stores/oauth.spec.js index 1081276d3..c4f4ba978 100644 --- a/test/unit/specs/stores/oauth.spec.js +++ b/test/unit/specs/stores/oauth.spec.js @@ -1,12 +1,64 @@ import { createTestingPinia } from '@pinia/testing' import { HttpResponse, http } from 'msw' -import { createPinia, setActivePinia } from 'pinia' +import { setActivePinia } from 'pinia' -import { authApis, injectMswToTest } from '/test/fixtures/mock_api.js' +import { test as it } from '/test/fixtures/mock_api.js' import { useOAuthStore } from 'src/stores/oauth.js' -const test = injectMswToTest(authApis) +import { + MASTODON_APP_URL, + MASTODON_APP_VERIFY_URL, + OAUTH_MFA_CHALLENGE_URL, + OAUTH_REVOKE_URL, + OAUTH_TOKEN_URL, +} from 'src/api/oauth.js' + +const authApis = () => [ + http.post(MASTODON_APP_URL, () => { + return HttpResponse.json({ + client_id: 'test-id', + client_secret: 'test-secret', + }) + }), + http.get(MASTODON_APP_VERIFY_URL, ({ request }) => { + const authHeader = request.headers.get('Authorization') + if ( + authHeader === 'Bearer test-app-token' || + authHeader === 'Bearer also-good-app-token' + ) { + return HttpResponse.json({}) + } else { + // Pleroma 2.9.0 gives the following respoonse upon error + return HttpResponse.json( + { error: { detail: 'Internal server error' } }, + { + status: 400, + }, + ) + } + }), + http.post(OAUTH_TOKEN_URL, async ({ request }) => { + const data = await request.formData() + + if ( + data.get('client_id') === 'test-id' && + data.get('client_secret') === 'test-secret' && + data.get('grant_type') === 'client_credentials' && + data.has('redirect_uri') + ) { + return HttpResponse.json({ access_token: 'test-app-token' }) + } else { + // Pleroma 2.9.0 gives the following respoonse upon error + return HttpResponse.json( + { error: 'Invalid credentials' }, + { + status: 400, + }, + ) + } + }), +] describe('oauth store', () => { beforeEach(() => { @@ -14,8 +66,11 @@ describe('oauth store', () => { }) describe('createApp', () => { - test('it should use create an app and record client id and secret', async () => { + it('should use create an app and record client id and secret', async ({ + worker, + }) => { const store = useOAuthStore() + worker.use(...authApis()) const app = await store.createApp() expect(store.clientId).to.eql('test-id') expect(store.clientSecret).to.eql('test-secret') @@ -23,9 +78,9 @@ describe('oauth store', () => { expect(app.clientSecret).to.eql('test-secret') }) - test('it should throw and not update if failed', async ({ worker }) => { + it('should throw and not update if failed', async ({ worker }) => { worker.use( - http.post('/api/v1/apps', () => { + http.post(MASTODON_APP_URL, () => { return HttpResponse.text('Throttled', { status: 429 }) }), ) @@ -39,7 +94,8 @@ describe('oauth store', () => { }) describe('ensureApp', () => { - test('it should create an app if it does not exist', async () => { + it('should create an app if it does not exist', async ({ worker }) => { + worker.use(...authApis()) const store = useOAuthStore() const app = await store.ensureApp() expect(store.clientId).to.eql('test-id') @@ -48,9 +104,9 @@ describe('oauth store', () => { expect(app.clientSecret).to.eql('test-secret') }) - test('it should not create an app if it exists', async ({ worker }) => { + it('should not create an app if it exists', async ({ worker }) => { worker.use( - http.post('/api/v1/apps', () => { + http.post(MASTODON_APP_URL, () => { return HttpResponse.text('Should not call this API', { status: 400 }) }), ) @@ -68,7 +124,8 @@ describe('oauth store', () => { }) describe('getAppToken', () => { - test('it should get app token and set it in state', async () => { + it('should get app token and set it in state', async ({ worker }) => { + worker.use(...authApis()) const store = useOAuthStore() store.clientId = 'test-id' store.clientSecret = 'test-secret' @@ -78,7 +135,10 @@ describe('oauth store', () => { expect(store.appToken).to.eql('test-app-token') }) - test('it should throw and not set state if it cannot get app token', async () => { + it('should throw and not set state if it cannot get app token', async ({ + worker, + }) => { + worker.use(...authApis()) const store = useOAuthStore() store.clientId = 'bad-id' store.clientSecret = 'bad-secret' @@ -89,14 +149,17 @@ describe('oauth store', () => { }) describe('ensureAppToken', () => { - test('it should work if the state is empty', async () => { + it('should work if the state is empty', async ({ worker }) => { + worker.use(...authApis()) + console.log('=========', worker.listHandlers()) const store = useOAuthStore() const token = await store.ensureAppToken() expect(token).to.eql('test-app-token') expect(store.appToken).to.eql('test-app-token') }) - test('it should work if we already have a working token', async () => { + it('should work if we already have a working token', async ({ worker }) => { + worker.use(...authApis()) const store = useOAuthStore() store.appToken = 'also-good-app-token' @@ -105,11 +168,12 @@ describe('oauth store', () => { expect(store.appToken).to.eql('also-good-app-token') }) - test('it should work if we have a bad token but good app credentials', async ({ + it('should work if we have a bad token but good app credentials', async ({ worker, }) => { worker.use( - http.post('/api/v1/apps', () => { + ...authApis(), + http.post(MASTODON_APP_URL, () => { return HttpResponse.text('Should not call this API', { status: 400 }) }), ) @@ -123,11 +187,12 @@ describe('oauth store', () => { expect(store.appToken).to.eql('test-app-token') }) - test('it should work if we have no token but good app credentials', async ({ + it('should work if we have no token but good app credentials', async ({ worker, }) => { worker.use( - http.post('/api/v1/apps', () => { + ...authApis(), + http.post(MASTODON_APP_URL, () => { return HttpResponse.text('Should not call this API', { status: 400 }) }), ) @@ -140,7 +205,10 @@ describe('oauth store', () => { expect(store.appToken).to.eql('test-app-token') }) - test('it should work if we have no token and bad app credentials', async () => { + it('should work if we have no token and bad app credentials', async ({ + worker, + }) => { + worker.use(...authApis()) const store = useOAuthStore() store.clientId = 'bad-id' store.clientSecret = 'bad-secret' @@ -152,7 +220,10 @@ describe('oauth store', () => { expect(store.clientSecret).to.eql('test-secret') }) - test('it should work if we have bad token and bad app credentials', async () => { + it('should work if we have bad token and bad app credentials', async ({ + worker, + }) => { + worker.use(...authApis()) const store = useOAuthStore() store.appToken = 'bad-app-token' store.clientId = 'bad-id' @@ -165,9 +236,9 @@ describe('oauth store', () => { expect(store.clientSecret).to.eql('test-secret') }) - test('it should throw if we cannot create an app', async ({ worker }) => { + it('should throw if we cannot create an app', async ({ worker }) => { worker.use( - http.post('/api/v1/apps', () => { + http.post(MASTODON_APP_URL, () => { return HttpResponse.text('Throttled', { status: 429 }) }), ) @@ -176,17 +247,15 @@ describe('oauth store', () => { await expect(store.ensureAppToken()).rejects.toThrowError('Throttled') }) - test('it should throw if we cannot obtain app token', async ({ - worker, - }) => { + it('should throw if we cannot obtain app token', async ({ worker }) => { worker.use( - http.post('/oauth/token', () => { + http.post(OAUTH_TOKEN_URL, () => { return HttpResponse.text('Throttled', { status: 429 }) }), ) const store = useOAuthStore() - await expect(store.ensureAppToken()).rejects.toThrowError('Throttled') + await expect(store.getAppToken()).rejects.toThrowError('Throttled') }) }) }) diff --git a/vite.config.js b/vite.config.js index 3abc24292..6cbba4a53 100644 --- a/vite.config.js +++ b/vite.config.js @@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url' import { DevTools } from '@vitejs/devtools' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' +import { playwright } from '@vitest/browser-playwright' import { defineConfig } from 'vite' import eslint from 'vite-plugin-eslint2' import stylelint from 'vite-plugin-stylelint' @@ -240,8 +241,10 @@ export default defineConfig(async ({ mode, command }) => { exclude: [...configDefaults.exclude, 'test/e2e-playwright/**'], browser: { enabled: true, - provider: 'playwright', - instances: [{ browser: 'firefox' }], + headless: true, + provider: playwright(), + // https://github.com/mswjs/msw/issues/2757 + instances: [{ browser: 'chromium' }], }, }, } diff --git a/yarn.lock b/yarn.lock index 4f3f12589..b8c3e3293 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,7 +29,7 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.27.1": +"@babel/code-frame@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== @@ -1131,11 +1131,6 @@ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== -"@babel/runtime@^7.12.5": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.1.tgz#9fce313d12c9a77507f264de74626e87fd0dc541" - integrity sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog== - "@babel/template@^7.27.0": version "7.27.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.0.tgz#b253e5406cc1df1c57dcd18f11760c2dbf40c0b4" @@ -1327,33 +1322,16 @@ resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.11.tgz#71ba2fb5505b3b01dd3cf551ef329e0094636125" integrity sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg== +"@blazediff/core@1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@blazediff/core/-/core-1.9.1.tgz#ad61c4ec48dc11a2913b9753c8c74902e05e8f14" + integrity sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA== + "@bufbuild/protobuf@^2.5.0": version "2.12.0" resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-2.12.0.tgz#53225636a8fcebb2bd94998ad9d42f99f96add4d" integrity sha512-B/XlCaFIP8LOwzo+bz5uFzATYokcwCKQcghqnlfwSmM5eX/qTkvDBnDPs+gXtX/RyjxJ4DRikECcPJbyALA8FA== -"@bundled-es-modules/cookie@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz#b41376af6a06b3e32a15241d927b840a9b4de507" - integrity sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw== - dependencies: - cookie "^0.7.2" - -"@bundled-es-modules/statuses@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz#761d10f44e51a94902c4da48675b71a76cc98872" - integrity sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg== - dependencies: - statuses "^2.0.1" - -"@bundled-es-modules/tough-cookie@^0.1.6": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz#fa9cd3cedfeecd6783e8b0d378b4a99e52bde5d3" - integrity sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw== - dependencies: - "@types/tough-cookie" "^4.0.5" - tough-cookie "^4.1.4" - "@cacheable/memoize@^2.0.3": version "2.0.3" resolved "https://registry.yarnpkg.com/@cacheable/memoize/-/memoize-2.0.3.tgz#64b18a6b42f987fe8a9e9e2e4391b14cbf85680f" @@ -1572,136 +1550,6 @@ dependencies: tslib "^2.4.0" -"@esbuild/aix-ppc64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz#2ae33300598132cc4cf580dbbb28d30fed3c5c49" - integrity sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg== - -"@esbuild/android-arm64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz#927708b3db5d739d6cb7709136924cc81bec9b03" - integrity sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ== - -"@esbuild/android-arm@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.11.tgz#571f94e7f4068957ec4c2cfb907deae3d01b55ae" - integrity sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg== - -"@esbuild/android-x64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.11.tgz#8a3bf5cae6c560c7ececa3150b2bde76e0fb81e6" - integrity sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g== - -"@esbuild/darwin-arm64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz#0a678c4ac4bf8717e67481e1a797e6c152f93c84" - integrity sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w== - -"@esbuild/darwin-x64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz#70f5e925a30c8309f1294d407a5e5e002e0315fe" - integrity sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ== - -"@esbuild/freebsd-arm64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz#4ec1db687c5b2b78b44148025da9632397553e8a" - integrity sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA== - -"@esbuild/freebsd-x64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz#4c81abd1b142f1e9acfef8c5153d438ca53f44bb" - integrity sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw== - -"@esbuild/linux-arm64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz#69517a111acfc2b93aa0fb5eaeb834c0202ccda5" - integrity sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA== - -"@esbuild/linux-arm@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz#58dac26eae2dba0fac5405052b9002dac088d38f" - integrity sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw== - -"@esbuild/linux-ia32@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz#b89d4efe9bdad46ba944f0f3b8ddd40834268c2b" - integrity sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw== - -"@esbuild/linux-loong64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz#11f603cb60ad14392c3f5c94d64b3cc8b630fbeb" - integrity sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw== - -"@esbuild/linux-mips64el@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz#b7d447ff0676b8ab247d69dac40a5cf08e5eeaf5" - integrity sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ== - -"@esbuild/linux-ppc64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz#b3a28ed7cc252a61b07ff7c8fd8a984ffd3a2f74" - integrity sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw== - -"@esbuild/linux-riscv64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz#ce75b08f7d871a75edcf4d2125f50b21dc9dc273" - integrity sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww== - -"@esbuild/linux-s390x@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz#cd08f6c73b6b6ff9ccdaabbd3ff6ad3dca99c263" - integrity sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw== - -"@esbuild/linux-x64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz#3c3718af31a95d8946ebd3c32bb1e699bdf74910" - integrity sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ== - -"@esbuild/netbsd-arm64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz#b4c767082401e3a4e8595fe53c47cd7f097c8077" - integrity sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg== - -"@esbuild/netbsd-x64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz#f2a930458ed2941d1f11ebc34b9c7d61f7a4d034" - integrity sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A== - -"@esbuild/openbsd-arm64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz#b4ae93c75aec48bc1e8a0154957a05f0641f2dad" - integrity sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg== - -"@esbuild/openbsd-x64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz#b42863959c8dcf9b01581522e40012d2c70045e2" - integrity sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw== - -"@esbuild/openharmony-arm64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz#b2e717141c8fdf6bddd4010f0912e6b39e1640f1" - integrity sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ== - -"@esbuild/sunos-x64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz#9fbea1febe8778927804828883ec0f6dd80eb244" - integrity sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA== - -"@esbuild/win32-arm64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz#501539cedb24468336073383989a7323005a8935" - integrity sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q== - -"@esbuild/win32-ia32@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz#8ac7229aa82cef8f16ffb58f1176a973a7a15343" - integrity sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA== - -"@esbuild/win32-x64@0.25.11": - version "0.25.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz#5ecda6f3fe138b7e456f4e429edde33c823f392f" - integrity sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA== - "@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.4.0", "@eslint-community/eslint-utils@^4.5.0": version "4.5.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz#b0fc7e06d0c94f801537fd4237edc2706d3b8e4c" @@ -1856,37 +1704,41 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.2.tgz#1860473de7dfa1546767448f333db80cb0ff2161" integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ== -"@inquirer/confirm@^5.0.0": - version "5.1.8" - resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.8.tgz#476af2476cd4867905dcabfca8598da4dd65e923" - integrity sha512-dNLWCYZvXDjO3rnQfk2iuJNL4Ivwz/T2+C3+WnNfJKsNGSuOs3wAo2F6e0p946gtSAk31nZMfW+MRmYaplPKsg== - dependencies: - "@inquirer/core" "^10.1.9" - "@inquirer/type" "^3.0.5" +"@inquirer/ansi@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@inquirer/ansi/-/ansi-2.0.7.tgz#86de22810cac3ed406ec10f8d66016815b8226b4" + integrity sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q== -"@inquirer/core@^10.1.9": - version "10.1.9" - resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.1.9.tgz#9ab672a2d9ca60c5d45c7fa9b63e4fe9e038a02e" - integrity sha512-sXhVB8n20NYkUBfDYgizGHlpRVaCRjtuzNZA6xpALIUbkgfd2Hjz+DfEN6+h1BRnuxw0/P4jCIMjMsEOAMwAJw== +"@inquirer/confirm@^6.0.11": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-6.1.1.tgz#9c6a7d79c6132b2af57fdb75747f056204e55356" + integrity sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ== dependencies: - "@inquirer/figures" "^1.0.11" - "@inquirer/type" "^3.0.5" - ansi-escapes "^4.3.2" + "@inquirer/core" "^11.2.1" + "@inquirer/type" "^4.0.7" + +"@inquirer/core@^11.2.1": + version "11.2.1" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-11.2.1.tgz#54ccd8f7d47852140b6066cbd77d63b2c2b168fd" + integrity sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA== + dependencies: + "@inquirer/ansi" "^2.0.7" + "@inquirer/figures" "^2.0.7" + "@inquirer/type" "^4.0.7" cli-width "^4.1.0" - mute-stream "^2.0.0" + fast-wrap-ansi "^0.2.0" + mute-stream "^3.0.0" signal-exit "^4.1.0" - wrap-ansi "^6.2.0" - yoctocolors-cjs "^2.1.2" -"@inquirer/figures@^1.0.11": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.11.tgz#4744e6db95288fea1dead779554859710a959a21" - integrity sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw== +"@inquirer/figures@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-2.0.7.tgz#f5cc5843732a81304d06a0db4b53cc7dbda15541" + integrity sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw== -"@inquirer/type@^3.0.5": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.5.tgz#fe00207e57d5f040e5b18e809c8e7abc3a2ade3a" - integrity sha512-ZJpeIYYueOz/i/ONzrfof8g89kNdO2hjGuvULROo3O8rlB2CRtSseE5KeirnyE4t/thAn/EwvS/vuQeJCn+NZg== +"@inquirer/type@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-4.0.7.tgz#9c6f0d857fe6ad549a3a932343b64e76acb34b10" + integrity sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g== "@intlify/core-base@11.1.12": version "11.1.12" @@ -1979,10 +1831,10 @@ resolved "https://registry.yarnpkg.com/@keyv/serialize/-/serialize-1.1.1.tgz#0c01dd3a3483882af7cf3878d4e71d505c81fc4a" integrity sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA== -"@mswjs/interceptors@^0.39.1": - version "0.39.2" - resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.39.2.tgz#de9de0ab23f99d387c7904df7219a92157d1d666" - integrity sha512-RuzCup9Ct91Y7V79xwCb146RaBRHZ7NBbrIUySumd1rpKqHL5OonaqrGIbug5hNwP/fRyxFMA6ISgw4FTtYFYg== +"@mswjs/interceptors@^0.41.3": + version "0.41.9" + resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.41.9.tgz#9d90bbd60d1ddc30dbcbb827a9bb2e470493530d" + integrity sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w== dependencies: "@open-draft/deferred-promise" "^2.2.0" "@open-draft/logger" "^0.3.0" @@ -2162,6 +2014,11 @@ resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz#4a822d10f6f0e316be4d67b4d4f8c9a124b073bd" integrity sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA== +"@open-draft/deferred-promise@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-3.0.0.tgz#9725acc5afe8ecde690e9e198a094859fdbf2e45" + integrity sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA== + "@open-draft/logger@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@open-draft/logger/-/logger-0.3.0.tgz#2b3ab1242b360aa0adb28b85f5d7da1c133a0954" @@ -2170,7 +2027,7 @@ is-node-process "^1.2.0" outvariant "^1.4.0" -"@open-draft/until@^2.0.0", "@open-draft/until@^2.1.0": +"@open-draft/until@^2.0.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-2.1.0.tgz#0acf32f470af2ceaf47f095cdecd40d68666efda" integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg== @@ -2503,116 +2360,6 @@ estree-walker "^2.0.2" picomatch "^4.0.2" -"@rollup/rollup-android-arm-eabi@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz#0f44a2f8668ed87b040b6fe659358ac9239da4db" - integrity sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ== - -"@rollup/rollup-android-arm64@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz#25b9a01deef6518a948431564c987bcb205274f5" - integrity sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA== - -"@rollup/rollup-darwin-arm64@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz#8a102869c88f3780c7d5e6776afd3f19084ecd7f" - integrity sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA== - -"@rollup/rollup-darwin-x64@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz#8e526417cd6f54daf1d0c04cf361160216581956" - integrity sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA== - -"@rollup/rollup-freebsd-arm64@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz#0e7027054493f3409b1f219a3eac5efd128ef899" - integrity sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA== - -"@rollup/rollup-freebsd-x64@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz#72b204a920139e9ec3d331bd9cfd9a0c248ccb10" - integrity sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ== - -"@rollup/rollup-linux-arm-gnueabihf@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz#ab1b522ebe5b7e06c99504cc38f6cd8b808ba41c" - integrity sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ== - -"@rollup/rollup-linux-arm-musleabihf@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz#f8cc30b638f1ee7e3d18eac24af47ea29d9beb00" - integrity sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ== - -"@rollup/rollup-linux-arm64-gnu@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz#7af37a9e85f25db59dc8214172907b7e146c12cc" - integrity sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg== - -"@rollup/rollup-linux-arm64-musl@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz#a623eb0d3617c03b7a73716eb85c6e37b776f7e0" - integrity sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q== - -"@rollup/rollup-linux-loong64-gnu@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz#76ea038b549c5c6c5f0d062942627c4066642ee2" - integrity sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA== - -"@rollup/rollup-linux-ppc64-gnu@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz#d9a4c3f0a3492bc78f6fdfe8131ac61c7359ccd5" - integrity sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw== - -"@rollup/rollup-linux-riscv64-gnu@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz#87ab033eebd1a9a1dd7b60509f6333ec1f82d994" - integrity sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw== - -"@rollup/rollup-linux-riscv64-musl@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz#bda3eb67e1c993c1ba12bc9c2f694e7703958d9f" - integrity sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg== - -"@rollup/rollup-linux-s390x-gnu@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz#f7bc10fbe096ab44694233dc42a2291ed5453d4b" - integrity sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ== - -"@rollup/rollup-linux-x64-gnu@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz#a151cb1234cc9b2cf5e8cfc02aa91436b8f9e278" - integrity sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q== - -"@rollup/rollup-linux-x64-musl@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz#7859e196501cc3b3062d45d2776cfb4d2f3a9350" - integrity sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg== - -"@rollup/rollup-openharmony-arm64@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz#85d0df7233734df31e547c1e647d2a5300b3bf30" - integrity sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw== - -"@rollup/rollup-win32-arm64-msvc@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz#e62357d00458db17277b88adbf690bb855cac937" - integrity sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w== - -"@rollup/rollup-win32-ia32-msvc@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz#fc7cd40f44834a703c1f1c3fe8bcc27ce476cd50" - integrity sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg== - -"@rollup/rollup-win32-x64-gnu@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz#1a22acfc93c64a64a48c42672e857ee51774d0d3" - integrity sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ== - -"@rollup/rollup-win32-x64-msvc@4.52.5": - version "4.52.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz#1657f56326bbe0ac80eedc9f9c18fc1ddd24e107" - integrity sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg== - "@rtsao/scc@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" @@ -2646,30 +2393,16 @@ lodash.get "^4.4.2" type-detect "^4.1.0" +"@standard-schema/spec@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" + integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== + "@testim/chrome-version@^1.1.4": version "1.1.4" resolved "https://registry.yarnpkg.com/@testim/chrome-version/-/chrome-version-1.1.4.tgz#86e04e677cd6c05fa230dd15ac223fa72d1d7090" integrity sha512-kIhULpw9TrGYnHp/8VfdcneIcxKnLixmADtukQRtJUmsVlMg0niMkwV0xZmi8hqa57xqilIHjWFA0GKvEjVU5g== -"@testing-library/dom@^10.4.0": - version "10.4.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8" - integrity sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.12.5" - "@types/aria-query" "^5.0.1" - aria-query "5.3.0" - chalk "^4.1.0" - dom-accessibility-api "^0.5.9" - lz-string "^1.5.0" - pretty-format "^27.0.2" - -"@testing-library/user-event@^14.6.1": - version "14.6.1" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149" - integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw== - "@tootallnate/quickjs-emscripten@^0.23.0": version "0.23.0" resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c" @@ -2682,22 +2415,25 @@ dependencies: tslib "^2.4.0" -"@types/aria-query@^5.0.1": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" - integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== - "@types/chai@^4.3.5": version "4.3.20" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.20.tgz#cb291577ed342ca92600430841a00329ba05cecc" integrity sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ== -"@types/cookie@^0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" - integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== +"@types/chai@^5.2.2": + version "5.2.3" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-5.2.3.tgz#8e9cd9e1c3581fa6b341a5aed5588eb285be0b4a" + integrity sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA== + dependencies: + "@types/deep-eql" "*" + assertion-error "^2.0.1" -"@types/estree@1.0.8", "@types/estree@^1.0.0": +"@types/deep-eql@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd" + integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== + +"@types/estree@^1.0.0": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== @@ -2739,15 +2475,17 @@ "@types/node" "*" "@types/ws" "*" -"@types/statuses@^2.0.4": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.5.tgz#f61ab46d5352fd73c863a1ea4e1cef3b0b51ae63" - integrity sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A== +"@types/set-cookie-parser@^2.4.10": + version "2.4.10" + resolved "https://registry.yarnpkg.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.10.tgz#ad3a807d6d921db9720621ea3374c5d92020bcbc" + integrity sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw== + dependencies: + "@types/node" "*" -"@types/tough-cookie@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" - integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== +"@types/statuses@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.6.tgz#66748315cc9a96d63403baa8671b2c124f8633aa" + integrity sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA== "@types/ws@*": version "8.18.1" @@ -2853,91 +2591,101 @@ dependencies: "@rolldown/pluginutils" "^1.0.1" -"@vitest/browser@^3.0.7": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@vitest/browser/-/browser-3.1.3.tgz#985f12382bc4aeddbffa4209850ab5cbaaa43e60" - integrity sha512-Dgyez9LbHJHl9ObZPo5mu4zohWLo7SMv8zRWclMF+dxhQjmOtEP0raEX13ac5ygcvihNoQPBZXdya5LMSbcCDQ== +"@vitest/browser-playwright@^4.1.7": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@vitest/browser-playwright/-/browser-playwright-4.1.9.tgz#845e65017dfed8aff59931f91016e7595b8f5c1d" + integrity sha512-Bq1rOGf9waevzG3EOkO/dene6bvKTUsZMVg8S1i+WH3JcMjuXEjiahP9rAqZRELUqjBySOJsvvSWqK/B3wjKQw== dependencies: - "@testing-library/dom" "^10.4.0" - "@testing-library/user-event" "^14.6.1" - "@vitest/mocker" "3.1.3" - "@vitest/utils" "3.1.3" - magic-string "^0.30.17" - sirv "^3.0.1" - tinyrainbow "^2.0.0" - ws "^8.18.1" + "@vitest/browser" "4.1.9" + "@vitest/mocker" "4.1.9" + tinyrainbow "^3.1.0" -"@vitest/expect@3.1.3": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.1.3.tgz#bbca175cd2f23d7de9448a215baed8f3d7abd7b7" - integrity sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg== +"@vitest/browser@4.1.9", "@vitest/browser@^4.1.7": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@vitest/browser/-/browser-4.1.9.tgz#838c5f215f4015089979cf49f930cd3db2888461" + integrity sha512-j1BKtWmPcqpMhmx/L9EPLgAJpCb0zKfwoWLmqBbxaogCXHjOwHFSEoHCBfnGtx93xKQwilZ26m+UOsHqHMkRNg== dependencies: - "@vitest/spy" "3.1.3" - "@vitest/utils" "3.1.3" - chai "^5.2.0" - tinyrainbow "^2.0.0" + "@blazediff/core" "1.9.1" + "@vitest/mocker" "4.1.9" + "@vitest/utils" "4.1.9" + magic-string "^0.30.21" + pngjs "^7.0.0" + sirv "^3.0.2" + tinyrainbow "^3.1.0" + ws "^8.19.0" -"@vitest/mocker@3.1.3": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-3.1.3.tgz#121d0f2fcca20c9ccada9e2d6e761f7fc687f4ce" - integrity sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ== +"@vitest/expect@4.1.9": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-4.1.9.tgz#ba1af73ae53262e3dc9b518cb7b76fb614e0ef53" + integrity sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA== dependencies: - "@vitest/spy" "3.1.3" + "@standard-schema/spec" "^1.1.0" + "@types/chai" "^5.2.2" + "@vitest/spy" "4.1.9" + "@vitest/utils" "4.1.9" + chai "^6.2.2" + tinyrainbow "^3.1.0" + +"@vitest/mocker@4.1.9": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-4.1.9.tgz#a483de79b358aba3dd8f319a0d8ab17c89f5c75d" + integrity sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw== + dependencies: + "@vitest/spy" "4.1.9" estree-walker "^3.0.3" - magic-string "^0.30.17" + magic-string "^0.30.21" -"@vitest/pretty-format@3.1.3", "@vitest/pretty-format@^3.1.3": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-3.1.3.tgz#760b9eab5f253d7d2e7dcd28ef34570f584023d4" - integrity sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA== +"@vitest/pretty-format@4.1.9": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-4.1.9.tgz#885cfe9fcb6ff3df4409ea66192cc1fb23d62fae" + integrity sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A== dependencies: - tinyrainbow "^2.0.0" + tinyrainbow "^3.1.0" -"@vitest/runner@3.1.3": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-3.1.3.tgz#b268fa90fca38fab363f1107f057c0a2a141ee45" - integrity sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA== +"@vitest/runner@4.1.9": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-4.1.9.tgz#bb742947ce4841dfb2d8984a2f9014850be10f51" + integrity sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg== dependencies: - "@vitest/utils" "3.1.3" + "@vitest/utils" "4.1.9" pathe "^2.0.3" -"@vitest/snapshot@3.1.3": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-3.1.3.tgz#39a8f9f8c6ba732ffde59adeacf0a549bef11e76" - integrity sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ== +"@vitest/snapshot@4.1.9": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-4.1.9.tgz#bdfb670ae5617613ea8776e93d0666a66defeeb7" + integrity sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA== dependencies: - "@vitest/pretty-format" "3.1.3" - magic-string "^0.30.17" + "@vitest/pretty-format" "4.1.9" + "@vitest/utils" "4.1.9" + magic-string "^0.30.21" pathe "^2.0.3" -"@vitest/spy@3.1.3": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-3.1.3.tgz#ca81e2b4f0c3d6c75f35defa77c3336f39c8f605" - integrity sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ== - dependencies: - tinyspy "^3.0.2" +"@vitest/spy@4.1.9": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-4.1.9.tgz#bfc40d48fb9bd1a1228bfbfde7f5555e7f6b3867" + integrity sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA== -"@vitest/ui@^3.0.7": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@vitest/ui/-/ui-3.1.3.tgz#ad3c3160e6c86d79f817e09b1f8f02f0e2799851" - integrity sha512-IipSzX+8DptUdXN/GWq3hq5z18MwnpphYdOMm0WndkRGYELzfq7NDP8dMpZT7JGW1uXFrIGxOW2D0Xi++ulByg== +"@vitest/ui@^4.1.7": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@vitest/ui/-/ui-4.1.9.tgz#8aeb5af7295ea04ef1064873334ced04ce20d646" + integrity sha512-U/cRvtqfEPj27FI1n9cyUvi4vXXdcLhjJiI+InYKdk8hP4VrS6RXOjGL7rfFaeBc37iRKANsR6eEzIoC7lmgBQ== dependencies: - "@vitest/utils" "3.1.3" + "@vitest/utils" "4.1.9" fflate "^0.8.2" - flatted "^3.3.3" + flatted "^3.4.2" pathe "^2.0.3" - sirv "^3.0.1" - tinyglobby "^0.2.13" - tinyrainbow "^2.0.0" + sirv "^3.0.2" + tinyglobby "^0.2.15" + tinyrainbow "^3.1.0" -"@vitest/utils@3.1.3": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-3.1.3.tgz#4f31bdfd646cd82d30bfa730d7410cb59d529669" - integrity sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg== +"@vitest/utils@4.1.9": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-4.1.9.tgz#0184c7e6eb3234739b2b6b3b985f78d1ed823ee1" + integrity sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA== dependencies: - "@vitest/pretty-format" "3.1.3" - loupe "^3.1.3" - tinyrainbow "^2.0.0" + "@vitest/pretty-format" "4.1.9" + convert-source-map "^2.0.0" + tinyrainbow "^3.1.0" "@vue/babel-helper-vue-jsx-merge-props@1.4.0": version "1.4.0" @@ -3342,13 +3090,6 @@ ansi-colors@4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== -ansi-escapes@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - ansi-regex@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" @@ -3378,11 +3119,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - ansi-styles@^6.1.0: version "6.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" @@ -3460,13 +3196,6 @@ aria-query@5.1.3: dependencies: deep-equal "^2.0.5" -aria-query@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" - integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== - dependencies: - dequal "^2.0.3" - array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" @@ -3808,11 +3537,6 @@ bytes@3.1.2, bytes@^3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cac@^6.7.14: - version "6.7.14" - resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" - integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== - cac@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cac/-/cac-7.0.0.tgz#7dda83da2268f75f840ab89ac3bcc36c120a78da" @@ -3903,16 +3627,10 @@ chai@5.3.3: loupe "^3.1.0" pathval "^2.0.0" -chai@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/chai/-/chai-5.2.0.tgz#1358ee106763624114addf84ab02697e411c9c05" - integrity sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw== - dependencies: - assertion-error "^2.0.1" - check-error "^2.1.1" - deep-eql "^5.0.1" - loupe "^3.1.0" - pathval "^2.0.0" +chai@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-6.2.2.tgz#ae41b52c9aca87734505362717f3255facda360e" + integrity sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg== chalk@2.4.2, chalk@^2.4.2: version "2.4.2" @@ -4184,11 +3902,16 @@ cookie-signature@^1.2.1: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== -cookie@^0.7.1, cookie@^0.7.2: +cookie@^0.7.1: version "0.7.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== +cookie@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c" + integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== + copy-anything@^3.0.2: version "3.0.5" resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-3.0.5.tgz#2d92dce8c498f790fa7ad16b01a1ae5a45b020a0" @@ -4500,11 +4223,6 @@ depd@2.0.0, depd@^2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -dequal@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" - integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== - destr@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.5.tgz#7d112ff1b925fb8d2079fac5bdb4a90973b51fdb" @@ -4574,11 +4292,6 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.9: - version "0.5.16" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" - integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== - dom-serializer@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" @@ -4864,10 +4577,10 @@ es-get-iterator@^1.1.3: isarray "^2.0.5" stop-iteration-iterator "^1.0.0" -es-module-lexer@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" - integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== +es-module-lexer@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.1.0.tgz#1dfcbb5ea3bbfb63f28e1fc3676c3676d1c9624c" + integrity sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ== es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: version "1.1.1" @@ -4902,38 +4615,6 @@ es-to-primitive@^1.3.0: is-date-object "^1.0.5" is-symbol "^1.0.4" -esbuild@^0.25.0: - version "0.25.11" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.11.tgz#0f31b82f335652580f75ef6897bba81962d9ae3d" - integrity sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q== - optionalDependencies: - "@esbuild/aix-ppc64" "0.25.11" - "@esbuild/android-arm" "0.25.11" - "@esbuild/android-arm64" "0.25.11" - "@esbuild/android-x64" "0.25.11" - "@esbuild/darwin-arm64" "0.25.11" - "@esbuild/darwin-x64" "0.25.11" - "@esbuild/freebsd-arm64" "0.25.11" - "@esbuild/freebsd-x64" "0.25.11" - "@esbuild/linux-arm" "0.25.11" - "@esbuild/linux-arm64" "0.25.11" - "@esbuild/linux-ia32" "0.25.11" - "@esbuild/linux-loong64" "0.25.11" - "@esbuild/linux-mips64el" "0.25.11" - "@esbuild/linux-ppc64" "0.25.11" - "@esbuild/linux-riscv64" "0.25.11" - "@esbuild/linux-s390x" "0.25.11" - "@esbuild/linux-x64" "0.25.11" - "@esbuild/netbsd-arm64" "0.25.11" - "@esbuild/netbsd-x64" "0.25.11" - "@esbuild/openbsd-arm64" "0.25.11" - "@esbuild/openbsd-x64" "0.25.11" - "@esbuild/openharmony-arm64" "0.25.11" - "@esbuild/sunos-x64" "0.25.11" - "@esbuild/win32-arm64" "0.25.11" - "@esbuild/win32-ia32" "0.25.11" - "@esbuild/win32-x64" "0.25.11" - escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" @@ -5250,10 +4931,10 @@ execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -expect-type@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.2.1.tgz#af76d8b357cf5fa76c41c09dafb79c549e75f71f" - integrity sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw== +expect-type@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.3.0.tgz#0d58ed361877a31bbc4dd6cf71bbfef7faf6bd68" + integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA== express@5.1.0: version "5.1.0" @@ -5330,11 +5011,30 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-string-truncated-width@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz#23afe0da67d752ca0727538f1e6967759728ce49" + integrity sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g== + +fast-string-width@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/fast-string-width/-/fast-string-width-3.0.2.tgz#16dbabb491ce5585b5ecb675b65c165d71688eeb" + integrity sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg== + dependencies: + fast-string-truncated-width "^3.0.2" + fast-uri@^3.0.1: version "3.0.6" resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== +fast-wrap-ansi@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz#95e952a0145bce3f59ad56e179f84c48d4072935" + integrity sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q== + dependencies: + fast-string-width "^3.0.2" + fastest-levenshtein@^1.0.16: version "1.0.16" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" @@ -5354,7 +5054,7 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -fdir@^6.4.4, fdir@^6.5.0: +fdir@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== @@ -5463,6 +5163,11 @@ flatted@^3.2.9, flatted@^3.3.3: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== +flatted@^3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== + follow-redirects@^1.0.0, follow-redirects@^1.15.6: version "1.15.9" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" @@ -5756,10 +5461,10 @@ graceful-fs@^4.2.0, graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -graphql@^16.8.1: - version "16.10.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.10.0.tgz#24c01ae0af6b11ea87bf55694429198aaa8e220c" - integrity sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ== +graphql@^16.13.2: + version "16.14.2" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.14.2.tgz#83faf25869e3df727cc855161db5da85b0e5b2c0" + integrity sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA== h3@2.0.1-rc.22: version "2.0.1-rc.22" @@ -5842,10 +5547,13 @@ he@1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -headers-polyfill@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-4.0.3.tgz#922a0155de30ecc1f785bcf04be77844ca95ad07" - integrity sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ== +headers-polyfill@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-5.0.1.tgz#9554eb2892b666db1c7a3380a91b6cfd467a6b19" + integrity sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA== + dependencies: + "@types/set-cookie-parser" "^2.4.10" + set-cookie-parser "^3.0.1" hookable@^5.5.3: version "5.5.3" @@ -6714,7 +6422,7 @@ loupe@^2.3.7: dependencies: get-func-name "^2.0.1" -loupe@^3.1.0, loupe@^3.1.3: +loupe@^3.1.0: version "3.1.3" resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.3.tgz#042a8f7986d77f3d0f98ef7990a2b2fef18b0fd2" integrity sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug== @@ -6748,11 +6456,6 @@ lru-cache@^7.14.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== -lz-string@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" - integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== - magic-string@^0.30.17: version "0.30.18" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.18.tgz#905bfbbc6aa5692703a93db26a9edcaa0007d2bb" @@ -6980,34 +6683,34 @@ ms@2.1.3, ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -msw@2.10.5: - version "2.10.5" - resolved "https://registry.yarnpkg.com/msw/-/msw-2.10.5.tgz#3e43f12e97581c260bf38d8817732b9fec3bfdb0" - integrity sha512-0EsQCrCI1HbhpBWd89DvmxY6plmvrM96b0sCIztnvcNHQbXn5vqwm1KlXslo6u4wN9LFGLC1WFjjgljcQhe40A== +msw@2.14.6: + version "2.14.6" + resolved "https://registry.yarnpkg.com/msw/-/msw-2.14.6.tgz#d30fa6ce8ec3299c6d9bf644cee3a5cc3c3f1197" + integrity sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg== dependencies: - "@bundled-es-modules/cookie" "^2.0.1" - "@bundled-es-modules/statuses" "^1.0.1" - "@bundled-es-modules/tough-cookie" "^0.1.6" - "@inquirer/confirm" "^5.0.0" - "@mswjs/interceptors" "^0.39.1" - "@open-draft/deferred-promise" "^2.2.0" - "@open-draft/until" "^2.1.0" - "@types/cookie" "^0.6.0" - "@types/statuses" "^2.0.4" - graphql "^16.8.1" - headers-polyfill "^4.0.2" + "@inquirer/confirm" "^6.0.11" + "@mswjs/interceptors" "^0.41.3" + "@open-draft/deferred-promise" "^3.0.0" + "@types/statuses" "^2.0.6" + cookie "^1.1.1" + graphql "^16.13.2" + headers-polyfill "^5.0.1" is-node-process "^1.2.0" outvariant "^1.4.3" path-to-regexp "^6.3.0" picocolors "^1.1.1" + rettime "^0.11.11" + statuses "^2.0.2" strict-event-emitter "^0.5.1" - type-fest "^4.26.1" + tough-cookie "^6.0.1" + type-fest "^5.5.0" + until-async "^3.0.2" yargs "^17.7.2" -mute-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b" - integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA== +mute-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-3.0.0.tgz#cd8014dd2acb72e1e91bb67c74f0019e620ba2d1" + integrity sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw== nanoid@^3.3.11, nanoid@^3.3.8: version "3.3.11" @@ -7605,6 +7308,11 @@ pngjs@^5.0.0: resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== +pngjs@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-7.0.0.tgz#a8b7446020ebbc6ac739db6c5415a65d17090e26" + integrity sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow== + pointer-tracker@^2.0.3: version "2.5.3" resolved "https://registry.yarnpkg.com/pointer-tracker/-/pointer-tracker-2.5.3.tgz#5ed01f5ff023c649b2d7b20b07d68c3ac40642a6" @@ -7663,7 +7371,7 @@ postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@8.5.6, postcss@^8.5.3, postcss@^8.5.6: +postcss@8.5.6, postcss@^8.5.6: version "8.5.6" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== @@ -7695,15 +7403,6 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -pretty-format@^27.0.2: - version "27.5.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" - integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== - dependencies: - ansi-regex "^5.0.1" - ansi-styles "^5.0.0" - react-is "^17.0.1" - process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -7844,11 +7543,6 @@ raw-body@^3.0.0: iconv-lite "0.6.3" unpipe "1.0.0" -react-is@^17.0.1: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" - integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== - readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" @@ -8009,6 +7703,11 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +rettime@^0.11.11: + version "0.11.11" + resolved "https://registry.yarnpkg.com/rettime/-/rettime-0.11.11.tgz#fe8fb192e1877bb0080fc1a640cb08eededd7d12" + integrity sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ== + reusify@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" @@ -8043,37 +7742,6 @@ rolldown@1.0.3: "@rolldown/binding-win32-arm64-msvc" "1.0.3" "@rolldown/binding-win32-x64-msvc" "1.0.3" -rollup@^4.34.9: - version "4.52.5" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.52.5.tgz#96982cdcaedcdd51b12359981f240f94304ec235" - integrity sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw== - dependencies: - "@types/estree" "1.0.8" - optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.52.5" - "@rollup/rollup-android-arm64" "4.52.5" - "@rollup/rollup-darwin-arm64" "4.52.5" - "@rollup/rollup-darwin-x64" "4.52.5" - "@rollup/rollup-freebsd-arm64" "4.52.5" - "@rollup/rollup-freebsd-x64" "4.52.5" - "@rollup/rollup-linux-arm-gnueabihf" "4.52.5" - "@rollup/rollup-linux-arm-musleabihf" "4.52.5" - "@rollup/rollup-linux-arm64-gnu" "4.52.5" - "@rollup/rollup-linux-arm64-musl" "4.52.5" - "@rollup/rollup-linux-loong64-gnu" "4.52.5" - "@rollup/rollup-linux-ppc64-gnu" "4.52.5" - "@rollup/rollup-linux-riscv64-gnu" "4.52.5" - "@rollup/rollup-linux-riscv64-musl" "4.52.5" - "@rollup/rollup-linux-s390x-gnu" "4.52.5" - "@rollup/rollup-linux-x64-gnu" "4.52.5" - "@rollup/rollup-linux-x64-musl" "4.52.5" - "@rollup/rollup-openharmony-arm64" "4.52.5" - "@rollup/rollup-win32-arm64-msvc" "4.52.5" - "@rollup/rollup-win32-ia32-msvc" "4.52.5" - "@rollup/rollup-win32-x64-gnu" "4.52.5" - "@rollup/rollup-win32-x64-msvc" "4.52.5" - fsevents "~2.3.2" - rou3@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/rou3/-/rou3-0.8.1.tgz#d18c9dae42bdd9cd4fffa77bc6731d5cfe92129a" @@ -8394,6 +8062,11 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-cookie-parser@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz#e0b1d94c8660c68e6a24dc4e2b5c9e955ccf7e28" + integrity sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw== + set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" @@ -8533,10 +8206,10 @@ sinon@20.0.0: diff "^7.0.0" supports-color "^7.2.0" -sirv@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-3.0.1.tgz#32a844794655b727f9e2867b777e0060fbe07bf3" - integrity sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A== +sirv@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-3.0.2.tgz#f775fccf10e22a40832684848d636346f41cd970" + integrity sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g== dependencies: "@polka/url" "^1.0.0-next.24" mrmime "^2.0.0" @@ -8628,10 +8301,15 @@ statuses@2.0.1, statuses@^2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -std-env@^3.9.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.9.0.tgz#1a6f7243b339dca4c9fd55e1c7504c77ef23e8f1" - integrity sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw== +statuses@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + +std-env@^4.0.0-rc.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-4.1.0.tgz#45899abc590d86d682e87f0acd1033a75084cd3f" + integrity sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ== stop-iteration-iterator@^1.0.0, stop-iteration-iterator@^1.1.0: version "1.1.0" @@ -8934,6 +8612,11 @@ table@^6.9.0: string-width "^4.2.3" strip-ansi "^6.0.1" +tagged-tag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/tagged-tag/-/tagged-tag-1.0.0.tgz#a0b5917c2864cba54841495abfa3f6b13edcf4d6" + integrity sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng== + tapable@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -8968,25 +8651,12 @@ tinybench@^2.9.0: resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== -tinyexec@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" - integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== - -tinyexec@^1.2.2: +tinyexec@^1.0.2, tinyexec@^1.2.2: version "1.2.4" resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.2.4.tgz#ae45bb2edebda94c70f4ea897e0f1243e470db71" integrity sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg== -tinyglobby@^0.2.13: - version "0.2.15" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" - integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== - dependencies: - fdir "^6.5.0" - picomatch "^4.0.3" - -tinyglobby@^0.2.16, tinyglobby@^0.2.17: +tinyglobby@^0.2.15, tinyglobby@^0.2.16, tinyglobby@^0.2.17: version "0.2.17" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.17.tgz#562a9a6c9eb2b3b123d39719f9af5bb44fcd7631" integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g== @@ -8994,20 +8664,22 @@ tinyglobby@^0.2.16, tinyglobby@^0.2.17: fdir "^6.5.0" picomatch "^4.0.4" -tinypool@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.2.tgz#706193cc532f4c100f66aa00b01c42173d9051b2" - integrity sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA== +tinyrainbow@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-3.1.0.tgz#1d8a623893f95cf0a2ddb9e5d11150e191409421" + integrity sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw== -tinyrainbow@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz#9509b2162436315e80e3eee0fcce4474d2444294" - integrity sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw== +tldts-core@^7.4.3: + version "7.4.3" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-7.4.3.tgz#d43401c0499cd884eeaf1ccf073df841a1e4e2dd" + integrity sha512-27ep5H9PzdBrNd5OFM/j3WCU8F3kPwM9D0BOaOf7uYfxMJfyr0K5Tjj69Gri+sZlh2WXd5buIm47NuPF29CDiw== -tinyspy@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" - integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== +tldts@^7.0.5: + version "7.4.3" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-7.4.3.tgz#536c93aecffc96d41ce5627a4b7e12f9c2cfceb5" + integrity sha512-A3BDQBeeukYPzB4QdQ1DtdlUmp4x2OCH8n5UVhEWbyANxNep8GavottKzd1xYKFJKjUgMyPT7EzOfnBO55s8Sg== + dependencies: + tldts-core "^7.4.3" tmp@^0.2.3: version "0.2.3" @@ -9041,6 +8713,13 @@ tough-cookie@^4.1.4: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-6.0.1.tgz#a495f833836609ed983c19bc65639cfbceb54c76" + integrity sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw== + dependencies: + tldts "^7.0.5" + tr46@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.1.0.tgz#4a077922360ae807e172075ce5beb79b36e4a101" @@ -9092,20 +8771,17 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - type-fest@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== -type-fest@^4.26.1: - version "4.39.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.39.0.tgz#c7758be50a83a5b879e7a59ea52421e9816b3928" - integrity sha512-w2IGJU1tIgcrepg9ZJ82d8UmItNQtOFJG0HCUE3SzMokKkTsruVDALl2fAdiEzJlfduoU+VyXJWIIUZ+6jV+nw== +type-fest@^5.5.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-5.7.0.tgz#bae586d3b7c2596bd9c7e62195f33c7fcada1c91" + integrity sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg== + dependencies: + tagged-tag "^1.0.0" type-is@^2.0.0, type-is@^2.0.1: version "2.0.1" @@ -9261,6 +8937,11 @@ unstorage@^1.17.5: ofetch "^1.5.1" ufo "^1.6.3" +until-async@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/until-async/-/until-async-3.0.2.tgz#447f1531fdd7bb2b4c7a98869bdb1a4c2a23865f" + integrity sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw== + untildify@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" @@ -9332,17 +9013,6 @@ vary@^1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -vite-node@3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-3.1.3.tgz#d021ced40b5a057305eaea9ce62c610c33b60a48" - integrity sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA== - dependencies: - cac "^6.7.14" - debug "^4.4.0" - es-module-lexer "^1.7.0" - pathe "^2.0.3" - vite "^5.0.0 || ^6.0.0" - vite-plugin-eslint2@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/vite-plugin-eslint2/-/vite-plugin-eslint2-5.1.0.tgz#c796d4dc852b35f91db508946a4833589adea319" @@ -9359,21 +9029,7 @@ vite-plugin-stylelint@^6.1.0: "@rollup/pluginutils" "^5.3.0" debug "^4.4.3" -"vite@^5.0.0 || ^6.0.0": - version "6.4.1" - resolved "https://registry.yarnpkg.com/vite/-/vite-6.4.1.tgz#afbe14518cdd6887e240a4b0221ab6d0ce733f96" - integrity sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g== - dependencies: - esbuild "^0.25.0" - fdir "^6.4.4" - picomatch "^4.0.2" - postcss "^8.5.3" - rollup "^4.34.9" - tinyglobby "^0.2.13" - optionalDependencies: - fsevents "~2.3.3" - -vite@^8.0.0: +"vite@^6.0.0 || ^7.0.0 || ^8.0.0", vite@^8.0.0: version "8.0.16" resolved "https://registry.yarnpkg.com/vite/-/vite-8.0.16.tgz#ae073866c06563d6634a90169a496e11bd84f1a6" integrity sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw== @@ -9386,31 +9042,30 @@ vite@^8.0.0: optionalDependencies: fsevents "~2.3.3" -vitest@^3.0.7: - version "3.1.3" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-3.1.3.tgz#0b0b01932408cd3af61867f4468d28bd83406ffb" - integrity sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw== +vitest@^4.1.7: + version "4.1.9" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-4.1.9.tgz#98f22fbd70e2a18c4a92bb20624bc92e5dfac5f3" + integrity sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ== dependencies: - "@vitest/expect" "3.1.3" - "@vitest/mocker" "3.1.3" - "@vitest/pretty-format" "^3.1.3" - "@vitest/runner" "3.1.3" - "@vitest/snapshot" "3.1.3" - "@vitest/spy" "3.1.3" - "@vitest/utils" "3.1.3" - chai "^5.2.0" - debug "^4.4.0" - expect-type "^1.2.1" - magic-string "^0.30.17" + "@vitest/expect" "4.1.9" + "@vitest/mocker" "4.1.9" + "@vitest/pretty-format" "4.1.9" + "@vitest/runner" "4.1.9" + "@vitest/snapshot" "4.1.9" + "@vitest/spy" "4.1.9" + "@vitest/utils" "4.1.9" + es-module-lexer "^2.0.0" + expect-type "^1.3.0" + magic-string "^0.30.21" + obug "^2.1.1" pathe "^2.0.3" - std-env "^3.9.0" + picomatch "^4.0.3" + std-env "^4.0.0-rc.1" tinybench "^2.9.0" - tinyexec "^0.3.2" - tinyglobby "^0.2.13" - tinypool "^1.0.2" - tinyrainbow "^2.0.0" - vite "^5.0.0 || ^6.0.0" - vite-node "3.1.3" + tinyexec "^1.0.2" + tinyglobby "^0.2.15" + tinyrainbow "^3.1.0" + vite "^6.0.0 || ^7.0.0 || ^8.0.0" why-is-node-running "^2.3.0" vue-component-type-helpers@^2.0.0: @@ -9699,12 +9354,7 @@ ws@^8.18.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb" integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w== -ws@^8.18.1: - version "8.18.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.2.tgz#42738b2be57ced85f46154320aabb51ab003705a" - integrity sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ== - -ws@^8.21.0: +ws@^8.19.0, ws@^8.21.0: version "8.21.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.21.0.tgz#012e413fc07429945121b0c153158c4343086951" integrity sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g== @@ -9843,11 +9493,6 @@ yocto-queue@^1.2.1: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.2.2.tgz#3e09c95d3f1aa89a58c114c99223edf639152c00" integrity sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ== -yoctocolors-cjs@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" - integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== - zip-stream@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.1.tgz#1337fe974dbaffd2fa9a1ba09662a66932bd7135" From fe1790f217b1b7f1472f7ae24b23fabfa7f0f977 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 18 Jun 2026 20:53:21 +0300 Subject: [PATCH 46/86] fixes --- src/api/public.js | 4 +- src/api/user.js | 2 +- src/components/conversation/conversation.js | 4 +- src/modules/statuses.js | 6 +- src/stores/sync_config.js | 1 - test/unit/specs/stores/lists.spec.js | 120 ++++++++++---------- 6 files changed, 67 insertions(+), 70 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index 570e84dff..1e91676f0 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -339,14 +339,14 @@ export const fetchFavoritedByUsers = ({ id, credentials }) => url: MASTODON_STATUS_FAVORITEDBY_URL(id), method: 'GET', credentials, - }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) })) + }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) })) export const fetchRebloggedByUsers = ({ id, credentials }) => promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id), method: 'GET', credentials, - }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) })) + }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) })) export const fetchEmojiReactions = ({ id, credentials }) => promisedRequest({ diff --git a/src/api/user.js b/src/api/user.js index f7228d84f..127debdea 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -44,7 +44,7 @@ const MASTODON_DENY_USER_URL = (id) => `/api/v1/follow_requests/${id}/reject` const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) => `/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}` const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists` -export const MASTODON_LIST_URL = (id) => `/api/v1/lists/${id}` +export const MASTODON_LIST_URL = (id = '') => `/api/v1/lists/${id}` export const MASTODON_LIST_ACCOUNTS_URL = (id) => `/api/v1/lists/${id}/accounts` const MASTODON_USER_BLOCKS_URL = ({ maxId, diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 27584d945..281f2b573 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -445,7 +445,7 @@ const conversation = { fetchConversation({ id: this.statusId, credentials: useOAuthStore().token, - }).then(({ ancestors, descendants }) => { + }).then(({ data: { ancestors, descendants } }) => { this.$store.dispatch('addNewStatuses', { statuses: ancestors }) this.$store.dispatch('addNewStatuses', { statuses: descendants }) this.setHighlight(this.originalStatusId) @@ -456,7 +456,7 @@ const conversation = { id: this.statusId, credentials: useOAuthStore().token, }) - .then((status) => { + .then(({ data: status }) => { this.$store.dispatch('addNewStatuses', { statuses: [status] }) this.fetchConversation() }) diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 66bcda13d..378759a82 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -788,12 +788,12 @@ const statuses = { fetchFavoritedByUsers({ id, credentials: useOAuthStore().token, - }), + }).then(({ data }) => data), fetchRebloggedByUsers({ id, credentials: useOAuthStore().token, - }), - ]).then(([{ data: favoritedByUsers }, { data: rebloggedByUsers }]) => { + }).then(({ data }) => data),, + ]).then(([favoritedByUsers, rebloggedByUsers]) => { commit('addFavs', { id, favoritedByUsers, diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js index f42d068d7..ccc4bbd4d 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -244,7 +244,6 @@ export const _mergeJournal = (...journals) => { } }) .map((x) => x.data) - console.log(journal) if (path.startsWith('collections')) { const lastRemoveIndex = findLastIndex( diff --git a/test/unit/specs/stores/lists.spec.js b/test/unit/specs/stores/lists.spec.js index 9c0092522..c71cce555 100644 --- a/test/unit/specs/stores/lists.spec.js +++ b/test/unit/specs/stores/lists.spec.js @@ -2,7 +2,7 @@ import { createTestingPinia } from '@pinia/testing' import { HttpResponse, http } from 'msw' import { setActivePinia } from 'pinia' -import { test as mockedIt } from '/test/fixtures/mock_api.js' +import { test as it } from '/test/fixtures/mock_api.js' import { useListsStore } from 'src/stores/lists.js' @@ -17,7 +17,7 @@ describe('The lists store', () => { }) describe('actions', () => { - mockedIt('updates array of all lists', () => { + it('updates array of all lists', () => { const list = { id: '1', title: 'testList' } store.setLists([list]) @@ -25,74 +25,72 @@ describe('The lists store', () => { expect(store.allLists).to.eql([list]) }) - mockedIt( - 'adds a new list with a title, updating the title for existing lists', - async ({ worker }) => { - const list = { id: '1', title: 'testList' } - const modList = { id: '1', title: 'anotherTestTitle' } + it('adds a new list with a title, updating the title for existing lists', async ({ + worker, + }) => { + const list = { id: '1', title: 'testList' } + const modList = { id: '1', title: 'anotherTestTitle' } - worker.use( - http.put(MASTODON_LIST_URL(':id'), () => - HttpResponse.json({ ok: true }), - ), - ) - console.log('1 =========', worker.listHandlers()) + worker.use( + http.put(MASTODON_LIST_URL(':id'), () => + HttpResponse.json({ ok: true }), + ), + ) + console.log('1 =========', worker.listHandlers()) - await store.setList({ listId: list.id, title: list.title }) - expect(store.allListsObject[list.id]).to.eql({ - title: list.title, - accountIds: [], - }) - expect(store.allLists).to.have.length(1) - expect(store.allLists[0]).to.eql(list) + await store.setList({ listId: list.id, title: list.title }) + expect(store.allListsObject[list.id]).to.eql({ + title: list.title, + accountIds: [], + }) + expect(store.allLists).to.have.length(1) + expect(store.allLists[0]).to.eql(list) - console.log('2 =========', worker.listHandlers()) + console.log('2 =========', worker.listHandlers()) - await store.setList({ listId: modList.id, title: modList.title }) - expect(store.allListsObject[modList.id]).to.eql({ - title: modList.title, - accountIds: [], - }) - expect(store.allLists).to.have.length(1) - expect(store.allLists[0]).to.eql(modList) + await store.setList({ listId: modList.id, title: modList.title }) + expect(store.allListsObject[modList.id]).to.eql({ + title: modList.title, + accountIds: [], + }) + expect(store.allLists).to.have.length(1) + expect(store.allLists[0]).to.eql(modList) - console.log('3 =========', worker.listHandlers()) - }, - ) + console.log('3 =========', worker.listHandlers()) + }) - mockedIt( - 'adds a new list with an array of IDs, updating the IDs for existing lists', - async ({ worker }) => { - const list = { id: '1', accountIds: ['1', '2', '3'] } - const modList = { id: '1', accountIds: ['3', '4', '5'] } + it('adds a new list with an array of IDs, updating the IDs for existing lists', async ({ + worker, + }) => { + const list = { id: '1', accountIds: ['1', '2', '3'] } + const modList = { id: '1', accountIds: ['3', '4', '5'] } - worker.use( - http.post(MASTODON_LIST_ACCOUNTS_URL(':id'), () => - HttpResponse.json({ ok: true }), - ), - http.delete(MASTODON_LIST_ACCOUNTS_URL(':id'), () => - HttpResponse.json({ ok: true }), - ), - ) + worker.use( + http.post(MASTODON_LIST_ACCOUNTS_URL(':id'), () => + HttpResponse.json({ ok: true }), + ), + http.delete(MASTODON_LIST_ACCOUNTS_URL(':id'), () => + HttpResponse.json({ ok: true }), + ), + ) - await store.setListAccounts({ - listId: list.id, - accountIds: list.accountIds, - }) - expect(store.allListsObject[list.id].accountIds).to.eql(list.accountIds) + await store.setListAccounts({ + listId: list.id, + accountIds: list.accountIds, + }) + expect(store.allListsObject[list.id].accountIds).to.eql(list.accountIds) - await store.setListAccounts({ - listId: modList.id, - accountIds: modList.accountIds, - }) + await store.setListAccounts({ + listId: modList.id, + accountIds: modList.accountIds, + }) - expect(store.allListsObject[modList.id].accountIds).to.eql( - modList.accountIds, - ) - }, - ) + expect(store.allListsObject[modList.id].accountIds).to.eql( + modList.accountIds, + ) + }) - mockedIt('deletes a list', async ({ worker }) => { + it('deletes a list', async ({ worker }) => { store.$patch({ allLists: [{ id: '1', title: 'testList' }], allListsObject: { @@ -114,7 +112,7 @@ describe('The lists store', () => { }) describe('getters', () => { - mockedIt('returns list title', () => { + it('returns list title', () => { store.$patch({ allLists: [{ id: '1', title: 'testList' }], allListsObject: { @@ -126,7 +124,7 @@ describe('The lists store', () => { expect(store.findListTitle(id)).to.eql('testList') }) - mockedIt('returns list accounts', () => { + it('returns list accounts', () => { store.$patch({ allLists: [{ id: '1', title: 'testList' }], allListsObject: { From f4f83bc26f3e1d6e7c9e1aa1d5b8f930cbe163b3 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 19 Jun 2026 14:43:09 +0300 Subject: [PATCH 47/86] refactor timelines --- src/api/public.js | 168 +++++++++++++++++++++------------------------- 1 file changed, 78 insertions(+), 90 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index 1e91676f0..e18a151c0 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -13,14 +13,14 @@ import { } from 'src/services/entity_normalizer/entity_normalizer.service.js' import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' -const SUGGESTIONS_URL = '/api/v1/suggestions' +const MASTODON_SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' const MASTODON_REGISTRATION_URL = '/api/v1/accounts' const MASTODON_PASSWORD_RESET_URL = ({ email }) => `/auth/password${paramsString({ email })}` -const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' -const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications' +const MASTODON_USER_NOTIFICATIONS_URL = ({ minId, sinceId, maxId, limit, includeTypes, replyVisibility }) => + `/api/v1/notifications${paramsString({ minId, sinceId, maxId, limit, includeTypes, replyVisibility })}` const MASTODON_FOLLOWING_URL = ( id, { minId, maxId, sinceId, limit, withRelationships }, @@ -31,26 +31,43 @@ const MASTODON_FOLLOWERS_URL = ( { minId, maxId, sinceId, limit, withRelationships }, ) => `/api/v1/accounts/${id}/followers${paramsString({ minId, maxId, sinceId, limit, withRelationships })}` -const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home' -const MASTODON_LIST_TIMELINE_URL = (id) => `/api/v1/timelines/list/${id}` -const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks' -const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = '/api/v1/timelines/direct' -const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public' + +const MASTODON_USER_HOME_TIMELINE_URL = ({ minId, sinceId, maxId, limit, replyVisibility }) => + `/api/v1/timelines/home${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +const MASTODON_LIST_TIMELINE_URL = (id, { minId, sinceId, maxId, limit, replyVisibility }) => + `/api/v1/timelines/list/${id}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = ({ minId, sinceId, maxId, limit, replyVisibility }) => + `/api/v1/timelines/direct${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +const MASTODON_PUBLIC_TIMELINE = ({ minId, sinceId, maxId, limit, replyVisibility, local, remote, onlyMedia }) => + `/api/v1/timelines/public${paramsString({ minId, sinceId, maxId, limit, replyVisibility, local, remote, onlyMedia })}` +const MASTODON_TAG_TIMELINE_URL = (tag, { minId, sinceId, maxId, limit, replyVisibility }) => + `/api/v1/timelines/tag/${tag}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +const MASTODON_USER_TIMELINE_URL = (id, { minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia }) => + `/api/v1/accounts/${id}/statuses${paramsString({ minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia })}` +const MASTODON_USER_FAVORITES_TIMELINE_URL = ({ minId, sinceId, maxId, limit, replyVisibility }) => + `/api/v1/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +const MASTODON_BOOKMARK_TIMELINE_URL = ({ minId, sinceId, maxId, limit, replyVisibility, folderId }) => + `/api/v1/bookmarks${paramsString({ minId, sinceId, maxId, limit, replyVisibility, folderId })}` +const PLEROMA_STATUS_QUOTES_URL = (id, { minId, sinceId, maxId, limit, replyVisibility }) => + `/api/v1/pleroma/statuses/${id}/quotes${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +const PLEROMA_USER_FAVORITES_TIMELINE_URL = (id, { minId, sinceId, maxId, limit, replyVisibility }) => + `/api/v1/pleroma/accounts/${id}/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +const AKKOMA_BUBBLE_TIMELINE_URL = ({ minId, sinceId, maxId, limit, replyVisibility }) => + `/api/v1/timelines/bubble${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` + export const MASTODON_STATUS_URL = (id) => `/api/v1/statuses/${id}` const MASTODON_STATUS_CONTEXT_URL = (id) => `/api/v1/statuses/${id}/context` const MASTODON_STATUS_SOURCE_URL = (id) => `/api/v1/statuses/${id}/source` const MASTODON_STATUS_HISTORY_URL = (id) => `/api/v1/statuses/${id}/history` const MASTODON_USER_URL = '/api/v1/accounts' const MASTODON_USER_LOOKUP_URL = '/api/v1/accounts/lookup' -const MASTODON_USER_TIMELINE_URL = (id) => `/api/v1/accounts/${id}/statuses` -const MASTODON_TAG_TIMELINE_URL = (tag) => `/api/v1/timelines/tag/${tag}` -const AKKOMA_BUBBLE_TIMELINE_URL = '/api/v1/timelines/bubble' -const MASTODON_POLL_URL = (id) => `/api/v1/polls/${id}` +const MASTODON_POLL_URL = (id = '') => `/api/v1/polls/${id}` const MASTODON_STATUS_FAVORITEDBY_URL = (id) => `/api/v1/statuses/${id}/favourited_by` const MASTODON_STATUS_REBLOGGEDBY_URL = (id) => `/api/v1/statuses/${id}/reblogged_by` -const MASTODON_SEARCH_2 = '/api/v2/search' +const MASTODON_SEARCH_2 = ({ q, resolve, limit, offset, following, type, withRelationships, accountId, excludeUnreviewed }) => + `/api/v2/search${paramsString({ q, resolve, limit, offset, following, type, withRelationships, accountId, excludeUnreviewed })}` const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' const MASTODON_STREAMING = '/api/v1/streaming' const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers' @@ -58,10 +75,6 @@ const PLEROMA_EMOJI_REACTIONS_URL = (id) => `/api/v1/pleroma/statuses/${id}/reactions` const PLEROMA_SCROBBLES_URL = (id, { maxId, sinceId, minId, limit, offset }) => `/api/v1/pleroma/accounts/${id}/scrobbles${paramsString({ maxId, sinceId, minId, limit, offset })}` -const PLEROMA_STATUS_QUOTES_URL = (id) => - `/api/v1/pleroma/statuses/${id}/quotes` -const PLEROMA_USER_FAVORITES_TIMELINE_URL = (id) => - `/api/v1/pleroma/accounts/${id}/favourites` const EMOJI_PACKS_URL = (page, pageSize) => `/api/v1/pleroma/emoji/packs${paramsString({ page, pageSize })}` @@ -210,23 +223,32 @@ export const fetchTimeline = ({ bookmarkFolderId, }) => { const timelineUrls = { - public: MASTODON_PUBLIC_TIMELINE, friends: MASTODON_USER_HOME_TIMELINE_URL, - dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL, - notifications: MASTODON_USER_NOTIFICATIONS_URL, + public: MASTODON_PUBLIC_TIMELINE, publicAndExternal: MASTODON_PUBLIC_TIMELINE, + dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL, user: MASTODON_USER_TIMELINE_URL, media: MASTODON_USER_TIMELINE_URL, list: MASTODON_LIST_TIMELINE_URL, favorites: MASTODON_USER_FAVORITES_TIMELINE_URL, publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL, - tag: MASTODON_TAG_TIMELINE_URL, bookmarks: MASTODON_BOOKMARK_TIMELINE_URL, - quotes: PLEROMA_STATUS_QUOTES_URL, bubble: AKKOMA_BUBBLE_TIMELINE_URL, + tag: MASTODON_TAG_TIMELINE_URL, + quotes: PLEROMA_STATUS_QUOTES_URL, + + notifications: MASTODON_USER_NOTIFICATIONS_URL, } - const isNotifications = timeline === 'notifications' + const twoArgs = new Set([ + 'user', + 'media', + 'list', + 'publicFavorites', + 'tag', + 'quotes' + ]) + const params = { minId, sinceId, @@ -234,54 +256,46 @@ export const fetchTimeline = ({ limit: 20, } - let url = timelineUrls[timeline] + const id = (() => { + switch (timeline) { + case 'user': + case 'media': + return userId + case 'list': + return listId + case 'quotes': + return statusId + case 'tag': + return tag + } + })() - if (timeline === 'favorites' && userId) { - url = timelineUrls.publicFavorites(userId) - } + const isNotifications = timeline === 'notifications' - if (timeline === 'user' || timeline === 'media') { - url = url(userId) - } - - if (timeline === 'list') { - url = url(listId) - } - - if (timeline === 'quotes') { - url = url(statusId) - } - - if (tag) { - url = url(tag) - } + const urlFunc = timelineUrls[timeline] if (timeline === 'media') { - params.onlyMedia = 1 + params.onlyMedia = true } if (timeline === 'public') { params.local = true } - if (timeline === 'public' || timeline === 'publicAndExternal') { - params.onlyMedia = false - } if (timeline !== 'favorites' && timeline !== 'bookmarks') { params.withMuted = withMuted } if (replyVisibility !== 'all') { params.replyVisibility = replyVisibility } - if (includeTypes.size > 0) { - params.includeTypes = includeTypes - } if (timeline === 'bookmarks' && bookmarkFolderId) { params.folderId = bookmarkFolderId } - return promisedRequest({ - url: url + paramsString(params), - credentials, - }).then(async (result) => { + if (isNotifications && includeTypes.size > 0) { + params.includeTypes = includeTypes + } + + const url = twoArgs.has(timeline) ? urlFunc(id, params) : urlFunc(params) + return promisedRequest({ url, credentials }).then((result) => { const pagination = parseLinkHeaderPagination( result.response.headers.get('Link'), { @@ -304,7 +318,7 @@ export const listEmojiPacks = ({ page, pageSize, credentials }) => export const fetchPinnedStatuses = ({ id, credentials }) => promisedRequest({ - url: MASTODON_USER_TIMELINE_URL(id) + '?pinned=true', + url: MASTODON_USER_TIMELINE_URL(id, { pinned: true }), credentials, }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseStatus) })) @@ -323,7 +337,7 @@ export const resetPassword = ({ email }) => { export const suggestions = ({ credentials }) => promisedRequest({ - url: SUGGESTIONS_URL, + url: MASTODON_SUGGESTIONS_URL, credentials, }) @@ -379,42 +393,16 @@ export const search2 = ({ following, type, }) => { - let url = MASTODON_SEARCH_2 - const params = [] - - if (q) { - params.push(['q', encodeURIComponent(q)]) - } - - if (resolve) { - params.push(['resolve', resolve]) - } - - if (limit) { - params.push(['limit', limit]) - } - - if (offset) { - params.push(['offset', offset]) - } - - if (following) { - params.push(['following', true]) - } - - if (type) { - params.push(['type', type]) - } - - params.push(['with_relationships', true]) - - const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join( - '&', - ) - url += `?${queryString}` - return promisedRequest({ - url, + url: MASTODON_SEARCH_2({ + q, + resolve, + limit, + offset, + following, + type, + withRelationships: true, + }), credentials, }) .then(({ data, ...rest }) => { From 08541b672baf7fb9ffb4dca5770f142b103e88c3 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 19 Jun 2026 14:59:16 +0300 Subject: [PATCH 48/86] more unification --- src/api/public.js | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index e18a151c0..a055a9112 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -68,8 +68,8 @@ const MASTODON_STATUS_REBLOGGEDBY_URL = (id) => `/api/v1/statuses/${id}/reblogged_by` const MASTODON_SEARCH_2 = ({ q, resolve, limit, offset, following, type, withRelationships, accountId, excludeUnreviewed }) => `/api/v2/search${paramsString({ q, resolve, limit, offset, following, type, withRelationships, accountId, excludeUnreviewed })}` -const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' -const MASTODON_STREAMING = '/api/v1/streaming' +const MASTODON_USER_SEARCH_URL = ({ q, resolve }) => `/api/v1/accounts/search${paramsString({ q, resolve })}` +const MASTODON_STREAMING = ({ accessToken, stream }) => `/api/v1/streaming${paramsString({accessToken, stream })}` const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers' const PLEROMA_EMOJI_REACTIONS_URL = (id) => `/api/v1/pleroma/statuses/${id}/reactions` @@ -376,11 +376,7 @@ export const fetchEmojiReactions = ({ id, credentials }) => export const searchUsers = ({ credentials, query }) => promisedRequest({ - url: MASTODON_USER_SEARCH_URL, - params: { - q: query, - resolve: true, - }, + url: MASTODON_USER_SEARCH_URL({ q: query, resolve: true }), credentials, }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) })) @@ -419,20 +415,10 @@ export const fetchKnownDomains = ({ credentials }) => promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials }) export const getMastodonSocketURI = ( - { credentials, stream, args = {} }, + { credentials, stream }, base, ) => { - const url = new URL(MASTODON_STREAMING, base) - if (credentials) { - url.searchParams.append('access_token', credentials) - } - if (stream) { - url.searchParams.append('stream', stream) - } - Object.entries(args).forEach(([key, val]) => { - url.searchParams.append(key, val) - }) - return url + return base + MASTODON_STREAMING({ accessToken: credentials, stream }) } const MASTODON_STREAMING_EVENTS = new Set([ From 55324aea1338eb3e30b4ba4350210f2b067459ae Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 19 Jun 2026 15:50:58 +0300 Subject: [PATCH 49/86] login/logout troubles --- src/api/public.js | 111 +++++++++++++++++++----- src/components/login_form/login_form.js | 21 ++--- src/modules/statuses.js | 2 +- src/modules/users.js | 4 +- 4 files changed, 106 insertions(+), 32 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index a055a9112..5cfd529cd 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -19,7 +19,14 @@ const MASTODON_REGISTRATION_URL = '/api/v1/accounts' const MASTODON_PASSWORD_RESET_URL = ({ email }) => `/auth/password${paramsString({ email })}` -const MASTODON_USER_NOTIFICATIONS_URL = ({ minId, sinceId, maxId, limit, includeTypes, replyVisibility }) => +const MASTODON_USER_NOTIFICATIONS_URL = ({ + minId, + sinceId, + maxId, + limit, + includeTypes, + replyVisibility, +}) => `/api/v1/notifications${paramsString({ minId, sinceId, maxId, limit, includeTypes, replyVisibility })}` const MASTODON_FOLLOWING_URL = ( id, @@ -32,27 +39,82 @@ const MASTODON_FOLLOWERS_URL = ( ) => `/api/v1/accounts/${id}/followers${paramsString({ minId, maxId, sinceId, limit, withRelationships })}` -const MASTODON_USER_HOME_TIMELINE_URL = ({ minId, sinceId, maxId, limit, replyVisibility }) => +const MASTODON_USER_HOME_TIMELINE_URL = ({ + minId, + sinceId, + maxId, + limit, + replyVisibility, +}) => `/api/v1/timelines/home${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const MASTODON_LIST_TIMELINE_URL = (id, { minId, sinceId, maxId, limit, replyVisibility }) => +const MASTODON_LIST_TIMELINE_URL = ( + id, + { minId, sinceId, maxId, limit, replyVisibility }, +) => `/api/v1/timelines/list/${id}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = ({ minId, sinceId, maxId, limit, replyVisibility }) => +const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = ({ + minId, + sinceId, + maxId, + limit, + replyVisibility, +}) => `/api/v1/timelines/direct${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const MASTODON_PUBLIC_TIMELINE = ({ minId, sinceId, maxId, limit, replyVisibility, local, remote, onlyMedia }) => +const MASTODON_PUBLIC_TIMELINE = ({ + minId, + sinceId, + maxId, + limit, + replyVisibility, + local, + remote, + onlyMedia, +}) => `/api/v1/timelines/public${paramsString({ minId, sinceId, maxId, limit, replyVisibility, local, remote, onlyMedia })}` -const MASTODON_TAG_TIMELINE_URL = (tag, { minId, sinceId, maxId, limit, replyVisibility }) => +const MASTODON_TAG_TIMELINE_URL = ( + tag, + { minId, sinceId, maxId, limit, replyVisibility }, +) => `/api/v1/timelines/tag/${tag}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const MASTODON_USER_TIMELINE_URL = (id, { minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia }) => +const MASTODON_USER_TIMELINE_URL = ( + id, + { minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia }, +) => `/api/v1/accounts/${id}/statuses${paramsString({ minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia })}` -const MASTODON_USER_FAVORITES_TIMELINE_URL = ({ minId, sinceId, maxId, limit, replyVisibility }) => +const MASTODON_USER_FAVORITES_TIMELINE_URL = ({ + minId, + sinceId, + maxId, + limit, + replyVisibility, +}) => `/api/v1/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const MASTODON_BOOKMARK_TIMELINE_URL = ({ minId, sinceId, maxId, limit, replyVisibility, folderId }) => +const MASTODON_BOOKMARK_TIMELINE_URL = ({ + minId, + sinceId, + maxId, + limit, + replyVisibility, + folderId, +}) => `/api/v1/bookmarks${paramsString({ minId, sinceId, maxId, limit, replyVisibility, folderId })}` -const PLEROMA_STATUS_QUOTES_URL = (id, { minId, sinceId, maxId, limit, replyVisibility }) => +const PLEROMA_STATUS_QUOTES_URL = ( + id, + { minId, sinceId, maxId, limit, replyVisibility }, +) => `/api/v1/pleroma/statuses/${id}/quotes${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const PLEROMA_USER_FAVORITES_TIMELINE_URL = (id, { minId, sinceId, maxId, limit, replyVisibility }) => +const PLEROMA_USER_FAVORITES_TIMELINE_URL = ( + id, + { minId, sinceId, maxId, limit, replyVisibility }, +) => `/api/v1/pleroma/accounts/${id}/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const AKKOMA_BUBBLE_TIMELINE_URL = ({ minId, sinceId, maxId, limit, replyVisibility }) => +const AKKOMA_BUBBLE_TIMELINE_URL = ({ + minId, + sinceId, + maxId, + limit, + replyVisibility, +}) => `/api/v1/timelines/bubble${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` export const MASTODON_STATUS_URL = (id) => `/api/v1/statuses/${id}` @@ -66,10 +128,22 @@ const MASTODON_STATUS_FAVORITEDBY_URL = (id) => `/api/v1/statuses/${id}/favourited_by` const MASTODON_STATUS_REBLOGGEDBY_URL = (id) => `/api/v1/statuses/${id}/reblogged_by` -const MASTODON_SEARCH_2 = ({ q, resolve, limit, offset, following, type, withRelationships, accountId, excludeUnreviewed }) => +const MASTODON_SEARCH_2 = ({ + q, + resolve, + limit, + offset, + following, + type, + withRelationships, + accountId, + excludeUnreviewed, +}) => `/api/v2/search${paramsString({ q, resolve, limit, offset, following, type, withRelationships, accountId, excludeUnreviewed })}` -const MASTODON_USER_SEARCH_URL = ({ q, resolve }) => `/api/v1/accounts/search${paramsString({ q, resolve })}` -const MASTODON_STREAMING = ({ accessToken, stream }) => `/api/v1/streaming${paramsString({accessToken, stream })}` +const MASTODON_USER_SEARCH_URL = ({ q, resolve }) => + `/api/v1/accounts/search${paramsString({ q, resolve })}` +const MASTODON_STREAMING = ({ accessToken, stream }) => + `/api/v1/streaming${paramsString({ accessToken, stream })}` const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers' const PLEROMA_EMOJI_REACTIONS_URL = (id) => `/api/v1/pleroma/statuses/${id}/reactions` @@ -246,7 +320,7 @@ export const fetchTimeline = ({ 'list', 'publicFavorites', 'tag', - 'quotes' + 'quotes', ]) const params = { @@ -414,10 +488,7 @@ export const search2 = ({ export const fetchKnownDomains = ({ credentials }) => promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials }) -export const getMastodonSocketURI = ( - { credentials, stream }, - base, -) => { +export const getMastodonSocketURI = ({ credentials, stream }, base) => { return base + MASTODON_STREAMING({ accessToken: credentials, stream }) } diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index 8f705f269..d1164c562 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -56,25 +56,26 @@ const LoginForm = { instance: this.server, username: this.user.username, password: this.user.password, - }).then((result) => { - if (result.error) { - if (result.error === 'mfa_required') { - this.requireMFA({ settings: result }) - } else if (result.identifier === 'password_reset_required') { + }) + .then(({ data: result }) => { + this.login(result).then(() => { + this.$router.push({ name: 'friends' }) + }) + }) + .catch((error) => { + if (error === 'mfa_required') { + this.requireMFA({ settings: error }) + } else if (error.identifier === 'password_reset_required') { this.$router.push({ name: 'password-reset', params: { passwordResetRequested: true }, }) } else { - this.error = result.error + this.error = error this.focusOnPasswordInput() } return - } - this.login(result).then(() => { - this.$router.push({ name: 'friends' }) }) - }) }) }, clearError() { diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 378759a82..445d1d436 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -792,7 +792,7 @@ const statuses = { fetchRebloggedByUsers({ id, credentials: useOAuthStore().token, - }).then(({ data }) => data),, + }).then(({ data }) => data), ]).then(([favoritedByUsers, rebloggedByUsers]) => { commit('addFavs', { id, diff --git a/src/modules/users.js b/src/modules/users.js index d4efec2be..d6b1adaaa 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -1,3 +1,4 @@ +import Cookies from 'js-cookie' import { compact, concat, @@ -717,7 +718,6 @@ const users = { .then(() => { store.commit('clearCurrentUser') store.dispatch('disconnectFromSocket') - oauth.clearToken() store.dispatch('stopFetchingTimeline', 'friends') store.dispatch('stopFetchingNotifications') useListsStore().stopFetching() @@ -726,6 +726,8 @@ const users = { store.commit('clearNotifications') store.commit('resetStatuses') store.dispatch('resetChats') + oauth.clearToken() + Cookies.remove('__Host-pleroma_key', { path: '/' }) useInterfaceStore().setLastTimeline('public-timeline') useInterfaceStore().setLayoutWidth(windowWidth()) useInterfaceStore().setLayoutHeight(windowHeight()) From 736c1ffb8ea380b141d963811ad2f5fd13d976e3 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 15:25:37 +0300 Subject: [PATCH 50/86] small changes --- src/api/public.js | 4 ++-- .../timeline_fetcher/timeline_fetcher.service.js | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index 5cfd529cd..e5f2cc340 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -274,10 +274,10 @@ export const fetchStatusHistory = ({ status, credentials }) => promisedRequest({ url: MASTODON_STATUS_HISTORY_URL(status.id), credentials, - }).then(({ data }) => { + }).then(({ data, ...rest }) => { return [...data].reverse().map((item) => { item.originalStatus = status - return parseStatus(item) + return { ...rest, data: parseStatus(item) } }) }) diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index efa06258d..d65897966 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -36,11 +36,11 @@ const fetchAndUpdate = ({ timeline = 'friends', older = false, showImmediately = false, - userId = false, - listId = false, - statusId = false, - bookmarkFolderId = false, - tag = false, + userId, + listId, + statusId, + bookmarkFolderId, + tag, maxId, sinceId, }) => { From 7fc0765dc9c084df68a84630637c690a5419f045 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 15:31:46 +0300 Subject: [PATCH 51/86] fix empty timeline bug --- src/services/timeline_fetcher/timeline_fetcher.service.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index d65897966..da2f59a09 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -52,7 +52,8 @@ const fetchAndUpdate = ({ const loggedIn = !!rootState.users.currentUser if (older) { - args.maxId = maxId || timelineData.minId + // When minId = 0 we need to fetch without maxId param + args.maxId = maxId || timelineData.minId || null } else { if (sinceId === undefined) { args.sinceId = timelineData.maxId From d449701b84394f7aa3c4857c801fff3aaa1ef045 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 15:48:30 +0300 Subject: [PATCH 52/86] post status form error handling --- src/components/post_status_form/post_status_form.js | 5 ----- src/services/errors/errors.js | 5 +++-- src/services/status_poster/status_poster.service.js | 5 ----- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 7eb129430..c564355f3 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -635,11 +635,6 @@ const PostStatusForm = { // Don't apply preview if not loading, because it means // user has closed the preview manually. if (!this.previewLoading) return - if (!data.error) { - this.preview = data - } else { - this.preview = { error: data.error } - } }) .catch((error) => { this.preview = { error } diff --git a/src/services/errors/errors.js b/src/services/errors/errors.js index 41829eb19..12d32c12b 100644 --- a/src/services/errors/errors.js +++ b/src/services/errors/errors.js @@ -13,8 +13,9 @@ function humanizeErrors(errors) { export function StatusCodeError(statusCode, body, options, response) { this.name = 'StatusCodeError' this.statusCode = statusCode - this.message = - statusCode + ' - ' + (JSON && JSON.stringify ? JSON.stringify(body) : body) + this.details = JSON && JSON.stringify ? JSON.stringify(body) : body + this.errorData = body.error + this.message = statusCode + ' - ' + body.error.error || body.error this.error = body // legacy attribute this.options = options this.response = response diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 7fa67e338..2d6ec9313 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -49,11 +49,6 @@ const postStatus = ({ return data }) - .catch((err) => { - return { - error: err.message, - } - }) } const editStatus = ({ From bf86f03a0aa5dcc5a3251d7a9618118fd11ce5a4 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 16:13:30 +0300 Subject: [PATCH 53/86] fix revoking tokens --- src/api/helpers.js | 2 ++ src/stores/oauth_tokens.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/helpers.js b/src/api/helpers.js index 687ed9628..8d5d1f5e9 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -106,6 +106,8 @@ export const promisedRequest = async ({ .get('content-type') .split(';') .map((x) => x.toLowerCase().trim()) + const contentLength = parseInt(response.headers.get('content-length')) + if (contentLength === 0) return null switch (contentType) { case 'text/plain': diff --git a/src/stores/oauth_tokens.js b/src/stores/oauth_tokens.js index bb9bbccbf..afbbb5f46 100644 --- a/src/stores/oauth_tokens.js +++ b/src/stores/oauth_tokens.js @@ -12,7 +12,7 @@ export const useOAuthTokensStore = defineStore('oauthTokens', { fetchTokens() { fetchOAuthTokens({ credentials: useOAuthStore().token, - }).then((tokens) => { + }).then(({ data: tokens }) => { this.swapTokens(tokens) }) }, From 1313e55d988d3cb354800e917c5d9d502567bfab Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 16:19:53 +0300 Subject: [PATCH 54/86] fix voting --- src/stores/polls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/polls.js b/src/stores/polls.js index f2eafacfb..1a1e8e332 100644 --- a/src/stores/polls.js +++ b/src/stores/polls.js @@ -60,7 +60,7 @@ export const usePollsStore = defineStore('polls', { pollId, choices, credentials: useOAuthStore().token, - }).then((poll) => { + }).then(({ data: poll }) => { this.mergeOrAddPoll(poll) return poll }) From 6c4f6dcd05210ba72a655af8183d41bb26843822 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 16:39:08 +0300 Subject: [PATCH 55/86] mfa fixes --- src/components/login_form/login_form.js | 2 +- .../settings_modal/tabs/security_tab/mfa.js | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index d1164c562..a00db03cf 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -63,7 +63,7 @@ const LoginForm = { }) }) .catch((error) => { - if (error === 'mfa_required') { + if (error.errorData?.error === 'mfa_required') { this.requireMFA({ settings: error }) } else if (error.identifier === 'password_reset_required') { this.$router.push({ diff --git a/src/components/settings_modal/tabs/security_tab/mfa.js b/src/components/settings_modal/tabs/security_tab/mfa.js index 12c464719..b5c68120b 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa.js +++ b/src/components/settings_modal/tabs/security_tab/mfa.js @@ -95,7 +95,7 @@ const Mfa = { return generateMfaBackupCodes({ credentials: useOAuthStore().token, - }).then((res) => { + }).then(({ data: res }) => { this.backupCodes.codes = res.codes this.backupCodes.inProgress = false }) @@ -122,7 +122,7 @@ const Mfa = { this.setupState.setupOTPState = 'prepare' mfaSetupOTP({ credentials: useOAuthStore().token, - }).then((res) => { + }).then(({ data: res }) => { this.otpSettings = res this.setupState.setupOTPState = 'confirm' }) @@ -135,11 +135,9 @@ const Mfa = { password: this.currentPassword, credentials: useOAuthStore().token, }).then((res) => { - if (res.error) { - this.error = res.error - return - } this.completeSetup() + }).catch((error) => { + this.error = error }) }, @@ -161,7 +159,7 @@ const Mfa = { // fetch settings from server async fetchSettings() { - const result = await settingsMFA({ + const { data: result } = await settingsMFA({ credentials: useOAuthStore().token, }) if (result.error) return From a39d3b1b56c821791208c85289c2162bf3336dc2 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 19:44:44 +0300 Subject: [PATCH 56/86] missing { data } extraction --- .../bookmark_folder_edit/bookmark_folder_edit.js | 2 +- src/components/chat/chat.js | 7 ++++--- .../remote_user_resolver/remote_user_resolver.js | 2 +- src/components/settings_modal/tabs/appearance_tab.js | 2 +- src/components/settings_modal/tabs/composing_tab.js | 2 +- .../settings_modal/tabs/data_import_export_tab.js | 10 +++++----- src/components/settings_modal/tabs/general_tab.js | 2 +- .../settings_modal/tabs/mutes_and_blocks_tab.js | 4 ++-- src/components/settings_modal/tabs/profile_tab.js | 2 +- .../settings_modal/tabs/security_tab/mfa_totp.js | 2 +- .../settings_modal/tabs/security_tab/security_tab.js | 10 +++++----- src/components/user_card/user_card.js | 2 +- src/components/who_to_follow/who_to_follow.js | 4 ++-- .../who_to_follow_panel/who_to_follow_panel.js | 4 ++-- src/modules/profileConfig.js | 4 ++-- .../follow_request_fetcher.service.js | 2 +- src/stores/admin_settings.js | 2 +- src/stores/emoji.js | 10 +--------- src/stores/instance.js | 3 ++- src/stores/lists.js | 8 +++++--- src/stores/oauth_tokens.js | 4 ++-- src/stores/polls.js | 2 +- src/stores/user_highlight.js | 2 +- 23 files changed, 44 insertions(+), 48 deletions(-) diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.js b/src/components/bookmark_folder_edit/bookmark_folder_edit.js index 748070495..ae5aebedd 100644 --- a/src/components/bookmark_folder_edit/bookmark_folder_edit.js +++ b/src/components/bookmark_folder_edit/bookmark_folder_edit.js @@ -27,7 +27,7 @@ const BookmarkFolderEdit = { fetchBookmarkFolders({ credentials: useOAuthStore().token, - }).then((folders) => { + }).then(({ data: folders }) => { const folder = folders.find((folder) => folder.id === this.id) if (!folder) return diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index b03bc0256..68306acbe 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -278,7 +278,7 @@ const Chat = { maxId, sinceId, credentials: useOAuthStore().token, - }).then((messages) => { + }).then(({ data: messages }) => { // Clear the current chat in case we're recovering from a ws connection loss. if (isFirstFetch) { chatService.clear(chatMessageService) @@ -310,10 +310,11 @@ const Chat = { let chat = this.findOpenedChatByRecipientId(this.recipientId) if (!chat) { try { - chat = await getOrCreateChat({ + const { data } = await getOrCreateChat({ accountId: this.recipientId, credentials: useOAuthStore().token, }) + chat = data } catch (e) { console.error('Error creating or getting a chat', e) this.errorLoadingChat = true @@ -383,7 +384,7 @@ const Chat = { params, credentials: useOAuthStore().token, }) - .then((data) => { + .then(({ data }) => { this.$store.dispatch('addChatMessages', { chatId: this.currentChat.id, updateMaxId: false, diff --git a/src/components/remote_user_resolver/remote_user_resolver.js b/src/components/remote_user_resolver/remote_user_resolver.js index b44629fcf..75304fe52 100644 --- a/src/components/remote_user_resolver/remote_user_resolver.js +++ b/src/components/remote_user_resolver/remote_user_resolver.js @@ -16,7 +16,7 @@ const RemoteUserResolver = { id, credentials: useOAuthStore().token, }) - .then((externalUser) => { + .then(({ data: externalUser }) => { if (externalUser.error) { this.error = true } else { diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js index 5dbd79de4..c11579e53 100644 --- a/src/components/settings_modal/tabs/appearance_tab.js +++ b/src/components/settings_modal/tabs/appearance_tab.js @@ -490,7 +490,7 @@ const AppearanceTab = { background, credentials: useOAuthStore().token, }) - .then((data) => { + .then(({ data }) => { this.$store.commit('addNewUsers', [data]) this.$store.commit('setCurrentUser', data) this.backgroundPreview = null diff --git a/src/components/settings_modal/tabs/composing_tab.js b/src/components/settings_modal/tabs/composing_tab.js index 3a2384832..1586c87bf 100644 --- a/src/components/settings_modal/tabs/composing_tab.js +++ b/src/components/settings_modal/tabs/composing_tab.js @@ -169,7 +169,7 @@ const ComposingTab = { updateProfile({ params, credentials: useOAuthStore().token, - }).then((user) => { + }).then(({ data: user }) => { this.$store.commit('addNewUsers', [user]) this.$store.commit('setCurrentUser', user) }) diff --git a/src/components/settings_modal/tabs/data_import_export_tab.js b/src/components/settings_modal/tabs/data_import_export_tab.js index a89657658..4554f4a1f 100644 --- a/src/components/settings_modal/tabs/data_import_export_tab.js +++ b/src/components/settings_modal/tabs/data_import_export_tab.js @@ -64,7 +64,7 @@ const DataImportExportTab = { return importFollows({ file, credentials: useOAuthStore().token, - }).then((status) => { + }).then(({ data: status }) => { if (!status) { throw new Error('failed') } @@ -74,7 +74,7 @@ const DataImportExportTab = { return importBlocks({ file, credentials: useOAuthStore().token, - }).then((status) => { + }).then(({ data: status }) => { if (!status) { throw new Error('failed') } @@ -84,13 +84,13 @@ const DataImportExportTab = { return importMutes({ file, credentials: useOAuthStore().token, - }).then((status) => { + }).then(({ data: status }) => { if (!status) { throw new Error('failed') } }) }, - generateExportableUsersContent(users) { + generateExportableUsersContent({ data: users }) { // Get addresses return users .map((user) => { @@ -121,7 +121,7 @@ const DataImportExportTab = { listBackups({ credentials: useOAuthStore().token, }) - .then((res) => { + .then(({ data: res }) => { this.backups = res this.listBackupsError = false }) diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index e40bb993e..bed6f6500 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -63,7 +63,7 @@ const GeneralTab = { updateProfile({ params, credentials: useOAuthStore().token, - }).then((user) => { + }).then(({ data: user }) => { this.$store.commit('addNewUsers', [user]) this.$store.commit('setCurrentUser', user) }) diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js index cb945012f..781412249 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js @@ -60,7 +60,7 @@ const MutesAndBlocks = { return importFollows({ file, credentials: useOAuthStore().token, - }).then((status) => { + }).then(({ data: status }) => { if (!status) { throw new Error('failed') } @@ -70,7 +70,7 @@ const MutesAndBlocks = { return importBlocks({ file, credentials: useOAuthStore().token, - }).then((status) => { + }).then(({ data: status }) => { if (!status) { throw new Error('failed') } diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index d30a82bf9..844e223c9 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -43,7 +43,7 @@ const ProfileTab = { params, credentials: useOAuthStore().token, }) - .then((user) => { + .then(({ data: user }) => { this.$store.commit('addNewUsers', [user]) this.$store.commit('setCurrentUser', user) }) diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.js b/src/components/settings_modal/tabs/security_tab/mfa_totp.js index a22e88070..bbe06c217 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa_totp.js +++ b/src/components/settings_modal/tabs/security_tab/mfa_totp.js @@ -40,7 +40,7 @@ export default { mfaDisableOTP({ password: this.currentPassword, credentials: useOAuthStore().token, - }).then((res) => { + }).then(({ data: res }) => { this.inProgress = false if (res.error) { this.error = res.error diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js index 30a8b580e..45876f848 100644 --- a/src/components/settings_modal/tabs/security_tab/security_tab.js +++ b/src/components/settings_modal/tabs/security_tab/security_tab.js @@ -78,7 +78,7 @@ const SecurityTab = { deleteAccount({ credentials: useOAuthStore().token, password: this.deleteAccountConfirmPasswordInput, - }).then((res) => { + }).then(({ data: res }) => { if (res.status === 'success') { this.$store.dispatch('logout') this.$router.push({ name: 'root' }) @@ -94,7 +94,7 @@ const SecurityTab = { newPasswordConfirmation: this.changePasswordInputs[2], credentials: useOAuthStore().token, } - changePassword(params).then((res) => { + changePassword(params).then(({ data: res }) => { if (res.status === 'success') { this.changedPassword = true this.changePasswordError = false @@ -111,7 +111,7 @@ const SecurityTab = { password: this.changeEmailPassword, credentials: useOAuthStore().token, } - changeEmail(params).then((res) => { + changeEmail(params).then(({ data: res }) => { if (res.status === 'success') { this.changedEmail = true this.changeEmailError = false @@ -127,7 +127,7 @@ const SecurityTab = { password: this.moveAccountPassword, credentials: useOAuthStore().token, } - moveAccount(params).then((res) => { + moveAccount(params).then(({ data: res }) => { if (res.status === 'success') { this.movedAccount = true this.moveAccountError = false @@ -163,7 +163,7 @@ const SecurityTab = { listAliases({ credentials: useOAuthStore().token, }) - .then((res) => { + .then(({ data: res }) => { this.aliases = res.aliases this.listAliasesError = false }) diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 2f10c5ebc..70fb057af 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -600,7 +600,7 @@ export default { } updateProfile({ params }) - .then((user) => { + .then(({ data: user }) => { this.newFields.splice(this.newFields.length) merge(this.newFields, user.fields) this.$store.commit('addNewUsers', [user]) diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js index 3c0b53b0d..6f00b3e5e 100644 --- a/src/components/who_to_follow/who_to_follow.js +++ b/src/components/who_to_follow/who_to_follow.js @@ -23,7 +23,7 @@ const WhoToFollow = { fetchUser({ id, credentials: useOAuthStore().token, - }).then((externalUser) => { + }).then(({ data: externalUser }) => { if (!externalUser.error) { this.$store.commit('addNewUsers', [externalUser]) this.users.push(externalUser) @@ -34,7 +34,7 @@ const WhoToFollow = { getWhoToFollow() { const credentials = useOAuthStore().token if (credentials) { - suggestions({ credentials }).then((reply) => { + suggestions({ credentials }).then(({ data: reply }) => { this.showWhoToFollow(reply) }) } diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js index 32f9dc918..7a65ba39d 100644 --- a/src/components/who_to_follow_panel/who_to_follow_panel.js +++ b/src/components/who_to_follow_panel/who_to_follow_panel.js @@ -21,7 +21,7 @@ function showWhoToFollow(panel, reply) { fetchUser({ id: name, credentials: useOAuthStore().token, - }).then((externalUser) => { + }).then(({ data: externalUser }) => { if (!externalUser.error) { panel.$store.commit('addNewUsers', [externalUser]) toFollow.id = externalUser.id @@ -36,7 +36,7 @@ function getWhoToFollow(panel) { panel.usersToFollow.forEach((toFollow) => { toFollow.name = 'Loading...' }) - suggestions({ credentials }).then((reply) => { + suggestions({ credentials }).then(({ data: reply }) => { showWhoToFollow(panel, reply) }) } diff --git a/src/modules/profileConfig.js b/src/modules/profileConfig.js index 80f881a25..3fca7d6a8 100644 --- a/src/modules/profileConfig.js +++ b/src/modules/profileConfig.js @@ -10,7 +10,7 @@ const defaultApi = ({ rootState, commit }, { path, value }) => { return updateProfile({ params, credentials: useOAuthStore().token, - }).then((result) => { + }).then(({ data: result }) => { commit('addNewUsers', [result]) commit('setCurrentUser', result) }) @@ -22,7 +22,7 @@ const notificationsApi = ({ rootState, commit }, { path, value, oldValue }) => { return updateNotificationSettings({ settings, credentials: useOAuthStore().token, - }).then((result) => { + }).then(({ data: result }) => { if (result.status === 'success') { commit('confirmProfileOption', { name, value }) } else { diff --git a/src/services/follow_request_fetcher/follow_request_fetcher.service.js b/src/services/follow_request_fetcher/follow_request_fetcher.service.js index ecb6cb7dd..e49206fcd 100644 --- a/src/services/follow_request_fetcher/follow_request_fetcher.service.js +++ b/src/services/follow_request_fetcher/follow_request_fetcher.service.js @@ -4,7 +4,7 @@ import { promiseInterval } from 'src/services/promise_interval/promise_interval. const fetchAndUpdate = ({ store, credentials }) => { return fetchFollowRequests({ credentials }) .then( - (requests) => { + ({ data: requests }) => { store.commit('setFollowRequests', requests) store.commit('addNewUsers', requests) }, diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index 9c9e69d8b..06bcab9ae 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -601,7 +601,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }).then(({ data }) => data) }, deleteEmojiPack({ name }) { - return createEmojiPack({ + return deleteEmojiPack({ name, credentials: useOAuthStore().token, }).then(({ data }) => data) diff --git a/src/stores/emoji.js b/src/stores/emoji.js index 6ea8d1032..3316f8328 100644 --- a/src/stores/emoji.js +++ b/src/stores/emoji.js @@ -192,7 +192,7 @@ export const useEmojiStore = defineStore('emoji', { listEmojiPacks({ ...params, credentials: useOAuthStore().token, - }), + }).then(({ data }) => data), ) this.adminPacksLocalLoading = false }, @@ -210,10 +210,6 @@ export const useEmojiStore = defineStore('emoji', { pageSize: 0, }) .then((data) => { - if (data.error !== undefined) { - return Promise.reject(data.error) - } - const promises = [] for (let i = 0; i < Math.ceil(data.count / pageSize); i++) { @@ -223,10 +219,6 @@ export const useEmojiStore = defineStore('emoji', { page: i, pageSize, }).then((pageData) => { - if (pageData.error !== undefined) { - return Promise.reject(pageData.error) - } - return pageData.packs }), ) diff --git a/src/stores/instance.js b/src/stores/instance.js index 08bcde1ae..ea3419e80 100644 --- a/src/stores/instance.js +++ b/src/stores/instance.js @@ -211,9 +211,10 @@ export const useInstanceStore = defineStore('instance', { }, async getKnownDomains() { try { - this.knownDomains = await fetchKnownDomains({ + const { data } = await fetchKnownDomains({ credentials: window.vuex.state.users.currentUser.credentials, }) + this.knownDomains = data } catch (e) { console.warn("Can't load known domains\n", e) } diff --git a/src/stores/lists.js b/src/stores/lists.js index 6bafbde7a..7634f1061 100644 --- a/src/stores/lists.js +++ b/src/stores/lists.js @@ -54,7 +54,7 @@ export const useListsStore = defineStore('lists', { return await createList({ title, credentials: useOAuthStore().token, - }).then((list) => { + }).then(({ data: list }) => { this.setList({ listId: list.id, title }) return list }) @@ -63,13 +63,15 @@ export const useListsStore = defineStore('lists', { return await getList({ listId, credentials: useOAuthStore().token, - }).then((list) => this.setList({ listId: list.id, title: list.title })) + }).then(({ data: list }) => + this.setList({ listId: list.id, title: list.title }), + ) }, async fetchListAccounts({ listId }) { return await getListAccounts({ listId, credentials: useOAuthStore().token, - }).then((accountIds) => { + }).then(({ data: accountIds }) => { if (!this.allListsObject[listId]) { this.allListsObject[listId] = { accountIds: [] } } diff --git a/src/stores/oauth_tokens.js b/src/stores/oauth_tokens.js index afbbb5f46..10aa371f9 100644 --- a/src/stores/oauth_tokens.js +++ b/src/stores/oauth_tokens.js @@ -20,8 +20,8 @@ export const useOAuthTokensStore = defineStore('oauthTokens', { revokeOAuthToken({ id, credentials: useOAuthStore().token, - }).then((response) => { - if (response.status === 201) { + }).then(({ status }) => { + if (status === 201) { this.swapTokens(this.tokens.filter((token) => token.id !== id)) } }) diff --git a/src/stores/polls.js b/src/stores/polls.js index 1a1e8e332..e2f4b0ab2 100644 --- a/src/stores/polls.js +++ b/src/stores/polls.js @@ -27,7 +27,7 @@ export const usePollsStore = defineStore('polls', { fetchPoll({ pollId, credentials: useOAuthStore().token, - }).then((poll) => { + }).then(({ data: poll }) => { setTimeout(() => { if (this.trackedPolls[pollId]) { this.updateTrackedPoll(pollId) diff --git a/src/stores/user_highlight.js b/src/stores/user_highlight.js index ca334f579..b056ca808 100644 --- a/src/stores/user_highlight.js +++ b/src/stores/user_highlight.js @@ -350,7 +350,7 @@ export const useUserHighlightStore = defineStore('user_highlight', { updateProfileJSON({ params, credentials: useOAuthStore().token, - }).then((user) => { + }).then(({ data: user }) => { this.initUserHighlight(user) this.dirty = false }) From a294de1248214b0b392925fab04bae07d91aded3 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 19:53:36 +0300 Subject: [PATCH 57/86] fix lookup --- src/api/public.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index e5f2cc340..8ea94c803 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -122,7 +122,7 @@ const MASTODON_STATUS_CONTEXT_URL = (id) => `/api/v1/statuses/${id}/context` const MASTODON_STATUS_SOURCE_URL = (id) => `/api/v1/statuses/${id}/source` const MASTODON_STATUS_HISTORY_URL = (id) => `/api/v1/statuses/${id}/history` const MASTODON_USER_URL = '/api/v1/accounts' -const MASTODON_USER_LOOKUP_URL = '/api/v1/accounts/lookup' +const MASTODON_USER_LOOKUP_URL = ({ acct }) => `/api/v1/accounts/lookup${paramsString({ acct })}` const MASTODON_POLL_URL = (id = '') => `/api/v1/polls/${id}` const MASTODON_STATUS_FAVORITEDBY_URL = (id) => `/api/v1/statuses/${id}/favourited_by` @@ -194,9 +194,8 @@ export const fetchUser = ({ id, credentials }) => export const fetchUserByName = ({ name, credentials }) => promisedRequest({ - url: MASTODON_USER_LOOKUP_URL, + url: MASTODON_USER_LOOKUP_URL({ acct: name }), credentials, - params: { acct: name }, }) .then(({ data }) => data.id) .catch((error) => { From d10f1c72c7fa074f207b9594ae2218cb94cedac1 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 20:07:58 +0300 Subject: [PATCH 58/86] don't do statuscodeerror if error isn't related to non-ok statuscode --- src/api/helpers.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/api/helpers.js b/src/api/helpers.js index 8d5d1f5e9..c9e2ed921 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -132,8 +132,7 @@ export const promisedRequest = async ({ ) } } catch (error) { - throw new StatusCodeError( - response.status, + throw new Error( error, { url, options }, response, From 10070155829e373cc095cb864428a692da5009ff Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 20:15:35 +0300 Subject: [PATCH 59/86] fix includeTypes --- src/api/public.js | 5 ++--- .../timeline_fetcher/timeline_fetcher.service.js | 12 ++++-------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index 8ea94c803..4ef226972 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -312,6 +312,7 @@ export const fetchTimeline = ({ notifications: MASTODON_USER_NOTIFICATIONS_URL, } + const urlFunc = timelineUrls[timeline] const twoArgs = new Set([ 'user', @@ -345,8 +346,6 @@ export const fetchTimeline = ({ const isNotifications = timeline === 'notifications' - const urlFunc = timelineUrls[timeline] - if (timeline === 'media') { params.onlyMedia = true } @@ -363,7 +362,7 @@ export const fetchTimeline = ({ params.folderId = bookmarkFolderId } - if (isNotifications && includeTypes.size > 0) { + if (isNotifications && includeTypes.length > 0) { params.includeTypes = includeTypes } diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index da2f59a09..ff75ea704 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -79,14 +79,6 @@ const fetchAndUpdate = ({ return fetchTimeline(args) .then((response) => { - if (response.errors) { - if (timeline === 'favorites') { - useInstanceCapabilitiesStore().pleromaPublicFavouritesAvailable = false - return - } - throw new Error(`${response.status} ${response.statusText}`) - } - const { data: statuses, pagination } = response if ( !older && @@ -108,6 +100,10 @@ const fetchAndUpdate = ({ return { statuses, pagination } }) .catch((error) => { + if (error.statusCode === 403 && timeline === 'favorites') { + useInstanceCapabilitiesStore().pleromaPublicFavouritesAvailable = false + return + } useInterfaceStore().pushGlobalNotice({ level: 'error', messageKey: 'timeline.error', From f8a895123329d318ed6c370cdefe347725412359 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 20:18:59 +0300 Subject: [PATCH 60/86] notificatiosn error handling --- src/services/errors/errors.js | 3 +- .../notifications_fetcher.service.js | 28 ++++++++----------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/services/errors/errors.js b/src/services/errors/errors.js index 12d32c12b..546554cf1 100644 --- a/src/services/errors/errors.js +++ b/src/services/errors/errors.js @@ -13,9 +13,10 @@ function humanizeErrors(errors) { export function StatusCodeError(statusCode, body, options, response) { this.name = 'StatusCodeError' this.statusCode = statusCode + this.statusText = body.error.error || body.error this.details = JSON && JSON.stringify ? JSON.stringify(body) : body this.errorData = body.error - this.message = statusCode + ' - ' + body.error.error || body.error + this.message = statusCode + ' - ' + statusText this.error = body // legacy attribute this.options = options this.response = response diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index bb94599eb..f787b92e4 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -83,27 +83,23 @@ const fetchAndUpdate = ({ store, credentials, older = false, sinceId }) => { const fetchNotifications = ({ store, args, older }) => { return fetchTimeline(args) .then((response) => { - if (response.errors) { - if ( - response.status === 400 && - response.statusText.includes('Invalid value for enum') - ) { - response.statusText - .matchAll(/(\w+) - Invalid value for enum./g) - .toArray() - .map((x) => x[1]) - .forEach((x) => mastoApiNotificationTypes.delete(x)) - return fetchNotifications({ store, args, older }) - } else { - throw new Error(`${response.status} ${response.statusText}`) - } - } - const notifications = response.data update({ store, notifications, older }) return notifications }) .catch((error) => { + if ( + error.statusCode === 400 && + error.statusText.includes('Invalid value for enum') + ) { + error.statusText + .matchAll(/(\w+) - Invalid value for enum./g) + .toArray() + .map((x) => x[1]) + .forEach((x) => mastoApiNotificationTypes.delete(x)) + return fetchNotifications({ store, args, older }) + } + useInterfaceStore().pushGlobalNotice({ level: 'error', messageKey: 'notifications.error', From f1ac6fab6150495e8a5ac07c59bcee0824748131 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 20:19:34 +0300 Subject: [PATCH 61/86] lint --- src/components/settings_modal/tabs/security_tab/mfa.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/settings_modal/tabs/security_tab/mfa.js b/src/components/settings_modal/tabs/security_tab/mfa.js index b5c68120b..20c28419d 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa.js +++ b/src/components/settings_modal/tabs/security_tab/mfa.js @@ -134,11 +134,13 @@ const Mfa = { token: this.otpConfirmToken, password: this.currentPassword, credentials: useOAuthStore().token, - }).then((res) => { - this.completeSetup() - }).catch((error) => { - this.error = error }) + .then(() => { + this.completeSetup() + }) + .catch((error) => { + this.error = error + }) }, completeSetup() { From 00de60eec4b66f469dc3c1a7feb15b687bd53d7d Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 20:19:54 +0300 Subject: [PATCH 62/86] small refactor+lint in poster --- .../status_poster/status_poster.service.js | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 2d6ec9313..c2ec2d30a 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -36,19 +36,16 @@ const postStatus = ({ poll, preview, idempotencyKey, - }) - .then(({ data }) => { - if (preview) return data - - store.dispatch('addNewStatuses', { - statuses: [data], - timeline: 'friends', - showImmediately: true, - noIdUpdate: true, // To prevent missing notices on next pull. - }) - - return data + }).then(({ data }) => { + if (!preview) store.dispatch('addNewStatuses', { + statuses: [data], + timeline: 'friends', + showImmediately: true, + noIdUpdate: true, // To prevent missing notices on next pull. }) + + return data + }) } const editStatus = ({ From c697041dfc641c7c8a791380bc0d6dfe61ff62bf Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Jun 2026 20:54:34 +0300 Subject: [PATCH 63/86] lint --- src/api/helpers.js | 6 +----- src/api/public.js | 3 ++- src/services/errors/errors.js | 2 +- src/services/status_poster/status_poster.service.js | 13 +++++++------ 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/api/helpers.js b/src/api/helpers.js index c9e2ed921..a7e1b624b 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -132,11 +132,7 @@ export const promisedRequest = async ({ ) } } catch (error) { - throw new Error( - error, - { url, options }, - response, - ) + throw new Error(error, { url, options }, response) } } diff --git a/src/api/public.js b/src/api/public.js index 4ef226972..12af848d7 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -122,7 +122,8 @@ const MASTODON_STATUS_CONTEXT_URL = (id) => `/api/v1/statuses/${id}/context` const MASTODON_STATUS_SOURCE_URL = (id) => `/api/v1/statuses/${id}/source` const MASTODON_STATUS_HISTORY_URL = (id) => `/api/v1/statuses/${id}/history` const MASTODON_USER_URL = '/api/v1/accounts' -const MASTODON_USER_LOOKUP_URL = ({ acct }) => `/api/v1/accounts/lookup${paramsString({ acct })}` +const MASTODON_USER_LOOKUP_URL = ({ acct }) => + `/api/v1/accounts/lookup${paramsString({ acct })}` const MASTODON_POLL_URL = (id = '') => `/api/v1/polls/${id}` const MASTODON_STATUS_FAVORITEDBY_URL = (id) => `/api/v1/statuses/${id}/favourited_by` diff --git a/src/services/errors/errors.js b/src/services/errors/errors.js index 546554cf1..00dfd3712 100644 --- a/src/services/errors/errors.js +++ b/src/services/errors/errors.js @@ -16,7 +16,7 @@ export function StatusCodeError(statusCode, body, options, response) { this.statusText = body.error.error || body.error this.details = JSON && JSON.stringify ? JSON.stringify(body) : body this.errorData = body.error - this.message = statusCode + ' - ' + statusText + this.message = this.statusCode + ' - ' + this.statusText this.error = body // legacy attribute this.options = options this.response = response diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index c2ec2d30a..9a26bd12f 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -37,12 +37,13 @@ const postStatus = ({ preview, idempotencyKey, }).then(({ data }) => { - if (!preview) store.dispatch('addNewStatuses', { - statuses: [data], - timeline: 'friends', - showImmediately: true, - noIdUpdate: true, // To prevent missing notices on next pull. - }) + if (!preview) + store.dispatch('addNewStatuses', { + statuses: [data], + timeline: 'friends', + showImmediately: true, + noIdUpdate: true, // To prevent missing notices on next pull. + }) return data }) From 8fa49f298f8c12a7b5acd8acdbcdcfb51f69a351 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 23 Jun 2026 15:56:28 +0300 Subject: [PATCH 64/86] debugging e2e --- test/e2e-playwright/playwright.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e-playwright/playwright.config.mjs b/test/e2e-playwright/playwright.config.mjs index 04747ee77..bb181d9ee 100644 --- a/test/e2e-playwright/playwright.config.mjs +++ b/test/e2e-playwright/playwright.config.mjs @@ -21,7 +21,7 @@ export default defineConfig({ use: { baseURL, screenshot: 'only-on-failure', - trace: 'on-first-retry', + trace: 'on', video: 'retain-on-failure', }, webServer: { From 7425392413ef72c718962b9a8ca7f195f5a39509 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 23 Jun 2026 16:01:39 +0300 Subject: [PATCH 65/86] Revert "debugging e2e" This reverts commit 8fa49f298f8c12a7b5acd8acdbcdcfb51f69a351. --- test/e2e-playwright/playwright.config.mjs | 2 +- test/e2e-playwright/specs/admin_smoke.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e-playwright/playwright.config.mjs b/test/e2e-playwright/playwright.config.mjs index bb181d9ee..04747ee77 100644 --- a/test/e2e-playwright/playwright.config.mjs +++ b/test/e2e-playwright/playwright.config.mjs @@ -21,7 +21,7 @@ export default defineConfig({ use: { baseURL, screenshot: 'only-on-failure', - trace: 'on', + trace: 'on-first-retry', video: 'retain-on-failure', }, webServer: { diff --git a/test/e2e-playwright/specs/admin_smoke.spec.js b/test/e2e-playwright/specs/admin_smoke.spec.js index 01fd38b29..7ccb73557 100644 --- a/test/e2e-playwright/specs/admin_smoke.spec.js +++ b/test/e2e-playwright/specs/admin_smoke.spec.js @@ -22,6 +22,6 @@ test('admin can open the admin settings modal', async ({ page }) => { modal.getByRole('heading', { name: 'Administration' }), ).toBeVisible() - await modal.getByRole('tab', { name: 'Emoji' }).click() + await modal.getByRole('tab', { title: 'Emoji' }).click() await expect(modal.getByText('Emoji packs')).toBeVisible() }) From 329c4c18a75a76f78304f5e1d84f9866923a0b68 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 23 Jun 2026 16:03:07 +0300 Subject: [PATCH 66/86] debugging --- test/e2e-playwright/specs/user_smoke.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/e2e-playwright/specs/user_smoke.spec.js b/test/e2e-playwright/specs/user_smoke.spec.js index 205cd3e69..74945ed70 100644 --- a/test/e2e-playwright/specs/user_smoke.spec.js +++ b/test/e2e-playwright/specs/user_smoke.spec.js @@ -55,11 +55,14 @@ const login = async (page, user) => { test('user can register, log out, and log back in', async ({ page }) => { const user = createTestUser() await register(page, user) + console.debug('Register Success') await expect(page.getByTitle('Log out')).toBeVisible() await logout(page) + console.debug('Logout Success') await login(page, user) + console.debug('Login Success') await expect(page.getByTitle('Log out')).toBeVisible() }) From 0b27da21ace179b241b74d33fbe43efcc5198dfa Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 23 Jun 2026 16:22:13 +0300 Subject: [PATCH 67/86] didn't help at all --- test/e2e-playwright/specs/admin_smoke.spec.js | 2 +- test/e2e-playwright/specs/user_smoke.spec.js | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/test/e2e-playwright/specs/admin_smoke.spec.js b/test/e2e-playwright/specs/admin_smoke.spec.js index 7ccb73557..01fd38b29 100644 --- a/test/e2e-playwright/specs/admin_smoke.spec.js +++ b/test/e2e-playwright/specs/admin_smoke.spec.js @@ -22,6 +22,6 @@ test('admin can open the admin settings modal', async ({ page }) => { modal.getByRole('heading', { name: 'Administration' }), ).toBeVisible() - await modal.getByRole('tab', { title: 'Emoji' }).click() + await modal.getByRole('tab', { name: 'Emoji' }).click() await expect(modal.getByText('Emoji packs')).toBeVisible() }) diff --git a/test/e2e-playwright/specs/user_smoke.spec.js b/test/e2e-playwright/specs/user_smoke.spec.js index 74945ed70..205cd3e69 100644 --- a/test/e2e-playwright/specs/user_smoke.spec.js +++ b/test/e2e-playwright/specs/user_smoke.spec.js @@ -55,14 +55,11 @@ const login = async (page, user) => { test('user can register, log out, and log back in', async ({ page }) => { const user = createTestUser() await register(page, user) - console.debug('Register Success') await expect(page.getByTitle('Log out')).toBeVisible() await logout(page) - console.debug('Logout Success') await login(page, user) - console.debug('Login Success') await expect(page.getByTitle('Log out')).toBeVisible() }) From ab34412a2ede8783aa70f43e42150000d0014783 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 23 Jun 2026 18:26:01 +0300 Subject: [PATCH 68/86] should fix admin e2e test --- docker-compose.e2e.yml | 2 ++ src/api/helpers.js | 1 + src/stores/admin_settings.js | 23 ++++++++++------------- test/e2e-playwright/playwright.config.mjs | 6 ++++-- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml index 75a4979a1..d97fb6bef 100644 --- a/docker-compose.e2e.yml +++ b/docker-compose.e2e.yml @@ -39,6 +39,8 @@ services: interval: 5s timeout: 3s retries: 60 + ports: + - 4000:4000 e2e: build: diff --git a/src/api/helpers.js b/src/api/helpers.js index a7e1b624b..50841a495 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -132,6 +132,7 @@ export const promisedRequest = async ({ ) } } catch (error) { + if (error.name === 'StatusCodeError') throw error throw new Error(error, { url, options }, response) } } diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index 06bcab9ae..44146756f 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -87,22 +87,19 @@ export const useAdminSettingsStore = defineStore('adminSettings', { loadAdminStuff() { getInstanceDBConfig({ credentials: useOAuthStore().token, - }).then(({ data: backendDbConfig }) => { - if (backendDbConfig.error) { - if (backendDbConfig.error.status === 400) { - backendDbConfig.error.json().then((errorJson) => { - if (/configurable_from_database/.test(errorJson.error)) { - this.setInstanceAdminNoDbConfig() - } - }) - } - } else { + }) + .then(({ data: backendDbConfig }) => this.setInstanceAdminSettings({ credentials: useOAuthStore().token, backendDbConfig, - }) - } - }) + })) + .catch(({ statusCode, statusText }) => { + if (statusCode === 400) { + if (/configurable_from_database/.test(statusText)) { + this.setInstanceAdminNoDbConfig() + } + } + }) if (this.descriptions === null) { getInstanceConfigDescriptions({ credentials: useOAuthStore().token, diff --git a/test/e2e-playwright/playwright.config.mjs b/test/e2e-playwright/playwright.config.mjs index 04747ee77..00bc15723 100644 --- a/test/e2e-playwright/playwright.config.mjs +++ b/test/e2e-playwright/playwright.config.mjs @@ -1,7 +1,7 @@ /* global process */ import { defineConfig, devices } from 'playwright/test' -const baseURL = process.env.E2E_BASE_URL || 'http://localhost:8080' +const baseURL = process.env.E2E_BASE_URL || 'http://localhost:8099' export default defineConfig({ testDir: './specs', @@ -25,12 +25,14 @@ export default defineConfig({ video: 'retain-on-failure', }, webServer: { - command: 'yarn dev -- --host 0.0.0.0 --port 8080 --strictPort', + command: 'yarn dev -- --host 0.0.0.0 --strictPort', url: baseURL, reuseExistingServer: !process.env.CI, timeout: 120_000, env: { ...process.env, + PORT: + process.env.PORT || '8099', VITE_PROXY_TARGET: process.env.VITE_PROXY_TARGET || 'http://localhost:4000', VITE_PROXY_ORIGIN: From 8c7369e15d9cca492c97283e400627e6b12924bb Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 23 Jun 2026 19:56:07 +0300 Subject: [PATCH 69/86] port? --- test/e2e-playwright/playwright.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e-playwright/playwright.config.mjs b/test/e2e-playwright/playwright.config.mjs index 00bc15723..0120b5b58 100644 --- a/test/e2e-playwright/playwright.config.mjs +++ b/test/e2e-playwright/playwright.config.mjs @@ -25,7 +25,7 @@ export default defineConfig({ video: 'retain-on-failure', }, webServer: { - command: 'yarn dev -- --host 0.0.0.0 --strictPort', + command: 'yarn dev -- --host 0.0.0.0 --port $PORT --strictPort', url: baseURL, reuseExistingServer: !process.env.CI, timeout: 120_000, From 0e4222c02b05dc0e2007a6d061a78405d367ea78 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 23 Jun 2026 20:01:36 +0300 Subject: [PATCH 70/86] use port 8099 --- .gitlab-ci.yml | 2 +- .woodpecker/test-e2e.yaml | 2 +- docker-compose.e2e.yml | 2 +- test/e2e/specs/test.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 247218091..692e71fd5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -175,7 +175,7 @@ e2e-pleroma: NOTIFY_EMAIL: $E2E_ADMIN_EMAIL VITE_PROXY_TARGET: http://pleroma:4000 VITE_PROXY_ORIGIN: http://localhost:4000 - E2E_BASE_URL: http://localhost:8080 + E2E_BASE_URL: http://localhost:8099 script: - npm install -g yarn@1.22.22 - yarn --frozen-lockfile diff --git a/.woodpecker/test-e2e.yaml b/.woodpecker/test-e2e.yaml index c0bf103cd..a5289bd78 100644 --- a/.woodpecker/test-e2e.yaml +++ b/.woodpecker/test-e2e.yaml @@ -30,7 +30,7 @@ steps: environment: APT_CACHE_DIR: apt-cache DEBIAN_FRONTEND: noninteractive - E2E_BASE_URL: http://localhost:8080 + E2E_BASE_URL: http://localhost:8099 FF_NETWORK_PER_BUILD: "true" PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" VITE_PROXY_ORIGIN: "http://pleroma:4000" diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml index d97fb6bef..c49984684 100644 --- a/docker-compose.e2e.yml +++ b/docker-compose.e2e.yml @@ -53,7 +53,7 @@ services: CI: "1" VITE_PROXY_TARGET: http://pleroma:4000 VITE_PROXY_ORIGIN: http://localhost:4000 - E2E_BASE_URL: http://localhost:8080 + E2E_BASE_URL: http://localhost:8099 E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin} E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin} command: ["yarn", "e2e:pw"] diff --git a/test/e2e/specs/test.js b/test/e2e/specs/test.js index f8993989b..ba3e757fd 100644 --- a/test/e2e/specs/test.js +++ b/test/e2e/specs/test.js @@ -4,7 +4,7 @@ module.exports = { 'default e2e tests': function (browser) { // automatically uses dev Server port from /config.index.js - // default: http://localhost:8080 + // default: http://localhost:8099 // see nightwatch.conf.js const devServer = browser.globals.devServerURL From cb20672f0b4a2f7115a4b1fe12311158dcabdd2f Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 23 Jun 2026 20:07:44 +0300 Subject: [PATCH 71/86] lint --- src/stores/admin_settings.js | 3 ++- test/e2e-playwright/playwright.config.mjs | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index 44146756f..5aa91c819 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -92,7 +92,8 @@ export const useAdminSettingsStore = defineStore('adminSettings', { this.setInstanceAdminSettings({ credentials: useOAuthStore().token, backendDbConfig, - })) + }), + ) .catch(({ statusCode, statusText }) => { if (statusCode === 400) { if (/configurable_from_database/.test(statusText)) { diff --git a/test/e2e-playwright/playwright.config.mjs b/test/e2e-playwright/playwright.config.mjs index 0120b5b58..51a4de21e 100644 --- a/test/e2e-playwright/playwright.config.mjs +++ b/test/e2e-playwright/playwright.config.mjs @@ -31,8 +31,7 @@ export default defineConfig({ timeout: 120_000, env: { ...process.env, - PORT: - process.env.PORT || '8099', + PORT: process.env.PORT || '8099', VITE_PROXY_TARGET: process.env.VITE_PROXY_TARGET || 'http://localhost:4000', VITE_PROXY_ORIGIN: From ee42d4095a70ab774203af401514c9d6ac81f22d Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 23 Jun 2026 20:15:47 +0300 Subject: [PATCH 72/86] wrong fetcher --- src/stores/bookmark_folders.js | 4 ++-- src/stores/lists.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/stores/bookmark_folders.js b/src/stores/bookmark_folders.js index 6ffec807b..713a21d00 100644 --- a/src/stores/bookmark_folders.js +++ b/src/stores/bookmark_folders.js @@ -27,8 +27,8 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { }, actions: { startFetching() { - promiseInterval(() => { - this.fetcher = fetchBookmarkFolders({ + this.fetcher = promiseInterval(() => { + fetchBookmarkFolders({ credentials: useOAuthStore().token, }) .then(({ data: folders }) => this.setBookmarkFolders(folders)) diff --git a/src/stores/lists.js b/src/stores/lists.js index 7634f1061..37471d46e 100644 --- a/src/stores/lists.js +++ b/src/stores/lists.js @@ -34,8 +34,8 @@ export const useListsStore = defineStore('lists', { }, actions: { startFetching() { - promiseInterval(() => { - this.fetcher = fetchLists({ + this.fetcher = promiseInterval(() => { + fetchLists({ credentials: useOAuthStore().token, }) .then(({ data: lists }) => this.setLists(lists)) From ada7ae65adb8645a4495052621209b3a5425391a Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 24 Jun 2026 18:50:56 +0300 Subject: [PATCH 73/86] don't rethrow --- src/api/helpers.js | 62 +++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/src/api/helpers.js b/src/api/helpers.js index 50841a495..6986a6ab7 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -98,42 +98,36 @@ export const promisedRequest = async ({ } } - let response = null - try { - response = await fetch(url, options) - const data = await (async () => { - const [contentType] = response.headers - .get('content-type') - .split(';') - .map((x) => x.toLowerCase().trim()) - const contentLength = parseInt(response.headers.get('content-length')) - if (contentLength === 0) return null + const response = await fetch(url, options) + const data = await (async () => { + const [contentType] = response.headers + .get('content-type') + .split(';') + .map((x) => x.toLowerCase().trim()) + const contentLength = parseInt(response.headers.get('content-length')) + if (contentLength === 0) return null - switch (contentType) { - case 'text/plain': - return await response.text() - case 'application/json': - return await response.json() - default: - return await response.bytes() - } - })() - - const { ok, status } = response - - if (ok) { - return { response, status, data } - } else { - throw new StatusCodeError( - response.status, - data, - { url, options }, - response, - ) + switch (contentType) { + case 'text/plain': + return await response.text() + case 'application/json': + return await response.json() + default: + return await response.bytes() } - } catch (error) { - if (error.name === 'StatusCodeError') throw error - throw new Error(error, { url, options }, response) + })() + + const { ok, status } = response + + if (ok) { + return { response, status, data } + } else { + throw new StatusCodeError( + response.status, + data, + { url, options }, + response, + ) } } From 30db636389654c74fb7f1e6b062e9ad7c322435a Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 24 Jun 2026 19:03:46 +0300 Subject: [PATCH 74/86] fix tests --- src/api/oauth.js | 2 -- src/services/errors/errors.js | 2 +- test/unit/specs/stores/oauth.spec.js | 6 ++++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/api/oauth.js b/src/api/oauth.js index 7ecb0cc3f..b774b3f6c 100644 --- a/src/api/oauth.js +++ b/src/api/oauth.js @@ -2,8 +2,6 @@ import { reduce } from 'lodash' import { paramsString, promisedRequest } from './helpers.js' -import { StatusCodeError } from 'src/services/errors/errors.js' - const REDIRECT_URI = `${window.location.origin}/oauth-callback` export const MASTODON_APP_VERIFY_URL = '/api/v1/apps/verify_credentials' diff --git a/src/services/errors/errors.js b/src/services/errors/errors.js index 00dfd3712..a28f31775 100644 --- a/src/services/errors/errors.js +++ b/src/services/errors/errors.js @@ -13,7 +13,7 @@ function humanizeErrors(errors) { export function StatusCodeError(statusCode, body, options, response) { this.name = 'StatusCodeError' this.statusCode = statusCode - this.statusText = body.error.error || body.error + this.statusText = body.error || body this.details = JSON && JSON.stringify ? JSON.stringify(body) : body this.errorData = body.error this.message = this.statusCode + ' - ' + this.statusText diff --git a/test/unit/specs/stores/oauth.spec.js b/test/unit/specs/stores/oauth.spec.js index c4f4ba978..828175c9f 100644 --- a/test/unit/specs/stores/oauth.spec.js +++ b/test/unit/specs/stores/oauth.spec.js @@ -69,6 +69,12 @@ describe('oauth store', () => { it('should use create an app and record client id and secret', async ({ worker, }) => { + worker.use( + http.post(MASTODON_APP_URL, () => { + return HttpResponse.text('Throttled', { status: 429 }) + }), + ) + const store = useOAuthStore() worker.use(...authApis()) const app = await store.createApp() From 6060d4efbcd57f18e58ec598f62337bfdc255d37 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 24 Jun 2026 19:03:50 +0300 Subject: [PATCH 75/86] update playwright --- package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index cdeefc30c..f594d7a99 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "msw": "2.14.6", "nightwatch": "3.12.2", "oxc": "^1.0.1", - "playwright": "1.57.0", + "playwright": "1.61.0", "postcss": "8.5.6", "postcss-html": "^1.5.0", "postcss-scss": "^4.0.6", diff --git a/yarn.lock b/yarn.lock index b8c3e3293..b60f88b7b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7289,17 +7289,17 @@ pkg-types@^1.3.1: mlly "^1.7.4" pathe "^2.0.1" -playwright-core@1.57.0: - version "1.57.0" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.57.0.tgz#3dcc9a865af256fa9f0af0d67fc8dd54eecaebf5" - integrity sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ== +playwright-core@1.61.0: + version "1.61.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.61.0.tgz#caf8078b2a39cd7738dc75ec11cb3b47f385c3f0" + integrity sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA== -playwright@1.57.0: - version "1.57.0" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.57.0.tgz#74d1dacff5048dc40bf4676940b1901e18ad0f46" - integrity sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw== +playwright@1.61.0: + version "1.61.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.61.0.tgz#7082df3df08ffa82b11420ea5fae84a40bd16e3f" + integrity sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ== dependencies: - playwright-core "1.57.0" + playwright-core "1.61.0" optionalDependencies: fsevents "2.3.2" From 728ba716b81418afd14a144296637ff4cc2efae0 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 24 Jun 2026 19:08:36 +0300 Subject: [PATCH 76/86] lint --- src/api/helpers.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/api/helpers.js b/src/api/helpers.js index 6986a6ab7..5fb11a183 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -122,12 +122,7 @@ export const promisedRequest = async ({ if (ok) { return { response, status, data } } else { - throw new StatusCodeError( - response.status, - data, - { url, options }, - response, - ) + throw new StatusCodeError(response.status, data, { url, options }, response) } } From 354f35c690b996db1d3297674dc2dc2cb3955078 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 24 Jun 2026 19:38:39 +0300 Subject: [PATCH 77/86] fix and refactor websocket --- src/api/public.js | 164 -------------------- src/components/chat/chat.js | 4 +- src/components/conversation/conversation.js | 4 +- src/modules/api.js | 6 +- vite.config.js | 21 +-- 5 files changed, 22 insertions(+), 177 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index 12af848d7..f4ae778bc 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -143,8 +143,6 @@ const MASTODON_SEARCH_2 = ({ `/api/v2/search${paramsString({ q, resolve, limit, offset, following, type, withRelationships, accountId, excludeUnreviewed })}` const MASTODON_USER_SEARCH_URL = ({ q, resolve }) => `/api/v1/accounts/search${paramsString({ q, resolve })}` -const MASTODON_STREAMING = ({ accessToken, stream }) => - `/api/v1/streaming${paramsString({ accessToken, stream })}` const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers' const PLEROMA_EMOJI_REACTIONS_URL = (id) => `/api/v1/pleroma/statuses/${id}/reactions` @@ -487,168 +485,6 @@ export const search2 = ({ export const fetchKnownDomains = ({ credentials }) => promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials }) -export const getMastodonSocketURI = ({ credentials, stream }, base) => { - return base + MASTODON_STREAMING({ accessToken: credentials, stream }) -} - -const MASTODON_STREAMING_EVENTS = new Set([ - 'update', - 'notification', - 'delete', - 'filters_changed', - 'status.update', -]) - -const PLEROMA_STREAMING_EVENTS = new Set([ - 'pleroma:chat_update', - 'pleroma:respond', -]) - -// A thin wrapper around WebSocket API that allows adding a pre-processor to it -// Uses EventTarget and a CustomEvent to proxy events -export const ProcessedWS = ({ - url, - preprocessor = handleMastoWS, - id = 'Unknown', - credentials, -}) => { - const eventTarget = new EventTarget() - const socket = new WebSocket(url) - if (!socket) throw new Error(`Failed to create socket ${id}`) - const proxy = (original, eventName, processor = (a) => a) => { - original.addEventListener(eventName, (eventData) => { - eventTarget.dispatchEvent( - new CustomEvent(eventName, { detail: processor(eventData) }), - ) - }) - } - socket.addEventListener('open', (wsEvent) => { - console.debug(`[WS][${id}] Socket connected`, wsEvent) - if (credentials) { - socket.send( - JSON.stringify({ - type: 'pleroma:authenticate', - token: credentials, - }), - ) - } - }) - socket.addEventListener('error', (wsEvent) => { - console.debug(`[WS][${id}] Socket errored`, wsEvent) - }) - socket.addEventListener('close', (wsEvent) => { - console.debug( - `[WS][${id}] Socket disconnected with code ${wsEvent.code}`, - wsEvent, - ) - }) - // Commented code reason: very spammy, uncomment to enable message debug logging - /* - socket.addEventListener('message', (wsEvent) => { - console.debug( - `[WS][${id}] Message received`, - wsEvent - ) - }) - /**/ - - const onAuthenticated = () => { - eventTarget.dispatchEvent(new CustomEvent('pleroma:authenticated')) - } - - proxy(socket, 'open') - proxy(socket, 'close') - proxy(socket, 'message', (event) => preprocessor(event, { onAuthenticated })) - proxy(socket, 'error') - - // 1000 = Normal Closure - eventTarget.close = () => { - socket.close(1000, 'Shutting down socket') - } - eventTarget.getState = () => socket.readyState - eventTarget.subscribe = (stream, args = {}) => { - console.debug(`[WS][${id}] Subscribing to stream ${stream} with args`, args) - socket.send( - JSON.stringify({ - type: 'subscribe', - stream, - ...args, - }), - ) - } - eventTarget.unsubscribe = (stream, args = {}) => { - console.debug( - `[WS][${id}] Unsubscribing from stream ${stream} with args`, - args, - ) - socket.send( - JSON.stringify({ - type: 'unsubscribe', - stream, - ...args, - }), - ) - } - - return eventTarget -} - -export const handleMastoWS = ( - wsEvent, - { - onAuthenticated = () => { - /* no-op */ - }, - } = {}, -) => { - const { data } = wsEvent - if (!data) return - const parsedEvent = JSON.parse(data) - const { event, payload } = parsedEvent - if ( - MASTODON_STREAMING_EVENTS.has(event) || - PLEROMA_STREAMING_EVENTS.has(event) - ) { - // MastoBE and PleromaBE both send payload for delete as a PLAIN string - if (event === 'delete') { - return { event, id: payload } - } - const data = payload ? JSON.parse(payload) : null - if (event === 'pleroma:respond') { - if (data.type === 'pleroma:authenticate') { - if (data.result === 'success') { - console.debug('[WS] Successfully authenticated') - onAuthenticated() - } else { - console.error('[WS] Unable to authenticate:', data.error) - wsEvent.target.close() - } - } - return null - } else if (event === 'update') { - return { event, status: parseStatus(data) } - } else if (event === 'status.update') { - return { event, status: parseStatus(data) } - } else if (event === 'notification') { - return { event, notification: parseNotification(data) } - } else if (event === 'pleroma:chat_update') { - return { event, chatUpdate: parseChat(data) } - } - } else { - console.warn('Unknown event', wsEvent) - return null - } -} - -export const WSConnectionStatus = Object.freeze({ - JOINED: 1, - CLOSED: 2, - ERROR: 3, - DISABLED: 4, - STARTING: 5, - STARTING_INITIAL: 6, -}) - export const fetchScrobbles = ({ accountId, limit = 1 }) => promisedRequest({ url: PLEROMA_SCROBBLES_URL(accountId, { limit }), diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index 68306acbe..acd1f1c5f 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -23,7 +23,9 @@ import { getOrCreateChat, sendChatMessage, } from 'src/api/chats.js' -import { WSConnectionStatus } from 'src/api/public.js' +import { + WSConnectionStatus, +} from 'src/api/websocket.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons' diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 281f2b573..af78b7383 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -13,8 +13,10 @@ import { useOAuthStore } from 'src/stores/oauth.js' import { fetchConversation, fetchStatus, - WSConnectionStatus, } from 'src/api/public.js' +import { + WSConnectionStatus, +} from 'src/api/websocket.js' import { library } from '@fortawesome/fontawesome-svg-core' import { diff --git a/src/modules/api.js b/src/modules/api.js index a36e21c05..9d6426c2e 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -10,10 +10,12 @@ import { useShoutStore } from 'src/stores/shout.js' import { fetchTimeline, +} from 'src/api/public.js' +import { getMastodonSocketURI, ProcessedWS, WSConnectionStatus, -} from 'src/api/public.js' +} from 'src/api/websocket.js' import followRequestFetcher from 'src/services/follow_request_fetcher/follow_request_fetcher.service' import notificationsFetcher from 'src/services/notifications_fetcher/notifications_fetcher.service.js' import timelineFetcher from 'src/services/timeline_fetcher/timeline_fetcher.service.js' @@ -99,7 +101,7 @@ const api = { const serv = useInstanceStore().server.replace('http', 'ws') const credentials = useOAuthStore().token - const url = getMastodonSocketURI({ credentials }, serv) + const url = getMastodonSocketURI({ credentials }) state.mastoUserSocket = ProcessedWS({ url, diff --git a/vite.config.js b/vite.config.js index 6cbba4a53..4c7b4f534 100644 --- a/vite.config.js +++ b/vite.config.js @@ -73,6 +73,7 @@ export default defineConfig(async ({ mode, command }) => { const settings = await getLocalDevSettings() const target = settings.target || 'http://localhost:4000/' const origin = settings.origin || target + const targetSW = target.replace(/^http/,'ws') const transformSW = getTransformSWSettings(settings) const proxy = { '/api': { @@ -80,6 +81,7 @@ export default defineConfig(async ({ mode, command }) => { changeOrigin: true, cookieDomainRewrite: 'localhost', ws: true, + rewriteWsOrigin: true, }, '/auth': { // Mastodon password reset lives here @@ -98,20 +100,21 @@ export default defineConfig(async ({ mode, command }) => { changeOrigin: true, cookieDomainRewrite: 'localhost', }, - '/socket': { - target, - changeOrigin: true, - cookieDomainRewrite: 'localhost', - ws: true, - headers: { - Origin: origin, - }, - }, '/oauth': { target, changeOrigin: true, cookieDomainRewrite: 'localhost', }, + '/socket': { + target: targetSW, + changeOrigin: true, + cookieDomainRewrite: 'localhost', + rewriteWsOrigin: true, + ws: true, + headers: { + Origin: origin, + }, + }, } const swSrc = 'src/sw.js' From e7e615565c75641090e6541784ef6af9515c4911 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 24 Jun 2026 19:41:28 +0300 Subject: [PATCH 78/86] lint --- src/api/public.js | 2 -- src/components/chat/chat.js | 4 +--- src/components/conversation/conversation.js | 9 ++------- src/modules/api.js | 5 +---- vite.config.js | 2 +- 5 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index f4ae778bc..cdb3a1305 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -3,8 +3,6 @@ import { concat, each, last, map } from 'lodash' import { paramsString, promisedRequest } from './helpers.js' import { - parseAttachment, - parseChat, parseLinkHeaderPagination, parseNotification, parseSource, diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index acd1f1c5f..16a03ab1d 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -23,9 +23,7 @@ import { getOrCreateChat, sendChatMessage, } from 'src/api/chats.js' -import { - WSConnectionStatus, -} from 'src/api/websocket.js' +import { WSConnectionStatus } from 'src/api/websocket.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons' diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index af78b7383..54bcbd373 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -10,13 +10,8 @@ import { useInterfaceStore } from 'src/stores/interface' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { - fetchConversation, - fetchStatus, -} from 'src/api/public.js' -import { - WSConnectionStatus, -} from 'src/api/websocket.js' +import { fetchConversation, fetchStatus } from 'src/api/public.js' +import { WSConnectionStatus } from 'src/api/websocket.js' import { library } from '@fortawesome/fontawesome-svg-core' import { diff --git a/src/modules/api.js b/src/modules/api.js index 9d6426c2e..ca1aacf05 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -8,9 +8,7 @@ import { useInterfaceStore } from 'src/stores/interface.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useShoutStore } from 'src/stores/shout.js' -import { - fetchTimeline, -} from 'src/api/public.js' +import { fetchTimeline } from 'src/api/public.js' import { getMastodonSocketURI, ProcessedWS, @@ -99,7 +97,6 @@ const api = { const { state, commit, dispatch, rootState } = store const timelineData = rootState.statuses.timelines.friends - const serv = useInstanceStore().server.replace('http', 'ws') const credentials = useOAuthStore().token const url = getMastodonSocketURI({ credentials }) diff --git a/vite.config.js b/vite.config.js index 4c7b4f534..64859ebb1 100644 --- a/vite.config.js +++ b/vite.config.js @@ -73,7 +73,7 @@ export default defineConfig(async ({ mode, command }) => { const settings = await getLocalDevSettings() const target = settings.target || 'http://localhost:4000/' const origin = settings.origin || target - const targetSW = target.replace(/^http/,'ws') + const targetSW = target.replace(/^http/, 'ws') const transformSW = getTransformSWSettings(settings) const proxy = { '/api': { From e80b049904ed7276e192dfdd9e5bc711c1ae7219 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 24 Jun 2026 19:42:54 +0300 Subject: [PATCH 79/86] lightening --- src/api/public.js | 2 -- src/api/user.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index cdb3a1305..bd3f56fce 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -1,5 +1,3 @@ -import { concat, each, last, map } from 'lodash' - import { paramsString, promisedRequest } from './helpers.js' import { diff --git a/src/api/user.js b/src/api/user.js index 127debdea..ce41c0c21 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -1,4 +1,4 @@ -import { concat, each, last, map } from 'lodash' +import { concat, last } from 'lodash' import { paramsString, promisedRequest } from './helpers.js' import { fetchFriends, MASTODON_STATUS_URL } from './public.js' From aaa3f64a0252d0bc19cb73674bdb7a5b2374f879 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 24 Jun 2026 19:43:58 +0300 Subject: [PATCH 80/86] missing file --- src/api/websocket.js | 176 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 src/api/websocket.js diff --git a/src/api/websocket.js b/src/api/websocket.js new file mode 100644 index 000000000..44f3d391a --- /dev/null +++ b/src/api/websocket.js @@ -0,0 +1,176 @@ +import { paramsString, promisedRequest } from './helpers.js' + +import { + parseChat, + parseNotification, + parseStatus, +} from 'src/services/entity_normalizer/entity_normalizer.service.js' + +const MASTODON_STREAMING = ({ accessToken, stream }) => + `/api/v1/streaming${paramsString({ accessToken, stream })}` + +export const getMastodonSocketURI = ({ credentials, stream }) => { + return MASTODON_STREAMING({ accessToken: credentials, stream }) +} + +const MASTODON_STREAMING_EVENTS = new Set([ + 'update', + 'notification', + 'delete', + 'filters_changed', + 'status.update', +]) + +const PLEROMA_STREAMING_EVENTS = new Set([ + 'pleroma:chat_update', + 'pleroma:respond', +]) + +// A thin wrapper around WebSocket API that allows adding a pre-processor to it +// Uses EventTarget and a CustomEvent to proxy events +export const ProcessedWS = ({ + url, + preprocessor = handleMastoWS, + id = 'Unknown', + credentials, +}) => { + const eventTarget = new EventTarget() + const socket = new WebSocket(url) + if (!socket) throw new Error(`Failed to create socket ${id}`) + const proxy = (original, eventName, processor = (a) => a) => { + original.addEventListener(eventName, (eventData) => { + eventTarget.dispatchEvent( + new CustomEvent(eventName, { detail: processor(eventData) }), + ) + }) + } + socket.addEventListener('open', (wsEvent) => { + console.debug(`[WS][${id}] Socket connected`, wsEvent) + if (credentials) { + socket.send( + JSON.stringify({ + type: 'pleroma:authenticate', + token: credentials, + }), + ) + } + }) + socket.addEventListener('error', (wsEvent) => { + console.debug(`[WS][${id}] Socket errored`, wsEvent) + }) + socket.addEventListener('close', (wsEvent) => { + console.debug( + `[WS][${id}] Socket disconnected with code ${wsEvent.code}`, + wsEvent, + ) + }) + // Commented code reason: very spammy, uncomment to enable message debug logging + /* + socket.addEventListener('message', (wsEvent) => { + console.debug( + `[WS][${id}] Message received`, + wsEvent + ) + }) + /**/ + + const onAuthenticated = () => { + eventTarget.dispatchEvent(new CustomEvent('pleroma:authenticated')) + } + + proxy(socket, 'open') + proxy(socket, 'close') + proxy(socket, 'message', (event) => preprocessor(event, { onAuthenticated })) + proxy(socket, 'error') + + // 1000 = Normal Closure + eventTarget.close = () => { + socket.close(1000, 'Shutting down socket') + } + eventTarget.getState = () => socket.readyState + eventTarget.subscribe = (stream, args = {}) => { + console.debug(`[WS][${id}] Subscribing to stream ${stream} with args`, args) + socket.send( + JSON.stringify({ + type: 'subscribe', + stream, + ...args, + }), + ) + } + eventTarget.unsubscribe = (stream, args = {}) => { + console.debug( + `[WS][${id}] Unsubscribing from stream ${stream} with args`, + args, + ) + socket.send( + JSON.stringify({ + type: 'unsubscribe', + stream, + ...args, + }), + ) + } + + return eventTarget +} + +export const handleMastoWS = ( + wsEvent, + { + onAuthenticated = () => { + /* no-op */ + }, + } = {}, +) => { + const { data } = wsEvent + if (!data) return + const parsedEvent = JSON.parse(data) + const { event, payload } = parsedEvent + if ( + MASTODON_STREAMING_EVENTS.has(event) || + PLEROMA_STREAMING_EVENTS.has(event) + ) { + // MastoBE and PleromaBE both send payload for delete as a PLAIN string + if (event === 'delete') { + return { event, id: payload } + } + const data = payload ? JSON.parse(payload) : null + if (event === 'pleroma:respond') { + if (data.type === 'pleroma:authenticate') { + if (data.result === 'success') { + console.debug('[WS] Successfully authenticated') + onAuthenticated() + } else { + if (data.error === 'already_authenticated') { + onAuthenticated() + } else { + console.error('[WS] Unable to authenticate:', data.error) + wsEvent.target.close() + } + } + } + return null + } else if (event === 'update') { + return { event, status: parseStatus(data) } + } else if (event === 'status.update') { + return { event, status: parseStatus(data) } + } else if (event === 'notification') { + return { event, notification: parseNotification(data) } + } else if (event === 'pleroma:chat_update') { + return { event, chatUpdate: parseChat(data) } + } + } else { + console.warn('Unknown event', wsEvent) + return null + } +} + +export const WSConnectionStatus = Object.freeze({ + JOINED: 1, + CLOSED: 2, + ERROR: 3, + DISABLED: 4, + STARTING: 5, + STARTING_INITIAL: 6, +}) From 0908d2bb65e1723fc18d2dd43a44fe864e627e8c Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 24 Jun 2026 19:48:54 +0300 Subject: [PATCH 81/86] playwright --- .gitlab-ci.yml | 2 +- .woodpecker/test-e2e.yaml | 2 +- .woodpecker/test.yaml | 2 +- docker/e2e/Dockerfile.e2e | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 692e71fd5..6c607eeea 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -73,7 +73,7 @@ test: e2e-pleroma: stage: test - image: mcr.microsoft.com/playwright:v1.57.0-jammy + image: mcr.microsoft.com/playwright:v1.61.0-jammy services: - name: postgres:15-alpine alias: db diff --git a/.woodpecker/test-e2e.yaml b/.woodpecker/test-e2e.yaml index a5289bd78..2a7d1511d 100644 --- a/.woodpecker/test-e2e.yaml +++ b/.woodpecker/test-e2e.yaml @@ -25,7 +25,7 @@ variables: steps: test: - image: mcr.microsoft.com/playwright:v1.57.0-jammy + image: mcr.microsoft.com/playwright:v1.61.0-jammy entrypoint: *script_file_entrypoint environment: APT_CACHE_DIR: apt-cache diff --git a/.woodpecker/test.yaml b/.woodpecker/test.yaml index acc48aacc..89f6f2f93 100644 --- a/.woodpecker/test.yaml +++ b/.woodpecker/test.yaml @@ -25,7 +25,7 @@ variables: steps: test: - image: mcr.microsoft.com/playwright:v1.57.0-jammy + image: mcr.microsoft.com/playwright:v1.61.0-jammy environment: APT_CACHE_DIR: apt-cache DEBIAN_FRONTEND: noninteractive diff --git a/docker/e2e/Dockerfile.e2e b/docker/e2e/Dockerfile.e2e index e84359ceb..7e3fbfbf1 100644 --- a/docker/e2e/Dockerfile.e2e +++ b/docker/e2e/Dockerfile.e2e @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright:v1.57.0-jammy +FROM mcr.microsoft.com/playwright:v1.61.0-jammy WORKDIR /app From 495e6143d4acf5b94981f74fcfb412e13538cae5 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 25 Jun 2026 13:28:05 +0300 Subject: [PATCH 82/86] split timelines part into its own file --- src/api/public.js | 193 +---------------- src/api/timelines.js | 198 ++++++++++++++++++ src/modules/api.js | 2 +- .../notifications_fetcher.service.js | 2 +- .../timeline_fetcher.service.js | 2 +- 5 files changed, 202 insertions(+), 195 deletions(-) create mode 100644 src/api/timelines.js diff --git a/src/api/public.js b/src/api/public.js index bd3f56fce..cf8d082e2 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -1,8 +1,7 @@ import { paramsString, promisedRequest } from './helpers.js' +import { MASTODON_USER_TIMELINE_URL } from './timelines.js' import { - parseLinkHeaderPagination, - parseNotification, parseSource, parseStatus, parseUser, @@ -15,15 +14,6 @@ const MASTODON_REGISTRATION_URL = '/api/v1/accounts' const MASTODON_PASSWORD_RESET_URL = ({ email }) => `/auth/password${paramsString({ email })}` -const MASTODON_USER_NOTIFICATIONS_URL = ({ - minId, - sinceId, - maxId, - limit, - includeTypes, - replyVisibility, -}) => - `/api/v1/notifications${paramsString({ minId, sinceId, maxId, limit, includeTypes, replyVisibility })}` const MASTODON_FOLLOWING_URL = ( id, { minId, maxId, sinceId, limit, withRelationships }, @@ -35,84 +25,6 @@ const MASTODON_FOLLOWERS_URL = ( ) => `/api/v1/accounts/${id}/followers${paramsString({ minId, maxId, sinceId, limit, withRelationships })}` -const MASTODON_USER_HOME_TIMELINE_URL = ({ - minId, - sinceId, - maxId, - limit, - replyVisibility, -}) => - `/api/v1/timelines/home${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const MASTODON_LIST_TIMELINE_URL = ( - id, - { minId, sinceId, maxId, limit, replyVisibility }, -) => - `/api/v1/timelines/list/${id}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = ({ - minId, - sinceId, - maxId, - limit, - replyVisibility, -}) => - `/api/v1/timelines/direct${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const MASTODON_PUBLIC_TIMELINE = ({ - minId, - sinceId, - maxId, - limit, - replyVisibility, - local, - remote, - onlyMedia, -}) => - `/api/v1/timelines/public${paramsString({ minId, sinceId, maxId, limit, replyVisibility, local, remote, onlyMedia })}` -const MASTODON_TAG_TIMELINE_URL = ( - tag, - { minId, sinceId, maxId, limit, replyVisibility }, -) => - `/api/v1/timelines/tag/${tag}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const MASTODON_USER_TIMELINE_URL = ( - id, - { minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia }, -) => - `/api/v1/accounts/${id}/statuses${paramsString({ minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia })}` -const MASTODON_USER_FAVORITES_TIMELINE_URL = ({ - minId, - sinceId, - maxId, - limit, - replyVisibility, -}) => - `/api/v1/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const MASTODON_BOOKMARK_TIMELINE_URL = ({ - minId, - sinceId, - maxId, - limit, - replyVisibility, - folderId, -}) => - `/api/v1/bookmarks${paramsString({ minId, sinceId, maxId, limit, replyVisibility, folderId })}` -const PLEROMA_STATUS_QUOTES_URL = ( - id, - { minId, sinceId, maxId, limit, replyVisibility }, -) => - `/api/v1/pleroma/statuses/${id}/quotes${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const PLEROMA_USER_FAVORITES_TIMELINE_URL = ( - id, - { minId, sinceId, maxId, limit, replyVisibility }, -) => - `/api/v1/pleroma/accounts/${id}/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` -const AKKOMA_BUBBLE_TIMELINE_URL = ({ - minId, - sinceId, - maxId, - limit, - replyVisibility, -}) => - `/api/v1/timelines/bubble${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` - export const MASTODON_STATUS_URL = (id) => `/api/v1/statuses/${id}` const MASTODON_STATUS_CONTEXT_URL = (id) => `/api/v1/statuses/${id}/context` const MASTODON_STATUS_SOURCE_URL = (id) => `/api/v1/statuses/${id}/source` @@ -275,109 +187,6 @@ export const fetchStatusHistory = ({ status, credentials }) => }) }) -export const fetchTimeline = ({ - timeline, - credentials, - sinceId, - minId, - maxId, - userId, - listId, - statusId, - tag, - withMuted, - replyVisibility = 'all', - includeTypes = [], - bookmarkFolderId, -}) => { - const timelineUrls = { - friends: MASTODON_USER_HOME_TIMELINE_URL, - public: MASTODON_PUBLIC_TIMELINE, - publicAndExternal: MASTODON_PUBLIC_TIMELINE, - dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL, - user: MASTODON_USER_TIMELINE_URL, - media: MASTODON_USER_TIMELINE_URL, - list: MASTODON_LIST_TIMELINE_URL, - favorites: MASTODON_USER_FAVORITES_TIMELINE_URL, - publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL, - bookmarks: MASTODON_BOOKMARK_TIMELINE_URL, - bubble: AKKOMA_BUBBLE_TIMELINE_URL, - tag: MASTODON_TAG_TIMELINE_URL, - quotes: PLEROMA_STATUS_QUOTES_URL, - - notifications: MASTODON_USER_NOTIFICATIONS_URL, - } - const urlFunc = timelineUrls[timeline] - - const twoArgs = new Set([ - 'user', - 'media', - 'list', - 'publicFavorites', - 'tag', - 'quotes', - ]) - - const params = { - minId, - sinceId, - maxId, - limit: 20, - } - - const id = (() => { - switch (timeline) { - case 'user': - case 'media': - return userId - case 'list': - return listId - case 'quotes': - return statusId - case 'tag': - return tag - } - })() - - const isNotifications = timeline === 'notifications' - - if (timeline === 'media') { - params.onlyMedia = true - } - if (timeline === 'public') { - params.local = true - } - if (timeline !== 'favorites' && timeline !== 'bookmarks') { - params.withMuted = withMuted - } - if (replyVisibility !== 'all') { - params.replyVisibility = replyVisibility - } - if (timeline === 'bookmarks' && bookmarkFolderId) { - params.folderId = bookmarkFolderId - } - - if (isNotifications && includeTypes.length > 0) { - params.includeTypes = includeTypes - } - - const url = twoArgs.has(timeline) ? urlFunc(id, params) : urlFunc(params) - return promisedRequest({ url, credentials }).then((result) => { - const pagination = parseLinkHeaderPagination( - result.response.headers.get('Link'), - { - flakeId: timeline !== 'bookmarks' && timeline !== 'notifications', - }, - ) - - return { - ...result, - data: result.data.map(isNotifications ? parseNotification : parseStatus), - pagination, - } - }) -} - export const listEmojiPacks = ({ page, pageSize, credentials }) => promisedRequest({ url: EMOJI_PACKS_URL(page, pageSize), diff --git a/src/api/timelines.js b/src/api/timelines.js new file mode 100644 index 000000000..0679b1eec --- /dev/null +++ b/src/api/timelines.js @@ -0,0 +1,198 @@ +import { paramsString, promisedRequest } from './helpers.js' + +import { + parseLinkHeaderPagination, + parseNotification, + parseStatus, +} from 'src/services/entity_normalizer/entity_normalizer.service.js' + +const MASTODON_USER_HOME_TIMELINE_URL = ({ + minId, + sinceId, + maxId, + limit, + replyVisibility, +}) => + `/api/v1/timelines/home${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +const MASTODON_LIST_TIMELINE_URL = ( + id, + { minId, sinceId, maxId, limit, replyVisibility }, +) => + `/api/v1/timelines/list/${id}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = ({ + minId, + sinceId, + maxId, + limit, + replyVisibility, +}) => + `/api/v1/timelines/direct${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +const MASTODON_PUBLIC_TIMELINE = ({ + minId, + sinceId, + maxId, + limit, + replyVisibility, + local, + remote, + onlyMedia, +}) => + `/api/v1/timelines/public${paramsString({ minId, sinceId, maxId, limit, replyVisibility, local, remote, onlyMedia })}` +const MASTODON_TAG_TIMELINE_URL = ( + tag, + { minId, sinceId, maxId, limit, replyVisibility }, +) => + `/api/v1/timelines/tag/${tag}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +export const MASTODON_USER_TIMELINE_URL = ( + id, + { minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia }, +) => + `/api/v1/accounts/${id}/statuses${paramsString({ minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia })}` +const MASTODON_USER_FAVORITES_TIMELINE_URL = ({ + minId, + sinceId, + maxId, + limit, + replyVisibility, +}) => + `/api/v1/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +const MASTODON_BOOKMARK_TIMELINE_URL = ({ + minId, + sinceId, + maxId, + limit, + replyVisibility, + folderId, +}) => + `/api/v1/bookmarks${paramsString({ minId, sinceId, maxId, limit, replyVisibility, folderId })}` +const PLEROMA_STATUS_QUOTES_URL = ( + id, + { minId, sinceId, maxId, limit, replyVisibility }, +) => + `/api/v1/pleroma/statuses/${id}/quotes${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +const PLEROMA_USER_FAVORITES_TIMELINE_URL = ( + id, + { minId, sinceId, maxId, limit, replyVisibility }, +) => + `/api/v1/pleroma/accounts/${id}/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` +const AKKOMA_BUBBLE_TIMELINE_URL = ({ + minId, + sinceId, + maxId, + limit, + replyVisibility, +}) => + `/api/v1/timelines/bubble${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}` + +const MASTODON_USER_NOTIFICATIONS_URL = ({ + minId, + sinceId, + maxId, + limit, + includeTypes, + replyVisibility, +}) => + `/api/v1/notifications${paramsString({ minId, sinceId, maxId, limit, includeTypes, replyVisibility })}` + +export const fetchTimeline = ({ + timeline, + credentials, + sinceId, + minId, + maxId, + userId, + listId, + statusId, + tag, + withMuted, + replyVisibility = 'all', + includeTypes = [], + bookmarkFolderId, +}) => { + const timelineUrls = { + friends: MASTODON_USER_HOME_TIMELINE_URL, + public: MASTODON_PUBLIC_TIMELINE, + publicAndExternal: MASTODON_PUBLIC_TIMELINE, + dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL, + user: MASTODON_USER_TIMELINE_URL, + media: MASTODON_USER_TIMELINE_URL, + list: MASTODON_LIST_TIMELINE_URL, + favorites: MASTODON_USER_FAVORITES_TIMELINE_URL, + publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL, + bookmarks: MASTODON_BOOKMARK_TIMELINE_URL, + bubble: AKKOMA_BUBBLE_TIMELINE_URL, + tag: MASTODON_TAG_TIMELINE_URL, + quotes: PLEROMA_STATUS_QUOTES_URL, + + notifications: MASTODON_USER_NOTIFICATIONS_URL, + } + const urlFunc = timelineUrls[timeline] + + const twoArgs = new Set([ + 'user', + 'media', + 'list', + 'publicFavorites', + 'tag', + 'quotes', + ]) + + const params = { + minId, + sinceId, + maxId, + limit: 20, + } + + const id = (() => { + switch (timeline) { + case 'user': + case 'media': + return userId + case 'list': + return listId + case 'quotes': + return statusId + case 'tag': + return tag + } + })() + + const isNotifications = timeline === 'notifications' + + if (timeline === 'media') { + params.onlyMedia = true + } + if (timeline === 'public') { + params.local = true + } + if (timeline !== 'favorites' && timeline !== 'bookmarks') { + params.withMuted = withMuted + } + if (replyVisibility !== 'all') { + params.replyVisibility = replyVisibility + } + if (timeline === 'bookmarks' && bookmarkFolderId) { + params.folderId = bookmarkFolderId + } + + if (isNotifications && includeTypes.length > 0) { + params.includeTypes = includeTypes + } + + const url = twoArgs.has(timeline) ? urlFunc(id, params) : urlFunc(params) + return promisedRequest({ url, credentials }).then((result) => { + const pagination = parseLinkHeaderPagination( + result.response.headers.get('Link'), + { + flakeId: timeline !== 'bookmarks' && timeline !== 'notifications', + }, + ) + + return { + ...result, + data: result.data.map(isNotifications ? parseNotification : parseStatus), + pagination, + } + }) +} diff --git a/src/modules/api.js b/src/modules/api.js index ca1aacf05..03d184160 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -8,7 +8,7 @@ import { useInterfaceStore } from 'src/stores/interface.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useShoutStore } from 'src/stores/shout.js' -import { fetchTimeline } from 'src/api/public.js' +import { fetchTimeline } from 'src/api/timelines.js' import { getMastodonSocketURI, ProcessedWS, diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index f787b92e4..46101db7d 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -5,7 +5,7 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j import { useInterfaceStore } from 'src/stores/interface.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { fetchTimeline } from 'src/api/public.js' +import { fetchTimeline } from 'src/api/timelines.js' const update = ({ store, notifications, older }) => { store.dispatch('addNewNotifications', { notifications, older }) diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index ff75ea704..c006ae172 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -7,7 +7,7 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j import { useInterfaceStore } from 'src/stores/interface.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { fetchTimeline } from 'src/api/public.js' +import { fetchTimeline } from 'src/api/timelines.js' const update = ({ store, From 893ab9d73b514a43587fa4ca6bf714cc6bc85e89 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 25 Jun 2026 13:46:27 +0300 Subject: [PATCH 83/86] cleanup unused imports --- biome.json | 1 + build/sw_plugin.js | 1 - src/App.js | 3 --- src/api/admin.js | 2 -- src/api/helpers.js | 2 +- src/api/oauth.js | 2 -- src/api/public.js | 1 - src/api/websocket.js | 2 +- src/boot/after_store.js | 1 - src/components/attachment/attachment.js | 1 - src/components/chat_message/chat_message.js | 3 +-- src/components/chat_new/chat_new.js | 2 -- src/components/chat_title/chat_title.js | 2 -- src/components/emoji_input/suggestor.js | 2 -- src/components/emoji_picker/emoji_picker.js | 1 - src/components/lists_menu/lists_menu_content.js | 1 - src/components/login_form/login_form.js | 2 +- src/components/media_modal/media_modal.js | 1 - src/components/mention_link/mention_link.js | 3 +-- .../moderation_tools/moderation_tools.js | 1 - .../post_status_form/post_status_form.js | 1 - .../post_status_modal/post_status_modal.js | 1 - src/components/quote/quote.js | 2 -- .../settings_modal/admin_tabs/admin_user_card.js | 2 -- .../settings_modal/admin_tabs/emoji_tab.js | 2 +- .../settings_modal/admin_tabs/users_tab.js | 2 -- src/components/settings_modal/helpers/setting.js | 1 - .../helpers/vertical_tab_switcher.jsx | 5 ++--- .../settings_modal_admin_content.js | 1 - src/components/settings_modal/tabs/general_tab.js | 1 - .../settings_modal/tabs/mutes_and_blocks_tab.js | 2 +- src/components/settings_modal/tabs/posts_tab.js | 1 - .../settings_modal/tabs/security_tab/mfa.js | 1 - .../settings_modal/tabs/security_tab/mfa_totp.js | 2 -- .../tabs/security_tab/security_tab.js | 1 - src/components/side_drawer/side_drawer.js | 1 - src/components/status_content/status_content.js | 3 +-- src/components/status_popover/status_popover.js | 1 - src/components/sticker_picker/sticker_picker.js | 1 - src/components/tab_switcher/tab_switcher.jsx | 5 +---- src/components/user_card/user_card.js | 1 - .../user_list_popover/user_list_popover.js | 2 -- src/components/user_popover/user_popover.js | 1 - src/components/user_profile/user_profile.js | 2 -- .../user_profile/user_profile_admin_view.js | 3 --- src/components/who_to_follow/who_to_follow.js | 1 - src/lib/language.js | 2 -- src/modules/api.js | 1 - src/modules/default_config_state.js | 2 +- .../notifications_fetcher.service.js | 1 - .../timeline_fetcher/timeline_fetcher.service.js | 1 - src/stores/instance.js | 2 +- src/stores/local_config.js | 3 --- src/stores/sync_config.js | 1 - src/stores/user_highlight.js | 15 --------------- .../entity_normalizer/entity_normalizer.spec.js | 1 - test/unit/specs/stores/lists.spec.js | 2 +- test/unit/specs/stores/oauth.spec.js | 2 -- test/unit/specs/stores/user_highlight.spec.js | 2 -- vite.config.js | 1 - 60 files changed, 15 insertions(+), 101 deletions(-) diff --git a/biome.json b/biome.json index 6a464a0e5..5ef6f79ee 100644 --- a/biome.json +++ b/biome.json @@ -46,6 +46,7 @@ "noUnusedLabels": "error", "noUnusedPrivateClassMembers": "error", "noUnusedVariables": "error", + "noUnusedImports": "error", "useIsNan": "error", "useValidForDirection": "error", "useValidTypeof": "error", diff --git a/build/sw_plugin.js b/build/sw_plugin.js index f278d55f6..2f0a4819d 100644 --- a/build/sw_plugin.js +++ b/build/sw_plugin.js @@ -1,4 +1,3 @@ -import { readFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { exactRegex } from '@rolldown/pluginutils' diff --git a/src/App.js b/src/App.js index ea2271a75..7de7a0696 100644 --- a/src/App.js +++ b/src/App.js @@ -21,9 +21,6 @@ import { useInterfaceStore } from 'src/stores/interface.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useShoutStore } from 'src/stores/shout.js' -import messages from 'src/i18n/messages' -import localeService from 'src/services/locale/locale.service.js' - // Helper to unwrap reactive proxies window.toValue = (x) => JSON.parse(JSON.stringify(x)) diff --git a/src/api/admin.js b/src/api/admin.js index 975a7386e..79eac9bd6 100644 --- a/src/api/admin.js +++ b/src/api/admin.js @@ -1,7 +1,5 @@ import { promisedRequest } from './helpers.js' -import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' - const REPORTS = '/api/v1/pleroma/admin/reports' const CONFIG_URL = '/api/v1/pleroma/admin/config' const DESCRIPTIONS_URL = '/api/v1/pleroma/admin/config/descriptions' diff --git a/src/api/helpers.js b/src/api/helpers.js index 5fb11a183..40b879232 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -1,6 +1,6 @@ import { snakeCase } from 'lodash' -import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' +import { StatusCodeError } from 'src/services/errors/errors' export const paramsString = (params = {}) => { if (params == null || params === undefined) return '' diff --git a/src/api/oauth.js b/src/api/oauth.js index b774b3f6c..f0fd2950d 100644 --- a/src/api/oauth.js +++ b/src/api/oauth.js @@ -1,5 +1,3 @@ -import { reduce } from 'lodash' - import { paramsString, promisedRequest } from './helpers.js' const REDIRECT_URI = `${window.location.origin}/oauth-callback` diff --git a/src/api/public.js b/src/api/public.js index cf8d082e2..7ce683dde 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -6,7 +6,6 @@ import { parseStatus, parseUser, } from 'src/services/entity_normalizer/entity_normalizer.service.js' -import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' const MASTODON_SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' diff --git a/src/api/websocket.js b/src/api/websocket.js index 44f3d391a..d952372e5 100644 --- a/src/api/websocket.js +++ b/src/api/websocket.js @@ -1,4 +1,4 @@ -import { paramsString, promisedRequest } from './helpers.js' +import { paramsString } from './helpers.js' import { parseChat, diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 95fb3e9ea..0b4825ca0 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -28,7 +28,6 @@ import { } from '../services/window_utils/window_utils' import routes from './routes' -import { useAnnouncementsStore } from 'src/stores/announcements' import { useAuthFlowStore } from 'src/stores/auth_flow' import { useEmojiStore } from 'src/stores/emoji.js' import { useI18nStore } from 'src/stores/i18n' diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index 4b733258f..d5bce12a7 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -2,7 +2,6 @@ import { mapState } from 'pinia' import { defineAsyncComponent } from 'vue' import Popover from 'src/components/popover/popover.vue' -import VideoAttachment from 'src/components/video_attachment/video_attachment.vue' import nsfwImage from '../../assets/nsfw.png' import { useInstanceStore } from 'src/stores/instance.js' diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js index ba06172ed..2066a8194 100644 --- a/src/components/chat_message/chat_message.js +++ b/src/components/chat_message/chat_message.js @@ -1,6 +1,5 @@ import { mapState as mapPiniaState } from 'pinia' -import { defineAsyncComponent } from 'vue' -import { mapGetters, mapState } from 'vuex' +import { mapState } from 'vuex' import Attachment from 'src/components/attachment/attachment.vue' import ChatMessageDate from 'src/components/chat_message_date/chat_message_date.vue' diff --git a/src/components/chat_new/chat_new.js b/src/components/chat_new/chat_new.js index ec2656e5c..0ee4e292b 100644 --- a/src/components/chat_new/chat_new.js +++ b/src/components/chat_new/chat_new.js @@ -5,8 +5,6 @@ import UserAvatar from 'src/components/user_avatar/user_avatar.vue' import { useOAuthStore } from 'src/stores/oauth.js' -import { chats } from 'src/api/chats.js' - import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons' diff --git a/src/components/chat_title/chat_title.js b/src/components/chat_title/chat_title.js index 52d4445b6..1d12384cc 100644 --- a/src/components/chat_title/chat_title.js +++ b/src/components/chat_title/chat_title.js @@ -1,5 +1,3 @@ -import { defineAsyncComponent } from 'vue' - import UserAvatar from 'src/components/user_avatar/user_avatar.vue' import UserPopover from 'src/components/user_popover/user_popover.vue' diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js index c9a6abcf1..fe8e9e105 100644 --- a/src/components/emoji_input/suggestor.js +++ b/src/components/emoji_input/suggestor.js @@ -1,5 +1,3 @@ -import { useEmojiStore } from 'src/stores/emoji.js' - /** * suggest - generates a suggestor function to be used by emoji-input * data: object providing source information for specific types of suggestions: diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index bc2bb092b..6889fb8b0 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -6,7 +6,6 @@ import Popover from 'src/components/popover/popover.vue' import { ensureFinalFallback } from '../../i18n/languages.js' import { useEmojiStore } from 'src/stores/emoji.js' -import { useInstanceStore } from 'src/stores/instance.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { library } from '@fortawesome/fontawesome-svg-core' diff --git a/src/components/lists_menu/lists_menu_content.js b/src/components/lists_menu/lists_menu_content.js index d96f14599..337ee4d4f 100644 --- a/src/components/lists_menu/lists_menu_content.js +++ b/src/components/lists_menu/lists_menu_content.js @@ -4,7 +4,6 @@ import { mapState } from 'vuex' import { getListEntries } from 'src/components/navigation/filter.js' import NavigationEntry from 'src/components/navigation/navigation_entry.vue' -import { useInstanceStore } from 'src/stores/instance.js' import { useListsStore } from 'src/stores/lists.js' export const ListsMenuContent = { diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index a00db03cf..a333e6970 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -1,4 +1,4 @@ -import { mapActions, mapState as mapPiniaState, mapStores } from 'pinia' +import { mapActions, mapState as mapPiniaState } from 'pinia' import { mapState } from 'vuex' import { useAuthFlowStore } from 'src/stores/auth_flow.js' diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index ad53b6e31..fe5994f7c 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -1,7 +1,6 @@ import { defineAsyncComponent } from 'vue' import Modal from 'src/components/modal/modal.vue' -import StillImage from 'src/components/still-image/still-image.vue' import GestureService from '../../services/gesture_service/gesture_service' import { useMediaViewerStore } from 'src/stores/media_viewer.js' diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js index 877e83c12..48f6d1d8e 100644 --- a/src/components/mention_link/mention_link.js +++ b/src/components/mention_link/mention_link.js @@ -1,6 +1,5 @@ import { mapState as mapPiniaState } from 'pinia' -import { defineAsyncComponent } from 'vue' -import { mapGetters, mapState } from 'vuex' +import { mapState } from 'vuex' import UnicodeDomainIndicator from 'src/components/unicode_domain_indicator/unicode_domain_indicator.vue' import UserAvatar from 'src/components/user_avatar/user_avatar.vue' diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js index b819c72aa..ca4852ab3 100644 --- a/src/components/moderation_tools/moderation_tools.js +++ b/src/components/moderation_tools/moderation_tools.js @@ -4,7 +4,6 @@ import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue' import Popover from 'src/components/popover/popover.vue' import { useAdminSettingsStore } from 'src/stores/admin_settings.js' -import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { library } from '@fortawesome/fontawesome-svg-core' diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index c564355f3..4d9c34546 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -1,7 +1,6 @@ import { debounce, map, reject, uniqBy } from 'lodash' import { mapActions, mapState } from 'pinia' import { defineAsyncComponent } from 'vue' -import { mapGetters } from 'vuex' import Attachment from 'src/components/attachment/attachment.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' diff --git a/src/components/post_status_modal/post_status_modal.js b/src/components/post_status_modal/post_status_modal.js index 6011281cf..973c2b1a5 100644 --- a/src/components/post_status_modal/post_status_modal.js +++ b/src/components/post_status_modal/post_status_modal.js @@ -1,5 +1,4 @@ import { get } from 'lodash' -import { defineAsyncComponent } from 'vue' import Modal from 'src/components/modal/modal.vue' import PostStatusForm from 'src/components/post_status_form/post_status_form.vue' diff --git a/src/components/quote/quote.js b/src/components/quote/quote.js index 3b771b3f2..439d3440a 100644 --- a/src/components/quote/quote.js +++ b/src/components/quote/quote.js @@ -1,5 +1,3 @@ -import { defineAsyncComponent } from 'vue' - import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' diff --git a/src/components/settings_modal/admin_tabs/admin_user_card.js b/src/components/settings_modal/admin_tabs/admin_user_card.js index a075db010..ba0f0e9f7 100644 --- a/src/components/settings_modal/admin_tabs/admin_user_card.js +++ b/src/components/settings_modal/admin_tabs/admin_user_card.js @@ -1,5 +1,3 @@ -import { defineAsyncComponent } from 'vue' - import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue' import ModerationTools from 'src/components/moderation_tools/moderation_tools.vue' diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js index f64128c57..56361587d 100644 --- a/src/components/settings_modal/admin_tabs/emoji_tab.js +++ b/src/components/settings_modal/admin_tabs/emoji_tab.js @@ -2,7 +2,7 @@ import Checkbox from 'components/checkbox/checkbox.vue' import Popover from 'components/popover/popover.vue' import Select from 'components/select/select.vue' import StillImage from 'components/still-image/still-image.vue' -import { assign, clone } from 'lodash' +import { clone } from 'lodash' import { defineAsyncComponent } from 'vue' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' diff --git a/src/components/settings_modal/admin_tabs/users_tab.js b/src/components/settings_modal/admin_tabs/users_tab.js index ab40c723a..68bc46aa2 100644 --- a/src/components/settings_modal/admin_tabs/users_tab.js +++ b/src/components/settings_modal/admin_tabs/users_tab.js @@ -1,5 +1,3 @@ -import { isEmpty } from 'lodash' - import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' import List from 'src/components/list/list.vue' diff --git a/src/components/settings_modal/helpers/setting.js b/src/components/settings_modal/helpers/setting.js index b9ff08bb5..3b76708fe 100644 --- a/src/components/settings_modal/helpers/setting.js +++ b/src/components/settings_modal/helpers/setting.js @@ -5,7 +5,6 @@ import LocalSettingIndicator from './local_setting_indicator.vue' import ModifiedIndicator from './modified_indicator.vue' import { useAdminSettingsStore } from 'src/stores/admin_settings.js' -import { useInstanceStore } from 'src/stores/instance.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useLocalConfigStore } from 'src/stores/local_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' diff --git a/src/components/settings_modal/helpers/vertical_tab_switcher.jsx b/src/components/settings_modal/helpers/vertical_tab_switcher.jsx index cbe1be158..61ed6bee1 100644 --- a/src/components/settings_modal/helpers/vertical_tab_switcher.jsx +++ b/src/components/settings_modal/helpers/vertical_tab_switcher.jsx @@ -1,8 +1,7 @@ // eslint-disable-next-line no-unused -import { throttle } from 'lodash' -import { mapState as mapPiniaState, mapState } from 'pinia' -import { Fragment, h } from 'vue' +import { mapState as mapPiniaState } from 'pinia' +import { Fragment } from 'vue' import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome' diff --git a/src/components/settings_modal/settings_modal_admin_content.js b/src/components/settings_modal/settings_modal_admin_content.js index fb2d26778..22ba0e16c 100644 --- a/src/components/settings_modal/settings_modal_admin_content.js +++ b/src/components/settings_modal/settings_modal_admin_content.js @@ -1,4 +1,3 @@ -import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import AuthTab from './admin_tabs/auth_tab.vue' import EmojiTab from './admin_tabs/emoji_tab.vue' import FederationTab from './admin_tabs/federation_tab.vue' diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index bed6f6500..3ab3846d2 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -8,7 +8,6 @@ import FloatSetting from '../helpers/float_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' -import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useLocalConfigStore } from 'src/stores/local_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js index 781412249..c45d6ad76 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js @@ -1,4 +1,4 @@ -import { get, isEmpty, map, reject } from 'lodash' +import { get, map, reject } from 'lodash' import Autosuggest from 'src/components/autosuggest/autosuggest.vue' import BlockCard from 'src/components/block_card/block_card.vue' diff --git a/src/components/settings_modal/tabs/posts_tab.js b/src/components/settings_modal/tabs/posts_tab.js index dbf284d93..640ba5084 100644 --- a/src/components/settings_modal/tabs/posts_tab.js +++ b/src/components/settings_modal/tabs/posts_tab.js @@ -6,7 +6,6 @@ import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' import { useLocalConfigStore } from 'src/stores/local_config.js' -import { useSyncConfigStore } from 'src/stores/sync_config.js' const PostsTab = { data() { diff --git a/src/components/settings_modal/tabs/security_tab/mfa.js b/src/components/settings_modal/tabs/security_tab/mfa.js index 20c28419d..1277b42ec 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa.js +++ b/src/components/settings_modal/tabs/security_tab/mfa.js @@ -1,5 +1,4 @@ import VueQrcode from '@chenfengyuan/vue-qrcode' -import { mapState } from 'vuex' import Confirm from './confirm.vue' import RecoveryCodes from './mfa_backup_codes.vue' diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.js b/src/components/settings_modal/tabs/security_tab/mfa_totp.js index bbe06c217..2c4c194bc 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa_totp.js +++ b/src/components/settings_modal/tabs/security_tab/mfa_totp.js @@ -1,5 +1,3 @@ -import { mapState } from 'vuex' - import Confirm from './confirm.vue' import { useOAuthStore } from 'src/stores/oauth.js' diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js index 45876f848..a2571bd54 100644 --- a/src/components/settings_modal/tabs/security_tab/security_tab.js +++ b/src/components/settings_modal/tabs/security_tab/security_tab.js @@ -2,7 +2,6 @@ import Checkbox from 'src/components/checkbox/checkbox.vue' import ProgressButton from 'src/components/progress_button/progress_button.vue' import Mfa from './mfa.vue' -import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthTokensStore } from 'src/stores/oauth_tokens' diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js index 840beeb7c..27e9a5249 100644 --- a/src/components/side_drawer/side_drawer.js +++ b/src/components/side_drawer/side_drawer.js @@ -1,5 +1,4 @@ import { mapActions, mapState } from 'pinia' -import { defineAsyncComponent } from 'vue' import { mapGetters } from 'vuex' import { USERNAME_ROUTES } from 'src/components/navigation/navigation.js' diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js index 0f02bb6fb..2186498ef 100644 --- a/src/components/status_content/status_content.js +++ b/src/components/status_content/status_content.js @@ -1,6 +1,5 @@ import { mapState as mapPiniaState } from 'pinia' -import { defineAsyncComponent } from 'vue' -import { mapGetters, mapState } from 'vuex' +import { mapState } from 'vuex' import Attachment from 'src/components/attachment/attachment.vue' import Gallery from 'src/components/gallery/gallery.vue' diff --git a/src/components/status_popover/status_popover.js b/src/components/status_popover/status_popover.js index fe0056305..95da91ba7 100644 --- a/src/components/status_popover/status_popover.js +++ b/src/components/status_popover/status_popover.js @@ -1,7 +1,6 @@ import { find } from 'lodash' import Popover from 'src/components/popover/popover.vue' -import Status from 'src/components/status/status.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' diff --git a/src/components/sticker_picker/sticker_picker.js b/src/components/sticker_picker/sticker_picker.js index 949d2ecf9..482aacb81 100644 --- a/src/components/sticker_picker/sticker_picker.js +++ b/src/components/sticker_picker/sticker_picker.js @@ -4,7 +4,6 @@ import statusPosterService from '../../services/status_poster/status_poster.serv import TabSwitcher from '../tab_switcher/tab_switcher.jsx' import { useEmojiStore } from 'src/stores/emoji.js' -import { useInstanceStore } from 'src/stores/instance.js' const StickerPicker = { components: { diff --git a/src/components/tab_switcher/tab_switcher.jsx b/src/components/tab_switcher/tab_switcher.jsx index b485b3ff8..a3c5b61cd 100644 --- a/src/components/tab_switcher/tab_switcher.jsx +++ b/src/components/tab_switcher/tab_switcher.jsx @@ -1,14 +1,11 @@ // eslint-disable-next-line no-unused -import { mapState } from 'pinia' -import { Fragment, h } from 'vue' +import { Fragment } from 'vue' import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome' import './tab_switcher.scss' -import { useInterfaceStore } from 'src/stores/interface.js' - const findFirstUsable = (slots) => slots.findIndex((_) => _.props) export default { diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 70fb057af..31a513130 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -22,7 +22,6 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j import { useInterfaceStore } from 'src/stores/interface' import { useMediaViewerStore } from 'src/stores/media_viewer' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { usePostStatusStore } from 'src/stores/post_status' import { useUserHighlightStore } from 'src/stores/user_highlight.js' diff --git a/src/components/user_list_popover/user_list_popover.js b/src/components/user_list_popover/user_list_popover.js index 8a4612690..7f9ba4ffb 100644 --- a/src/components/user_list_popover/user_list_popover.js +++ b/src/components/user_list_popover/user_list_popover.js @@ -1,5 +1,3 @@ -import { defineAsyncComponent } from 'vue' - import Popover from 'src/components/popover/popover.vue' import UnicodeDomainIndicator from 'src/components/unicode_domain_indicator/unicode_domain_indicator.vue' import UserAvatar from 'src/components/user_avatar/user_avatar.vue' diff --git a/src/components/user_popover/user_popover.js b/src/components/user_popover/user_popover.js index de354d676..67c75be50 100644 --- a/src/components/user_popover/user_popover.js +++ b/src/components/user_popover/user_popover.js @@ -1,5 +1,4 @@ import { mapState } from 'pinia' -import { defineAsyncComponent } from 'vue' import Popover from 'src/components/popover/popover.vue' import UserCard from 'src/components/user_card/user_card.vue' diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index d2d766a7f..eb1d8e068 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -1,5 +1,4 @@ import { get } from 'lodash' -import { mapState } from 'pinia' import FollowCard from 'src/components/follow_card/follow_card.vue' import List from 'src/components/list/list.vue' @@ -7,7 +6,6 @@ import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import Timeline from 'src/components/timeline/timeline.vue' import UserCard from 'src/components/user_card/user_card.vue' -import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' diff --git a/src/components/user_profile/user_profile_admin_view.js b/src/components/user_profile/user_profile_admin_view.js index 94092b155..da2c6c322 100644 --- a/src/components/user_profile/user_profile_admin_view.js +++ b/src/components/user_profile/user_profile_admin_view.js @@ -1,6 +1,3 @@ -import { get } from 'lodash' -import { mapState } from 'pinia' - import Checkbox from 'src/components/checkbox/checkbox.vue' import List from 'src/components/list/list.vue' import Status from 'src/components/status/status.vue' diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js index 6f00b3e5e..28b63c973 100644 --- a/src/components/who_to_follow/who_to_follow.js +++ b/src/components/who_to_follow/who_to_follow.js @@ -1,6 +1,5 @@ import FollowCard from 'src/components/follow_card/follow_card.vue' -import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' import { fetchUser, suggestions } from 'src/api/public.js' diff --git a/src/lib/language.js b/src/lib/language.js index af53eeffd..dc5121452 100644 --- a/src/lib/language.js +++ b/src/lib/language.js @@ -1,5 +1,3 @@ -import Cookies from 'js-cookie' - import { useEmojiStore } from 'src/stores/emoji.js' import { useI18nStore } from 'src/stores/i18n.js' diff --git a/src/modules/api.js b/src/modules/api.js index 03d184160..72d7fb4d7 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -2,7 +2,6 @@ import { Socket } from 'phoenix' import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' -import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useOAuthStore } from 'src/stores/oauth.js' diff --git a/src/modules/default_config_state.js b/src/modules/default_config_state.js index 6a970ef0f..ec190af28 100644 --- a/src/modules/default_config_state.js +++ b/src/modules/default_config_state.js @@ -1,4 +1,4 @@ -import { get, set } from 'lodash' +import { get } from 'lodash' const browserLocale = (navigator.language || 'en').split('-')[0] diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index 46101db7d..8530c468c 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -1,6 +1,5 @@ import { promiseInterval } from '../promise_interval/promise_interval.js' -import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index c006ae172..80dbc75d0 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -2,7 +2,6 @@ import { camelCase } from 'lodash' import { promiseInterval } from '../promise_interval/promise_interval.js' -import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' diff --git a/src/stores/instance.js b/src/stores/instance.js index ea3419e80..02edd1235 100644 --- a/src/stores/instance.js +++ b/src/stores/instance.js @@ -1,4 +1,4 @@ -import { get, set } from 'lodash' +import { set } from 'lodash' import { defineStore } from 'pinia' import { diff --git a/src/stores/local_config.js b/src/stores/local_config.js index 5b15c6ff1..786da657b 100644 --- a/src/stores/local_config.js +++ b/src/stores/local_config.js @@ -1,8 +1,5 @@ import { cloneDeep, set } from 'lodash' import { defineStore } from 'pinia' -import { toRaw } from 'vue' - -import { useInstanceStore } from 'src/stores/instance' import { LOCAL_DEFAULT_CONFIG, diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js index ccc4bbd4d..47c6978d5 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -20,7 +20,6 @@ import { toRaw } from 'vue' import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' -import { useInstanceStore } from 'src/stores/instance.js' import { useLocalConfigStore } from 'src/stores/local_config.js' import { useOAuthStore } from 'src/stores/oauth.js' diff --git a/src/stores/user_highlight.js b/src/stores/user_highlight.js index b056ca808..759ebd509 100644 --- a/src/stores/user_highlight.js +++ b/src/stores/user_highlight.js @@ -1,15 +1,11 @@ import { merge as _merge, - clamp, clone, cloneDeep, - findLastIndex, flatten, - get, groupBy, isEqual, takeRight, - uniqWith, } from 'lodash' import { defineStore } from 'pinia' import { toRaw } from 'vue' @@ -33,17 +29,6 @@ export const defaultState = { cache: null, } -export const _moveItemInArray = (array, value, movement) => { - const oldIndex = array.indexOf(value) - const newIndex = oldIndex + movement - const newArray = [...array] - // remove old - newArray.splice(oldIndex, 1) - // add new - newArray.splice(clamp(newIndex, 0, newArray.length + 1), 0, value) - return newArray -} - const _wrapData = (data, userName) => { return { ...data, diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js index a9cb3fd27..afd17e56b 100644 --- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js +++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js @@ -2,7 +2,6 @@ import mastoapidata from '../../../../fixtures/mastoapi.json' import { parseLinkHeaderPagination, - parseNotification, parseStatus, parseUser, } from 'src/services/entity_normalizer/entity_normalizer.service.js' diff --git a/test/unit/specs/stores/lists.spec.js b/test/unit/specs/stores/lists.spec.js index c71cce555..3720aa776 100644 --- a/test/unit/specs/stores/lists.spec.js +++ b/test/unit/specs/stores/lists.spec.js @@ -12,7 +12,7 @@ describe('The lists store', () => { let store beforeEach(() => { - createTestingPinia({ stubActions: false }) + setActivePinia(createTestingPinia({ stubActions: false })) store = useListsStore() }) diff --git a/test/unit/specs/stores/oauth.spec.js b/test/unit/specs/stores/oauth.spec.js index 828175c9f..6e61fea79 100644 --- a/test/unit/specs/stores/oauth.spec.js +++ b/test/unit/specs/stores/oauth.spec.js @@ -9,8 +9,6 @@ import { useOAuthStore } from 'src/stores/oauth.js' import { MASTODON_APP_URL, MASTODON_APP_VERIFY_URL, - OAUTH_MFA_CHALLENGE_URL, - OAUTH_REVOKE_URL, OAUTH_TOKEN_URL, } from 'src/api/oauth.js' diff --git a/test/unit/specs/stores/user_highlight.spec.js b/test/unit/specs/stores/user_highlight.spec.js index c46852256..f80143b6e 100644 --- a/test/unit/specs/stores/user_highlight.spec.js +++ b/test/unit/specs/stores/user_highlight.spec.js @@ -1,10 +1,8 @@ -import { cloneDeep } from 'lodash' import { createPinia, setActivePinia } from 'pinia' import { _getRecentData, _mergeHighlights, - _moveItemInArray, useUserHighlightStore, } from 'src/stores/user_highlight.js' diff --git a/vite.config.js b/vite.config.js index 64859ebb1..beff5fe76 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,6 +1,5 @@ import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' -import { DevTools } from '@vitejs/devtools' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' import { playwright } from '@vitest/browser-playwright' From 8becc0704b6932133b2af3a54c2d221b7d119fc3 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 25 Jun 2026 15:36:49 +0300 Subject: [PATCH 84/86] don't rethrow --- src/api/public.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index 7ce683dde..3817c0f82 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -151,9 +151,6 @@ export const fetchConversation = ({ id, credentials }) => descendants: result.data.descendants.map(parseStatus), }, })) - .catch((error) => { - throw new Error('Error fetching timeline', error) - }) export const fetchStatus = ({ id, credentials }) => promisedRequest({ @@ -161,9 +158,6 @@ export const fetchStatus = ({ id, credentials }) => credentials, }) .then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) - .catch((error) => { - throw new Error('Error fetching timeline', error) - }) export const fetchStatusSource = ({ id, credentials }) => promisedRequest({ @@ -171,9 +165,6 @@ export const fetchStatusSource = ({ id, credentials }) => credentials, }) .then(({ data, ...rest }) => ({ ...rest, data: parseSource(data) })) - .catch((error) => { - throw new Error('Error fetching timeline', error) - }) export const fetchStatusHistory = ({ status, credentials }) => promisedRequest({ @@ -281,9 +272,6 @@ export const search2 = ({ data.statuses = data.statuses.slice(0, limit).map((s) => parseStatus(s)) return { ...rest, data } }) - .catch((error) => { - throw new Error('Error fetching timeline', error) - }) } export const fetchKnownDomains = ({ credentials }) => From 8dae81e29df9cba3874bd179598f47fce157708e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 25 Jun 2026 23:24:52 +0300 Subject: [PATCH 85/86] lint --- src/api/public.js | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index 3817c0f82..e001ba749 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -142,29 +142,26 @@ export const fetchConversation = ({ id, credentials }) => promisedRequest({ url: MASTODON_STATUS_CONTEXT_URL(id), credentials, - }) - .then((result) => ({ - ...result, - data: { - ...result.data, - ancestors: result.data.ancestors.map(parseStatus), - descendants: result.data.descendants.map(parseStatus), - }, - })) + }).then((result) => ({ + ...result, + data: { + ...result.data, + ancestors: result.data.ancestors.map(parseStatus), + descendants: result.data.descendants.map(parseStatus), + }, + })) export const fetchStatus = ({ id, credentials }) => promisedRequest({ url: MASTODON_STATUS_URL(id), credentials, - }) - .then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) + }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) export const fetchStatusSource = ({ id, credentials }) => promisedRequest({ url: MASTODON_STATUS_SOURCE_URL(id), credentials, - }) - .then(({ data, ...rest }) => ({ ...rest, data: parseSource(data) })) + }).then(({ data, ...rest }) => ({ ...rest, data: parseSource(data) })) export const fetchStatusHistory = ({ status, credentials }) => promisedRequest({ @@ -266,12 +263,11 @@ export const search2 = ({ withRelationships: true, }), credentials, + }).then(({ data, ...rest }) => { + data.accounts = data.accounts.slice(0, limit).map((u) => parseUser(u)) + data.statuses = data.statuses.slice(0, limit).map((s) => parseStatus(s)) + return { ...rest, data } }) - .then(({ data, ...rest }) => { - data.accounts = data.accounts.slice(0, limit).map((u) => parseUser(u)) - data.statuses = data.statuses.slice(0, limit).map((s) => parseStatus(s)) - return { ...rest, data } - }) } export const fetchKnownDomains = ({ credentials }) => From 9b53879e7069b3d06221f9488a98b40b1480eb97 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 26 Jun 2026 14:42:01 +0300 Subject: [PATCH 86/86] self-review --- src/api/admin.js | 16 ---------------- src/api/user.js | 1 - .../post_status_form/post_status_form.js | 1 + .../user_reporting_modal/user_reporting_modal.js | 1 - src/services/errors/errors.js | 4 ++-- src/stores/oauth_tokens.js | 5 +++++ test/fixtures/worker.js | 3 --- test/unit/specs/stores/lists.spec.js | 5 ----- test/unit/specs/stores/oauth.spec.js | 1 - 9 files changed, 8 insertions(+), 29 deletions(-) diff --git a/src/api/admin.js b/src/api/admin.js index 79eac9bd6..67fb5038b 100644 --- a/src/api/admin.js +++ b/src/api/admin.js @@ -312,9 +312,6 @@ export const deleteAnnouncement = ({ id, credentials }) => }) export const setReportState = ({ id, state, credentials }) => { - // TODO: Can't use promisedRequest because on OK this does not return json - // See https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1322 - return promisedRequest({ url: REPORTS, credentials, @@ -328,19 +325,6 @@ export const setReportState = ({ id, state, credentials }) => { ], }, }) - .then((data) => { - if (data.status >= 500) { - throw Error(data.statusText) - } else if (data.status >= 400) { - return data.json() - } - return data - }) - .then((data) => { - if (data.errors) { - throw Error(data.errors[0].message) - } - }) } export const getInstanceDBConfig = ({ credentials }) => diff --git a/src/api/user.js b/src/api/user.js index ce41c0c21..b9035da77 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -842,7 +842,6 @@ export const getList = ({ listId, credentials }) => }) export const updateList = ({ listId, title, credentials }) => - console.log('PUT', MASTODON_LIST_URL(listId)) || promisedRequest({ url: MASTODON_LIST_URL(listId), diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 4d9c34546..6cb99a3b6 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -634,6 +634,7 @@ const PostStatusForm = { // Don't apply preview if not loading, because it means // user has closed the preview manually. if (!this.previewLoading) return + this.preview = data }) .catch((error) => { this.preview = { error } diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js index 9659022d4..017a17efa 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.js +++ b/src/components/user_reporting_modal/user_reporting_modal.js @@ -31,7 +31,6 @@ const UserReportingModal = { return !!this.$store.state.users.currentUser }, isOpen() { - console.log(this.reportModal) return this.isLoggedIn && this.reportModal.activated }, userId() { diff --git a/src/services/errors/errors.js b/src/services/errors/errors.js index a28f31775..ccbae9b3e 100644 --- a/src/services/errors/errors.js +++ b/src/services/errors/errors.js @@ -13,9 +13,9 @@ function humanizeErrors(errors) { export function StatusCodeError(statusCode, body, options, response) { this.name = 'StatusCodeError' this.statusCode = statusCode - this.statusText = body.error || body + this.statusText = body.error || body.errors || body this.details = JSON && JSON.stringify ? JSON.stringify(body) : body - this.errorData = body.error + this.errorData = body.error || body.errors this.message = this.statusCode + ' - ' + this.statusText this.error = body // legacy attribute this.options = options diff --git a/src/stores/oauth_tokens.js b/src/stores/oauth_tokens.js index 10aa371f9..4d18ffae7 100644 --- a/src/stores/oauth_tokens.js +++ b/src/stores/oauth_tokens.js @@ -4,6 +4,11 @@ import { useOAuthStore } from 'src/stores/oauth.js' import { fetchOAuthTokens, revokeOAuthToken } from 'src/api/user.js' +/* Just to clear the confusion: + * OAuth Store is responsible for user authentication + * OAuth Tokens Store is responsible for *managing* all of the user's tokens, + * i.e. for current and other clients + */ export const useOAuthTokensStore = defineStore('oauthTokens', { state: () => ({ tokens: [], diff --git a/test/fixtures/worker.js b/test/fixtures/worker.js index e0db13ffc..e6ed89dc9 100644 --- a/test/fixtures/worker.js +++ b/test/fixtures/worker.js @@ -2,7 +2,4 @@ import { setupWorker } from 'msw/browser' export const worker = setupWorker() -console.log('=============== TEST ===============') -console.log(window.__test__) -console.log('=============== TEST ===============') window.__test__ = window.__test__ || 'TEST' diff --git a/test/unit/specs/stores/lists.spec.js b/test/unit/specs/stores/lists.spec.js index 3720aa776..bb1ef12b4 100644 --- a/test/unit/specs/stores/lists.spec.js +++ b/test/unit/specs/stores/lists.spec.js @@ -36,7 +36,6 @@ describe('The lists store', () => { HttpResponse.json({ ok: true }), ), ) - console.log('1 =========', worker.listHandlers()) await store.setList({ listId: list.id, title: list.title }) expect(store.allListsObject[list.id]).to.eql({ @@ -46,8 +45,6 @@ describe('The lists store', () => { expect(store.allLists).to.have.length(1) expect(store.allLists[0]).to.eql(list) - console.log('2 =========', worker.listHandlers()) - await store.setList({ listId: modList.id, title: modList.title }) expect(store.allListsObject[modList.id]).to.eql({ title: modList.title, @@ -55,8 +52,6 @@ describe('The lists store', () => { }) expect(store.allLists).to.have.length(1) expect(store.allLists[0]).to.eql(modList) - - console.log('3 =========', worker.listHandlers()) }) it('adds a new list with an array of IDs, updating the IDs for existing lists', async ({ diff --git a/test/unit/specs/stores/oauth.spec.js b/test/unit/specs/stores/oauth.spec.js index 6e61fea79..a06cbb2fd 100644 --- a/test/unit/specs/stores/oauth.spec.js +++ b/test/unit/specs/stores/oauth.spec.js @@ -155,7 +155,6 @@ describe('oauth store', () => { describe('ensureAppToken', () => { it('should work if the state is empty', async ({ worker }) => { worker.use(...authApis()) - console.log('=========', worker.listHandlers()) const store = useOAuthStore() const token = await store.ensureAppToken() expect(token).to.eql('test-app-token')