diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 247218091..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
@@ -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..2a7d1511d 100644
--- a/.woodpecker/test-e2e.yaml
+++ b/.woodpecker/test-e2e.yaml
@@ -25,12 +25,12 @@ 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
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/.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/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 066c8da12..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'
@@ -102,8 +101,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 +115,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/changelog.d/api-refactor.skip b/changelog.d/api-refactor.skip
new file mode 100644
index 000000000..e69de29bb
diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml
index 75a4979a1..c49984684 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:
@@ -51,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/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
diff --git a/package.json b/package.json
index c51d98b81..f594d7a99 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,10 +96,10 @@
"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",
+ "playwright": "1.61.0",
"postcss": "8.5.6",
"postcss-html": "^1.5.0",
"postcss-scss": "^4.0.6",
@@ -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/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
new file mode 100644
index 000000000..67fb5038b
--- /dev/null
+++ b/src/api/admin.js
@@ -0,0 +1,491 @@
+import { promisedRequest } from './helpers.js'
+
+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_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 = ({ 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
+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 }) => {
+ return promisedRequest({
+ url: REPORTS,
+ credentials,
+ method: 'PATCH',
+ payload: {
+ reports: [
+ {
+ id,
+ state,
+ },
+ ],
+ },
+ })
+}
+
+export const getInstanceDBConfig = ({ credentials }) =>
+ promisedRequest({
+ url: CONFIG_URL,
+ credentials,
+ })
+
+export const getInstanceConfigDescriptions = ({ credentials }) =>
+ promisedRequest({
+ 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 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/api/chats.js b/src/api/chats.js
new file mode 100644
index 000000000..51e83c74f
--- /dev/null
+++ b/src/api/chats.js
@@ -0,0 +1,87 @@
+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 }) =>
+ `/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/api/helpers.js b/src/api/helpers.js
new file mode 100644
index 000000000..40b879232
--- /dev/null
+++ b/src/api/helpers.js
@@ -0,0 +1,135 @@
+import { snakeCase } from 'lodash'
+
+import { 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 = async ({
+ method,
+ url,
+ payload,
+ formData,
+ credentials,
+ headers = {},
+}) => {
+ const options = {
+ method,
+ credentials: 'same-origin',
+ headers: {
+ Accept: 'application/json',
+ ...headers,
+ },
+ }
+
+ if (!formData) {
+ options.headers['Content-Type'] = 'application/json'
+ }
+
+ if (formData || payload) {
+ options.body = formData || JSON.stringify(payload)
+ }
+
+ if (credentials) {
+ options.headers = {
+ ...options.headers,
+ ...authHeaders(credentials),
+ }
+ }
+
+ 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)
+ }
+}
+
+const authHeaders = (accessToken) => {
+ if (accessToken) {
+ return { Authorization: `Bearer ${accessToken}` }
+ } else {
+ return {}
+ }
+}
diff --git a/src/api/mfa.js b/src/api/mfa.js
new file mode 100644
index 000000000..8c1677fbe
--- /dev/null
+++ b/src/api/mfa.js
@@ -0,0 +1,45 @@
+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 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: `${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..f0fd2950d
--- /dev/null
+++ b/src/api/oauth.js
@@ -0,0 +1,146 @@
+import { paramsString, promisedRequest } from './helpers.js'
+
+const REDIRECT_URI = `${window.location.origin}/oauth-callback`
+
+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()
+
+ 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({
+ method: 'POST',
+ url: MASTODON_APP_URL,
+ formData,
+ }).then(({ data, ...rest }) => ({
+ ...rest,
+ data: {
+ ...data,
+ clientId: data.client_id,
+ clientSecret: data.client_secret,
+ },
+ }))
+}
+
+export const verifyAppToken = ({ credentials }) =>
+ promisedRequest({
+ url: MASTODON_APP_VERIFY_URL,
+ credentials,
+ })
+
+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,
+ 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: OAUTH_TOKEN_URL,
+ method: 'POST',
+ formData,
+ })
+}
+
+export const getToken = ({ clientId, clientSecret, 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: OAUTH_TOKEN_URL,
+ method: 'POST',
+ formData,
+ })
+}
+
+export const getClientToken = ({ clientId, clientSecret }) => {
+ 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: OAUTH_TOKEN_URL,
+ method: 'POST',
+ formData,
+ })
+}
+
+export const verifyOTPCode = ({ app, 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: OAUTH_MFA_CHALLENGE_URL,
+ method: 'POST',
+ formData,
+ })
+}
+
+export const verifyRecoveryCode = ({ app, 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: OAUTH_MFA_CHALLENGE_URL,
+ method: 'POST',
+ formData,
+ })
+}
+
+export const revokeToken = ({ app, 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: OAUTH_REVOKE_URL,
+ method: 'POST',
+ formData,
+ })
+}
diff --git a/src/api/public.js b/src/api/public.js
new file mode 100644
index 000000000..e001ba749
--- /dev/null
+++ b/src/api/public.js
@@ -0,0 +1,279 @@
+import { paramsString, promisedRequest } from './helpers.js'
+import { MASTODON_USER_TIMELINE_URL } from './timelines.js'
+
+import {
+ parseSource,
+ parseStatus,
+ parseUser,
+} from 'src/services/entity_normalizer/entity_normalizer.service.js'
+
+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_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 })}`
+
+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 = ({ 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`
+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 = ({ q, resolve }) =>
+ `/api/v1/accounts/search${paramsString({ q, resolve })}`
+const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers'
+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 EMOJI_PACKS_URL = (page, pageSize) =>
+ `/api/v1/pleroma/emoji/packs${paramsString({ page, pageSize })}`
+
+// Params needed:
+// nickname
+// email
+// fullname
+// password
+// password_confirm
+//
+// Optional
+// bio
+// homepage
+// location
+// token
+// language
+export const register = ({ params, credentials }) => {
+ const { nickname, ...rest } = params
+ return promisedRequest({
+ url: MASTODON_REGISTRATION_URL,
+ method: 'POST',
+ credentials,
+ payload: {
+ nickname,
+ locale: 'en_US',
+ agreement: true,
+ ...rest,
+ },
+ })
+}
+
+export const getCaptcha = () =>
+ promisedRequest({
+ url: '/api/pleroma/captcha',
+ })
+
+export const fetchUser = ({ id, credentials }) =>
+ promisedRequest({
+ url: `${MASTODON_USER_URL}/${id}`,
+ credentials,
+ }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
+
+export const fetchUserByName = ({ name, credentials }) =>
+ promisedRequest({
+ url: MASTODON_USER_LOOKUP_URL({ acct: name }),
+ credentials,
+ })
+ .then(({ data }) => data.id)
+ .catch((error) => {
+ if (error && error.statusCode === 404) {
+ // Either the backend does not support lookup endpoint,
+ // or there is no user with such name. Fallback and treat name as id.
+ return name
+ } else {
+ throw error
+ }
+ })
+ .then((id) => fetchUser({ id, credentials }))
+
+export const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) =>
+ promisedRequest({
+ url: MASTODON_FOLLOWING_URL(id, { maxId, sinceId, limit }),
+ credentials,
+ }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
+
+export const fetchFollowers = ({
+ id,
+ maxId,
+ sinceId,
+ limit = 20,
+ credentials,
+}) =>
+ promisedRequest({
+ url: MASTODON_FOLLOWERS_URL(id, {
+ maxId,
+ sinceId,
+ limit,
+ withRelationships: true,
+ }),
+ credentials,
+ }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
+
+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),
+ },
+ }))
+
+export const fetchStatus = ({ id, credentials }) =>
+ promisedRequest({
+ url: MASTODON_STATUS_URL(id),
+ credentials,
+ }).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) }))
+
+export const fetchStatusHistory = ({ status, credentials }) =>
+ promisedRequest({
+ url: MASTODON_STATUS_HISTORY_URL(status.id),
+ credentials,
+ }).then(({ data, ...rest }) => {
+ return [...data].reverse().map((item) => {
+ item.originalStatus = status
+ return { ...rest, data: parseStatus(item) }
+ })
+ })
+
+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 }),
+ credentials,
+ }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseStatus) }))
+
+export const verifyCredentials = ({ credentials }) =>
+ promisedRequest({
+ url: MASTODON_LOGIN_URL,
+ credentials,
+ }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
+
+export const resetPassword = ({ email }) => {
+ return promisedRequest({
+ url: MASTODON_PASSWORD_RESET_URL({ email }),
+ method: 'POST',
+ })
+}
+
+export const suggestions = ({ credentials }) =>
+ promisedRequest({
+ url: MASTODON_SUGGESTIONS_URL,
+ credentials,
+ })
+
+export const fetchPoll = ({ pollId, credentials }) =>
+ promisedRequest({
+ url: MASTODON_POLL_URL(encodeURIComponent(pollId)),
+ method: 'GET',
+ credentials,
+ })
+
+export const fetchFavoritedByUsers = ({ id, credentials }) =>
+ promisedRequest({
+ url: MASTODON_STATUS_FAVORITEDBY_URL(id),
+ method: 'GET',
+ credentials,
+ }).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: data.map(parseUser) }))
+
+export const fetchEmojiReactions = ({ id, credentials }) =>
+ promisedRequest({
+ url: PLEROMA_EMOJI_REACTIONS_URL(id),
+ credentials,
+ }).then(({ data, ...rest }) => ({
+ ...rest,
+ data: data.map((r) => {
+ r.accounts = r.accounts.map(parseUser)
+ return r
+ }),
+ }))
+
+export const searchUsers = ({ credentials, query }) =>
+ promisedRequest({
+ url: MASTODON_USER_SEARCH_URL({ q: query, resolve: true }),
+ credentials,
+ }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
+
+export const search2 = ({
+ credentials,
+ q,
+ resolve,
+ limit,
+ offset,
+ following,
+ type,
+}) => {
+ return promisedRequest({
+ url: MASTODON_SEARCH_2({
+ q,
+ resolve,
+ limit,
+ offset,
+ following,
+ type,
+ 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 }
+ })
+}
+
+export const fetchKnownDomains = ({ credentials }) =>
+ promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials })
+
+export const fetchScrobbles = ({ accountId, limit = 1 }) =>
+ promisedRequest({
+ url: PLEROMA_SCROBBLES_URL(accountId, { limit }),
+ })
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/api/user.js b/src/api/user.js
new file mode 100644
index 000000000..b9035da77
--- /dev/null
+++ b/src/api/user.js
@@ -0,0 +1,924 @@
+import { concat, last } from 'lodash'
+
+import { paramsString, promisedRequest } from './helpers.js'
+import { fetchFriends, MASTODON_STATUS_URL } from './public.js'
+
+import {
+ parseAttachment,
+ parseStatus,
+ parseUser,
+} from 'src/services/entity_normalizer/entity_normalizer.service.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`
+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,
+ 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_ANNOUNCEMENTS_URL = '/api/v1/announcements'
+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, ...rest }) => ({ ...rest, data: parseStatus(data) }))
+
+export const unfavorite = ({ id, credentials }) =>
+ promisedRequest({
+ url: MASTODON_UNFAVORITE_URL(id),
+ method: 'POST',
+ credentials,
+ }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
+
+export const retweet = ({ id, credentials }) =>
+ promisedRequest({
+ url: MASTODON_RETWEET_URL(id),
+ method: 'POST',
+ credentials,
+ }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
+
+export const unretweet = ({ id, credentials }) =>
+ promisedRequest({
+ url: MASTODON_UNRETWEET_URL(id),
+ method: 'POST',
+ credentials,
+ }).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(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
+
+export const unreactWithEmoji = ({ id, emoji, credentials }) =>
+ promisedRequest({
+ url: PLEROMA_EMOJI_UNREACT_URL(id, emoji),
+ method: 'DELETE',
+ credentials,
+ }).then(({ data, ...rest }) => ({ ...rest, 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 pinOwnStatus = ({ id, credentials }) =>
+ promisedRequest({
+ url: MASTODON_PIN_OWN_STATUS(id),
+ credentials,
+ method: 'POST',
+ }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
+
+export const unpinOwnStatus = ({ id, credentials }) =>
+ promisedRequest({
+ url: MASTODON_UNPIN_OWN_STATUS(id),
+ credentials,
+ method: 'POST',
+ }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
+
+export const muteConversation = ({ id, credentials }) =>
+ promisedRequest({
+ url: MASTODON_MUTE_CONVERSATION(id),
+ credentials,
+ method: 'POST',
+ }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
+
+export const unmuteConversation = ({ id, credentials }) =>
+ promisedRequest({
+ url: MASTODON_UNMUTE_CONVERSATION(id),
+ credentials,
+ method: 'POST',
+ }).then(({ data, ...rest }) => ({ ...rest, 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, ...rest }) => ({ ...rest, 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, ...rest }) => ({ ...rest, 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, ...rest }) => ({ ...rest, data: parseAttachment(data) }))
+
+export const setMediaDescription = ({ id, description, credentials }) =>
+ promisedRequest({
+ url: `${MASTODON_MEDIA_UPLOAD_URL}/${id}`,
+ method: 'PUT',
+ credentials,
+ payload: {
+ description,
+ },
+ }).then(({ data, ...rest }) => ({ ...rest, data: parseAttachment(data) }))
+
+// #Notifications
+export const dismissNotification = ({ credentials, id }) =>
+ promisedRequest({
+ url: MASTODON_DISMISS_NOTIFICATION_URL(id),
+ method: 'POST',
+ payload: { id },
+ 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',
+ })
+}
+
+// #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()
+ 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, ...rest }) => ({ ...rest, data: 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, ...rest }) => ({ ...rest, data: parseUser(data) }))
+}
+
+export const updateProfileJSON = ({ credentials, params }) =>
+ promisedRequest({
+ url: MASTODON_PROFILE_UPDATE_URL,
+ credentials,
+ payload: params,
+ method: 'PATCH',
+ }).then(({ data, ...rest }) => ({ ...rest, 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, ...rest }) => ({ ...rest, 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(({ data, ...rest }) => ({ ...rest, data: data.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(({ data, ...rest }) => ({ ...rest, data: data.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_LIST_URL(),
+ credentials,
+ })
+
+export const createList = ({ title, credentials }) =>
+ promisedRequest({
+ url: MASTODON_LIST_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/api/websocket.js b/src/api/websocket.js
new file mode 100644
index 000000000..d952372e5
--- /dev/null
+++ b/src/api/websocket.js
@@ -0,0 +1,176 @@
+import { paramsString } 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,
+})
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index fe65a7387..0b4825ca0 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'
@@ -29,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'
@@ -38,7 +36,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,16 +259,6 @@ const getStickers = async ({ store }) => {
}
}
-const getAppSecret = async ({ store }) => {
- const oauth = useOAuthStore()
- if (oauth.userToken) {
- store.commit(
- 'setBackendInteractor',
- backendInteractorService(oauth.getToken),
- )
- }
-}
-
const resolveStaffAccounts = ({ store, accounts }) => {
const nicknames = accounts.map((uri) => uri.split('/').pop())
useInstanceStore().set({
@@ -461,14 +449,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()
}
@@ -578,10 +565,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/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js
index 3c73198c5..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.includes(
- 'announcements_manage_announcements',
- )
+ this.currentUser.privileges.has('announcements_manage_announcements')
)
},
},
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/bookmark_folder_card/bookmark_folder_card.js b/src/components/bookmark_folder_card/bookmark_folder_card.js
deleted file mode 100644
index 37b3f2e5e..000000000
--- a/src/components/bookmark_folder_card/bookmark_folder_card.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { library } from '@fortawesome/fontawesome-svg-core'
-import { faEllipsisH } from '@fortawesome/free-solid-svg-icons'
-
-library.add(faEllipsisH)
-
-const BookmarkFolderCard = {
- props: ['folder', 'allBookmarks'],
- computed: {
- firstLetter() {
- return this.folder ? this.folder.name[0] : null
- },
- },
-}
-
-export default BookmarkFolderCard
diff --git a/src/components/bookmark_folder_card/bookmark_folder_card.vue b/src/components/bookmark_folder_card/bookmark_folder_card.vue
deleted file mode 100644
index 9e8bef618..000000000
--- a/src/components/bookmark_folder_card/bookmark_folder_card.vue
+++ /dev/null
@@ -1,111 +0,0 @@
-
-
-
-
-
- {{ $t('nav.all_bookmarks') }}
-
-
-
-
-
-
-
- {{ folder.emoji }}
-
-
- {{ firstLetter }}{{ folder.name }}
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.js b/src/components/bookmark_folder_edit/bookmark_folder_edit.js
index 43aa239b2..ae5aebedd 100644
--- a/src/components/bookmark_folder_edit/bookmark_folder_edit.js
+++ b/src/components/bookmark_folder_edit/bookmark_folder_edit.js
@@ -1,8 +1,10 @@
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 { useInterfaceStore } from 'src/stores/interface.js'
+import { useOAuthStore } from 'src/stores/oauth.js'
+
+import { fetchBookmarkFolders } from 'src/api/user.js'
const BookmarkFolderEdit = {
data() {
@@ -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: useOAuthStore().token,
+ }).then(({ data: folders }) => {
const folder = folders.find((folder) => folder.id === this.id)
if (!folder) return
diff --git a/src/components/bookmark_folders/bookmark_folders.js b/src/components/bookmark_folders/bookmark_folders.js
index 9fd62dae0..cd12d4c3a 100644
--- a/src/components/bookmark_folders/bookmark_folders.js
+++ b/src/components/bookmark_folders/bookmark_folders.js
@@ -1,4 +1,4 @@
-import BookmarkFolderCard from 'src/components/bookmark_folder_card/bookmark_folder_card.vue'
+import FolderCard from 'src/components/folder_card/folder_card.vue'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
@@ -9,7 +9,7 @@ const BookmarkFolders = {
}
},
components: {
- BookmarkFolderCard,
+ FolderCard,
},
computed: {
bookmarkFolders() {
diff --git a/src/components/bookmark_folders/bookmark_folders.vue b/src/components/bookmark_folders/bookmark_folders.vue
index fdd461064..56c9b62ce 100644
--- a/src/components/bookmark_folders/bookmark_folders.vue
+++ b/src/components/bookmark_folders/bookmark_folders.vue
@@ -12,14 +12,28 @@
-
-
+
+
+
+ {{ $t('nav.all_bookmarks') }}
+
+
+
diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js
index 7428c0dc7..16a03ab1d 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 '../../services/api/api.service.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'
@@ -17,6 +16,14 @@ import {
} from './chat_layout_utils.js'
import { useInterfaceStore } from 'src/stores/interface.js'
+import { useOAuthStore } from 'src/stores/oauth.js'
+
+import {
+ chatMessages,
+ getOrCreateChat,
+ sendChatMessage,
+} from 'src/api/chats.js'
+import { WSConnectionStatus } from 'src/api/websocket.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,43 +273,48 @@ 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: useOAuthStore().token,
+ }).then(({ data: 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({
+ 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
@@ -369,9 +380,11 @@ const Chat = {
doSendMessage({ params, fakeMessage, retriesLeft = MAX_RETRIES }) {
if (retriesLeft <= 0) return
- this.backendInteractor
- .sendChatMessage(params)
- .then((data) => {
+ sendChatMessage({
+ params,
+ credentials: useOAuthStore().token,
+ })
+ .then(({ data }) => {
this.$store.dispatch('addChatMessages', {
chatId: this.currentChat.id,
updateMaxId: false,
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 5ee2b610d..0ee4e292b 100644
--- a/src/components/chat_new/chat_new.js
+++ b/src/components/chat_new/chat_new.js
@@ -3,6 +3,8 @@ 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 { useOAuthStore } from 'src/stores/oauth.js'
+
import { library } from '@fortawesome/fontawesome-svg-core'
import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons'
@@ -22,7 +24,9 @@ const chatNew = {
}
},
async created() {
- const { chats } = await this.backendInteractor.chats()
+ const { chats } = await chats({
+ credentials: useOAuthStore().token,
+ })
chats.forEach((chat) => this.suggestions.push(chat.account))
},
computed: {
@@ -38,7 +42,6 @@ const chatNew = {
},
...mapState({
currentUser: (state) => state.users.currentUser,
- backendInteractor: (state) => state.api.backendInteractor,
}),
...mapGetters(['findUser']),
},
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/conversation/conversation.js b/src/components/conversation/conversation.js
index da15c79d6..54bcbd373 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -5,10 +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 { 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 { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -436,22 +439,26 @@ 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: useOAuthStore().token,
+ }).then(({ data: { 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 })
- .then((status) => {
+ fetchStatus({
+ id: this.statusId,
+ credentials: useOAuthStore().token,
+ })
+ .then(({ data: status }) => {
this.$store.dispatch('addNewStatuses', { statuses: [status] })
this.fetchConversation()
})
.catch((error) => {
+ console.error(error)
this.loadStatusError = error
})
}
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/folder_card/folder_card.js b/src/components/folder_card/folder_card.js
new file mode 100644
index 000000000..7a68bc3ac
--- /dev/null
+++ b/src/components/folder_card/folder_card.js
@@ -0,0 +1,38 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faEllipsisH } from '@fortawesome/free-solid-svg-icons'
+
+library.add(faEllipsisH)
+
+const FolderCard = {
+ props: {
+ name: {
+ type: String,
+ required: true,
+ },
+ emoji: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ emojiUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ link: {
+ type: Object,
+ required: true,
+ },
+ linkEdit: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ firstLetter() {
+ return this.name[0]
+ },
+ },
+}
+
+export default FolderCard
diff --git a/src/components/folder_card/folder_card.vue b/src/components/folder_card/folder_card.vue
new file mode 100644
index 000000000..043a326cc
--- /dev/null
+++ b/src/components/folder_card/folder_card.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+ {{ emoji }}
+
+
+ {{ firstLetter }}{{ name }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js
index 44658e985..c2e10c242 100644
--- a/src/components/follow_request_card/follow_request_card.js
+++ b/src/components/follow_request_card/follow_request_card.js
@@ -4,6 +4,9 @@ import { notificationsFromStore } from '../../services/notification_utils/notifi
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/api/user.js'
const FollowRequestCard = {
props: ['user'],
@@ -48,7 +51,10 @@ const FollowRequestCard = {
}
},
doApprove() {
- this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
+ approveUser({
+ id: this.user.id,
+ credentials: useOAuthStore().token,
+ })
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: useOAuthStore().token,
+ }).then(() => {
+ this.$store.dispatch('dismissNotificationLocal', { id: notifId })
+ this.$store.dispatch('removeFollowRequest', this.user)
+ })
this.hideDenyConfirmDialog()
},
},
diff --git a/src/components/lists/lists.js b/src/components/lists/lists.js
index 9dcb7636c..7545d9126 100644
--- a/src/components/lists/lists.js
+++ b/src/components/lists/lists.js
@@ -1,4 +1,4 @@
-import ListsCard from 'src/components/lists_card/lists_card.vue'
+import FolderCard from 'src/components/folder_card/folder_card.vue'
import { useListsStore } from 'src/stores/lists.js'
@@ -9,7 +9,7 @@ const Lists = {
}
},
components: {
- ListsCard,
+ FolderCard,
},
computed: {
lists() {
diff --git a/src/components/lists/lists.vue b/src/components/lists/lists.vue
index 05df5b72f..f3987205e 100644
--- a/src/components/lists/lists.vue
+++ b/src/components/lists/lists.vue
@@ -14,10 +14,12 @@
-
diff --git a/src/components/lists_card/lists_card.js b/src/components/lists_card/lists_card.js
deleted file mode 100644
index 81b811534..000000000
--- a/src/components/lists_card/lists_card.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { library } from '@fortawesome/fontawesome-svg-core'
-import { faEllipsisH } from '@fortawesome/free-solid-svg-icons'
-
-library.add(faEllipsisH)
-
-const ListsCard = {
- props: ['list'],
-}
-
-export default ListsCard
diff --git a/src/components/lists_card/lists_card.vue b/src/components/lists_card/lists_card.vue
deleted file mode 100644
index a5dc6371e..000000000
--- a/src/components/lists_card/lists_card.vue
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
- {{ list.title }}
-
-
-
-
-
-
-
-
-
-
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 b16fb8d2f..a333e6970 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 { mapActions, mapState as mapPiniaState } 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,32 @@ 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(({ data: result }) => {
this.login(result).then(() => {
this.$router.push({ name: 'friends' })
})
})
+ .catch((error) => {
+ if (error.errorData?.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 = error
+ this.focusOnPasswordInput()
+ }
+ return
+ })
})
},
clearError() {
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/mfa_form/recovery_form.js b/src/components/mfa_form/recovery_form.js
index f4ddcadd0..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 mfaApi from '../../services/new_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'
@@ -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..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 mfaApi from '../../services/new_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'
@@ -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/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/notification/notification.js b/src/components/notification/notification.js
index 856850920..b6ee27f9c 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -15,8 +15,10 @@ import {
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/api/user.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: useOAuthStore().token,
+ })
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: useOAuthStore().token,
+ }).then(() => {
+ this.$store.dispatch('dismissNotificationLocal', {
+ id: this.notification.id,
})
+ this.$store.dispatch('removeFollowRequest', this.user)
+ })
this.hideDenyConfirmDialog()
},
},
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/components/password_reset/password_reset.js b/src/components/password_reset/password_reset.js
index 5a54d846b..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 passwordResetApi from '../../services/new_api/password_reset.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,9 +44,8 @@ const passwordReset = {
submit() {
this.isPending = true
const email = this.user.email
- const server = this.server
- passwordResetApi({ server, email })
+ resetPassword({ email })
.then(({ status }) => {
this.isPending = false
this.user.email = ''
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 7eb129430..6cb99a3b6 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'
@@ -635,11 +634,7 @@ 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 }
- }
+ this.preview = data
})
.catch((error) => {
this.preview = { error }
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/remote_user_resolver/remote_user_resolver.js b/src/components/remote_user_resolver/remote_user_resolver.js
index 430f56c84..75304fe52 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 { useOAuthStore } from 'src/stores/oauth.js'
+
+import { fetchUser } from 'src/api/public.js'
+
const RemoteUserResolver = {
data: () => ({
error: false,
@@ -7,11 +11,12 @@ const RemoteUserResolver = {
},
methods: {
redirect() {
- const acct =
- this.$route.params.username + '@' + this.$route.params.hostname
- this.$store.state.api.backendInteractor
- .fetchUser({ id: acct })
- .then((externalUser) => {
+ const id = this.$route.params.username + '@' + this.$route.params.hostname
+ fetchUser({
+ id,
+ credentials: useOAuthStore().token,
+ })
+ .then(({ data: externalUser }) => {
if (externalUser.error) {
this.error = true
} else {
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 98c0cb467..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'
@@ -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/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/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue
index 7078d2488..d3db657d3 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 { useOAuthStore } from 'src/stores/oauth.js'
+
+import {
+ addNewEmojiFile,
+ deleteEmojiFile,
+ updateEmojiFile,
+} from 'src/api/admin.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: useOAuthStore().token,
+ })
.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: useOAuthStore().token,
+ })
.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: useOAuthStore().token,
+ })
.then((resp) => resp.json())
.then((resp) => {
if (resp.error !== undefined) {
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/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js
index 68d2b923a..c11579e53 100644
--- a/src/components/settings_modal/tabs/appearance_tab.js
+++ b/src/components/settings_modal/tabs/appearance_tab.js
@@ -12,7 +12,9 @@ import Preview from './old_theme_tab/theme_preview.vue'
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'
import {
adoptStyleSheets,
@@ -484,9 +486,11 @@ const AppearanceTab = {
}
this.backgroundUploading = true
- this.$store.state.api.backendInteractor
- .updateProfileImages({ background })
- .then((data) => {
+ updateProfileImages({
+ background,
+ credentials: useOAuthStore().token,
+ })
+ .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 ab8d0101d..1586c87bf 100644
--- a/src/components/settings_modal/tabs/composing_tab.js
+++ b/src/components/settings_modal/tabs/composing_tab.js
@@ -14,8 +14,10 @@ import UnitSetting from '../helpers/unit_setting.vue'
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'
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: useOAuthStore().token,
+ }).then(({ data: 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..4554f4a1f 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 { useOAuthStore } from 'src/stores/oauth.js'
import { useOAuthTokensStore } from 'src/stores/oauth_tokens.js'
+import {
+ addBackup,
+ exportFriends,
+ fetchBlocks,
+ fetchMutes,
+ importBlocks,
+ importFollows,
+ importMutes,
+ listBackups,
+} from 'src/api/user.js'
+
const DataImportExportTab = {
data() {
return {
@@ -28,48 +40,57 @@ 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: useOAuthStore().token,
+ }).then(this.generateExportableUsersContent)
},
getBlocksContent() {
- return this.backendInteractor
- .fetchBlocks()
- .then(this.generateExportableUsersContent)
+ return fetchBlocks({
+ credentials: useOAuthStore().token,
+ }).then(this.generateExportableUsersContent)
},
getMutesContent() {
- return this.backendInteractor
- .fetchMutes()
- .then(this.generateExportableUsersContent)
+ return fetchMutes({
+ credentials: useOAuthStore().token,
+ }).then(this.generateExportableUsersContent)
},
importFollows(file) {
- return this.backendInteractor.importFollows({ file }).then((status) => {
+ return importFollows({
+ file,
+ credentials: useOAuthStore().token,
+ }).then(({ data: status }) => {
if (!status) {
throw new Error('failed')
}
})
},
importBlocks(file) {
- return this.backendInteractor.importBlocks({ file }).then((status) => {
+ return importBlocks({
+ file,
+ credentials: useOAuthStore().token,
+ }).then(({ data: status }) => {
if (!status) {
throw new Error('failed')
}
})
},
importMutes(file) {
- return this.backendInteractor.importMutes({ file }).then((status) => {
+ return importMutes({
+ file,
+ credentials: useOAuthStore().token,
+ }).then(({ data: status }) => {
if (!status) {
throw new Error('failed')
}
})
},
- generateExportableUsersContent(users) {
+ generateExportableUsersContent({ data: users }) {
// Get addresses
return users
.map((user) => {
@@ -83,8 +104,9 @@ const DataImportExportTab = {
.join('\n')
},
addBackup() {
- this.$store.state.api.backendInteractor
- .addBackup()
+ addBackup({
+ credentials: useOAuthStore().token,
+ })
.then(() => {
this.addedBackup = true
this.addBackupError = false
@@ -96,9 +118,10 @@ const DataImportExportTab = {
.then(() => this.fetchBackups())
},
fetchBackups() {
- this.$store.state.api.backendInteractor
- .listBackups()
- .then((res) => {
+ listBackups({
+ credentials: useOAuthStore().token,
+ })
+ .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 db1177cb2..3ab3846d2 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -8,12 +8,13 @@ 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'
+import { useOAuthStore } from 'src/stores/oauth.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
+import { updateProfile } from 'src/api/user.js'
import localeService from 'src/services/locale/locale.service.js'
const GeneralTab = {
@@ -58,12 +59,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: useOAuthStore().token,
+ }).then(({ data: 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..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'
@@ -10,8 +10,11 @@ import ProgressButton from 'src/components/progress_button/progress_button.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
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/api/user.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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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..a666d42e3 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 { useOAuthStore } from 'src/stores/oauth.js'
+
+import { updateNotificationSettings } from 'src/api/user.js'
+
const NotificationsTab = {
data() {
return {
@@ -27,7 +31,8 @@ const NotificationsTab = {
},
methods: {
updateNotificationSettings() {
- this.$store.state.api.backendInteractor.updateNotificationSettings({
+ updateNotificationSettings({
+ credentials: useOAuthStore().token,
settings: this.notificationSettings,
})
},
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/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
index 3e12f265f..844e223c9 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 { useOAuthStore } from 'src/stores/oauth.js'
+
+import { updateProfile } from 'src/api/user.js'
+
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faCircleNotch,
@@ -35,10 +39,11 @@ const ProfileTab = {
const params = {
locked: this.locked,
}
-
- this.$store.state.api.backendInteractor
- .updateProfile({ params })
- .then((user) => {
+ updateProfile({
+ params,
+ credentials: useOAuthStore().token,
+ })
+ .then(({ data: 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..1277b42ec 100644
--- a/src/components/settings_modal/tabs/security_tab/mfa.js
+++ b/src/components/settings_modal/tabs/security_tab/mfa.js
@@ -1,10 +1,18 @@
import VueQrcode from '@chenfengyuan/vue-qrcode'
-import { mapState } from 'vuex'
import Confirm from './confirm.vue'
import RecoveryCodes from './mfa_backup_codes.vue'
import TOTP from './mfa_totp.vue'
+import { useOAuthStore } from 'src/stores/oauth.js'
+
+import {
+ generateMfaBackupCodes,
+ mfaConfirmOTP,
+ mfaSetupOTP,
+ settingsMFA,
+} from 'src/api/user.js'
+
const Mfa = {
data: () => ({
settings: {
@@ -71,9 +79,6 @@ const Mfa = {
confirmNewBackupCodes() {
return this.backupCodes.getNewCodes
},
- ...mapState({
- backendInteractor: (state) => state.api.backendInteractor,
- }),
},
methods: {
@@ -87,7 +92,9 @@ const Mfa = {
this.backupCodes.inProgress = true
this.backupCodes.codes = []
- return this.backendInteractor.generateMfaBackupCodes().then((res) => {
+ return generateMfaBackupCodes({
+ credentials: useOAuthStore().token,
+ }).then(({ data: res }) => {
this.backupCodes.codes = res.codes
this.backupCodes.inProgress = false
})
@@ -112,7 +119,9 @@ const Mfa = {
// prepare setup OTP
this.setupState.state = 'setupOTP'
this.setupState.setupOTPState = 'prepare'
- this.backendInteractor.mfaSetupOTP().then((res) => {
+ mfaSetupOTP({
+ credentials: useOAuthStore().token,
+ }).then(({ data: res }) => {
this.otpSettings = res
this.setupState.setupOTPState = 'confirm'
})
@@ -120,18 +129,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
- }
+ mfaConfirmOTP({
+ token: this.otpConfirmToken,
+ password: this.currentPassword,
+ credentials: useOAuthStore().token,
+ })
+ .then(() => {
this.completeSetup()
})
+ .catch((error) => {
+ this.error = error
+ })
},
completeSetup() {
@@ -152,7 +160,9 @@ const Mfa = {
// fetch settings from server
async fetchSettings() {
- const result = await this.backendInteractor.settingsMFA()
+ const { data: result } = await settingsMFA({
+ credentials: useOAuthStore().token,
+ })
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..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,7 +1,9 @@
-import { mapState } from 'vuex'
-
import Confirm from './confirm.vue'
+import { useOAuthStore } from 'src/stores/oauth.js'
+
+import { mfaDisableOTP } from 'src/api/user.js'
+
export default {
props: ['settings'],
data: () => ({
@@ -17,9 +19,6 @@ export default {
isActivated() {
return this.settings.totp
},
- ...mapState({
- backendInteractor: (state) => state.api.backendInteractor,
- }),
},
methods: {
doActivate() {
@@ -36,19 +35,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: useOAuthStore().token,
+ }).then(({ data: 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..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,10 +2,19 @@ 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'
+import {
+ addAlias,
+ changeEmail,
+ changePassword,
+ deleteAccount,
+ deleteAlias,
+ listAliases,
+ moveAccount,
+} from 'src/api/user.js'
import localeService from 'src/services/locale/locale.service.js'
const SecurityTab = {
@@ -65,78 +74,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: useOAuthStore().token,
+ password: this.deleteAccountConfirmPasswordInput,
+ }).then(({ data: 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: useOAuthStore().token,
}
- 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(({ data: 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: useOAuthStore().token,
}
- 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(({ data: 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: useOAuthStore().token,
}
- 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(({ data: 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: useOAuthStore().token,
+ }).then(() => this.fetchAliases())
},
addAlias() {
- this.$store.state.api.backendInteractor
- .addAlias({ alias: this.addAliasTarget })
+ addAlias({
+ alias: this.addAliasTarget,
+ credentials: useOAuthStore().token,
+ })
.then(() => {
this.addedAlias = true
this.addAliasError = false
@@ -149,9 +159,10 @@ const SecurityTab = {
.then(() => this.fetchAliases())
},
fetchAliases() {
- this.$store.state.api.backendInteractor
- .listAliases()
- .then((res) => {
+ listAliases({
+ credentials: useOAuthStore().token,
+ })
+ .then(({ data: res }) => {
this.aliases = res.aliases
this.listAliasesError = false
})
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_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/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/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/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 acb2c4ea1..31a513130 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -25,6 +25,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/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'
@@ -307,9 +308,9 @@ export default {
const privileges = this.loggedIn.privileges
return (
this.loggedIn.role === 'admin' ||
- privileges.includes('users_manage_activation_state') ||
- privileges.includes('users_delete') ||
- privileges.includes('users_manage_tags')
+ privileges.has('users_manage_activation_state') ||
+ privileges.has('users_delete') ||
+ privileges.has('users_manage_tags')
)
},
hasNote() {
@@ -597,9 +598,8 @@ export default {
params.header = this.newBannerFile
}
- this.$store.state.api.backendInteractor
- .updateProfile({ params })
- .then((user) => {
+ updateProfile({ params })
+ .then(({ data: user }) => {
this.newFields.splice(this.newFields.length)
merge(this.newFields, user.fields)
this.$store.commit('addNewUsers', [user])
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/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
index 12d548054..017a17efa 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 { useOAuthStore } from 'src/stores/oauth.js'
import { useReportsStore } from 'src/stores/reports.js'
+import { reportUser } from 'src/api/user.js'
+
const UserReportingModal = {
components: {
List,
@@ -70,9 +73,9 @@ const UserReportingModal = {
comment: this.comment,
forward: this.forward,
statusIds: [...this.statusIdsToReport],
+ credentials: useOAuthStore().token,
}
- 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..28b63c973 100644
--- a/src/components/who_to_follow/who_to_follow.js
+++ b/src/components/who_to_follow/who_to_follow.js
@@ -1,7 +1,8 @@
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'
+
+import { fetchUser, suggestions } from 'src/api/public.js'
const WhoToFollow = {
components: {
@@ -17,21 +18,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: useOAuthStore().token,
+ }).then(({ data: externalUser }) => {
+ if (!externalUser.error) {
+ this.$store.commit('addNewUsers', [externalUser])
+ this.users.push(externalUser)
+ }
+ })
})
},
getWhoToFollow() {
- const credentials = this.$store.state.users.currentUser.credentials
+ const credentials = useOAuthStore().token
if (credentials) {
- apiService.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 cd0524ecf..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
@@ -1,10 +1,10 @@
import { shuffle } from 'lodash'
-import apiService from '../../services/api/api.service.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/api/public.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: useOAuthStore().token,
+ }).then(({ data: 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(({ data: reply }) => {
showWhoToFollow(panel, reply)
})
}
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 6f4b8b15f..72d7fb4d7 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -1,20 +1,27 @@
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 { 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 { fetchTimeline } from 'src/api/timelines.js'
+import {
+ getMastodonSocketURI,
+ ProcessedWS,
+ WSConnectionStatus,
+} 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'
+
const retryTimeout = (multiplier) => 1000 * multiplier
const api = {
state: {
retryMultiplier: 1,
- backendInteractor: backendInteractorService(),
fetchers: {},
socket: null,
mastoUserSocket: null,
@@ -25,9 +32,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 +95,16 @@ const api = {
try {
const { state, commit, dispatch, rootState } = store
const timelineData = rootState.statuses.timelines.friends
- state.mastoUserSocket = state.backendInteractor.startUserSocket({
- store,
+
+ const credentials = useOAuthStore().token
+ const url = getMastodonSocketURI({ credentials })
+
+ state.mastoUserSocket = ProcessedWS({
+ url,
+ id: 'Unified',
+ credentials,
})
+
state.mastoUserSocket.addEventListener(
'pleroma:authenticated',
() => {
@@ -245,7 +256,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 +264,9 @@ const api = {
statusId,
bookmarkFolderId,
tag,
+ credentials: useOAuthStore().token,
})
+
store.commit('addFetcher', { fetcherName: timeline, fetcher })
},
stopFetchingTimeline(store, timeline) {
@@ -261,19 +274,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: useOAuthStore().token,
})
},
// Notifications
startFetchingNotifications(store) {
if (store.state.fetchers.notifications) return
- const fetcher = store.state.backendInteractor.startFetchingNotifications({
+ const fetcher = notificationsFetcher.startFetching({
store,
+ credentials: useOAuthStore().token,
})
store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
},
@@ -282,19 +298,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: useOAuthStore().token,
+ })
store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
},
@@ -303,39 +314,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 308b2cb27..abc035f09 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 { useOAuthStore } from 'src/stores/oauth.js'
+
+import { chats, deleteChatMessage, readChat } from 'src/api/chats.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],
@@ -51,7 +55,6 @@ const chats = {
// Chat list
startFetchingChats({ dispatch, commit }) {
const fetcher = () => dispatch('fetchChats', { latest: true })
- fetcher()
commit('setChatListFetcher', {
fetcher: () => promiseInterval(fetcher, 5000),
})
@@ -60,8 +63,10 @@ const chats = {
commit('setChatListFetcher', { fetcher: undefined })
},
fetchChats({ dispatch, rootState }) {
- return rootState.api.backendInteractor.chats().then(({ chats }) => {
- dispatch('addNewChats', { chats })
+ return chats({
+ credentials: useOAuthStore().token,
+ }).then(({ chatList }) => {
+ dispatch('addNewChats', { chats: chatList })
return chats
})
},
@@ -113,11 +118,18 @@ const chats = {
commit('readChat', { id, lastReadId })
if (isNewMessage) {
- rootState.api.backendInteractor.readChat({ id, lastReadId })
+ readChat({
+ id,
+ lastReadId,
+ credentials: useOAuthStore().token,
+ })
}
},
deleteChatMessage({ rootState, commit }, value) {
- rootState.api.backendInteractor.deleteChatMessage(value)
+ deleteChatMessage({
+ ...value,
+ credentials: useOAuthStore().token,
+ })
commit('deleteChatMessage', { commit, ...value })
},
resetChats({ commit, dispatch }) {
@@ -262,4 +274,4 @@ const chats = {
},
}
-export default chats
+export default chatsModule
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/modules/notifications.js b/src/modules/notifications.js
index d501b39db..b77683811 100644
--- a/src/modules/notifications.js
+++ b/src/modules/notifications.js
@@ -1,4 +1,3 @@
-import apiService from '../services/api/api.service.js'
import {
closeAllDesktopNotifications,
closeDesktopNotification,
@@ -11,9 +10,12 @@ import {
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'
+import { dismissNotification, markNotificationsAsSeen } from 'src/api/user.js'
+
const emptyNotifications = () => ({
desktopNotificationSilence: true,
maxId: 0,
@@ -154,33 +156,32 @@ 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 })
},
dismissNotification({ rootState, commit }, { id }) {
commit('dismissNotification', { id })
- rootState.api.backendInteractor.dismissNotification({ id })
+ dismissNotification({
+ id,
+ credentials: useOAuthStore().token,
+ })
},
updateNotification({ commit }, { id, updater }) {
commit('updateNotification', { id, updater })
diff --git a/src/modules/profileConfig.js b/src/modules/profileConfig.js
index 90571e21d..3fca7d6a8 100644
--- a/src/modules/profileConfig.js
+++ b/src/modules/profileConfig.js
@@ -1,28 +1,34 @@
import { get, set } from 'lodash'
+import { useOAuthStore } from 'src/stores/oauth.js'
+
+import { updateNotificationSettings, updateProfile } from 'src/api/user.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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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 b4f0a93bf..445d1d436 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -13,10 +13,36 @@ import {
slice,
} from 'lodash'
-import apiService 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'
+
+import {
+ fetchEmojiReactions,
+ fetchFavoritedByUsers,
+ fetchPinnedStatuses,
+ fetchRebloggedByUsers,
+ fetchScrobbles,
+ fetchStatus,
+ fetchStatusHistory,
+ fetchStatusSource,
+ search2,
+} from 'src/api/public.js'
+import {
+ bookmarkStatus,
+ deleteStatus,
+ favorite,
+ muteConversation,
+ pinOwnStatus,
+ reactWithEmoji,
+ retweet,
+ unbookmarkStatus,
+ unfavorite,
+ unmuteConversation,
+ unpinOwnStatus,
+ unreactWithEmoji,
+ unretweet,
+} from 'src/api/user.js'
const emptyTl = (userId = 0) => ({
statuses: [],
@@ -131,9 +157,8 @@ const getLatestScrobble = (state, user) => {
state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000
if (!scrobblesSupport) return
- apiService
- .fetchScrobbles({ accountId: user.id })
- .then((scrobbles) => {
+ fetchScrobbles({ accountId: user.id })
+ .then(({ data: scrobbles }) => {
if (scrobbles?.error) {
useInstanceCapabilitiesStore().set('pleromaScrobblesAvailable', false)
return
@@ -602,25 +627,24 @@ const statuses = {
})
},
fetchStatus({ rootState, dispatch }, id) {
- return rootState.api.backendInteractor
- .fetchStatus({ id })
- .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
+ return fetchStatus({ id }).then(({ data: status }) =>
+ dispatch('addNewStatuses', { statuses: [status] }),
+ )
},
fetchStatusSource({ rootState }, status) {
- return apiService.fetchStatusSource({
+ return fetchStatusSource({
id: status.id,
- credentials: rootState.users.currentUser.credentials,
- })
+ credentials: useOAuthStore().token,
+ }).then(({ data }) => data)
},
fetchStatusHistory(_, status) {
- return apiService.fetchStatusHistory({ status })
+ return fetchStatusHistory({ status }).then(({ data }) => data)
},
deleteStatus({ rootState, commit }, status) {
- apiService
- .deleteStatus({
- id: status.id,
- credentials: rootState.users.currentUser.credentials,
- })
+ deleteStatus({
+ id: status.id,
+ credentials: useOAuthStore().token,
+ })
.then(() => {
commit('setDeleted', { status })
})
@@ -643,99 +667,115 @@ 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: status }) => {
+ commit('setBookmarkedConfirm', { status })
+ })
},
queueFlush({ commit }, { timeline, id }) {
commit('queueFlush', { timeline, id })
@@ -745,8 +785,14 @@ const statuses = {
},
fetchFavsAndRepeats({ rootState, commit }, id) {
Promise.all([
- rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),
- rootState.api.backendInteractor.fetchRebloggedByUsers({ id }),
+ fetchFavoritedByUsers({
+ id,
+ credentials: useOAuthStore().token,
+ }).then(({ data }) => data),
+ fetchRebloggedByUsers({
+ id,
+ credentials: useOAuthStore().token,
+ }).then(({ data }) => data),
]).then(([favoritedByUsers, rebloggedByUsers]) => {
commit('addFavs', {
id,
@@ -765,7 +811,11 @@ const statuses = {
if (!currentUser) return
commit('addOwnReaction', { id, emoji, currentUser })
- rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then(() => {
+ reactWithEmoji({
+ id,
+ emoji,
+ credentials: useOAuthStore().token,
+ }).then(() => {
dispatch('fetchEmojiReactionsBy', id)
})
},
@@ -774,59 +824,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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).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 ec70f8105..d6b1adaaa 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -1,3 +1,4 @@
+import Cookies from 'js-cookie'
import {
compact,
concat,
@@ -9,9 +10,6 @@ import {
uniq,
} from 'lodash'
-import apiService 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,
unregisterPushNotifications,
@@ -21,15 +19,42 @@ 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'
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 { revokeToken } from 'src/api/oauth.js'
+import {
+ fetchFollowers,
+ fetchFriends,
+ 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,
+} from 'src/api/user.js'
+
// TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => {
if (!item) {
@@ -72,43 +97,35 @@ 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 apiBlockUser({ id, expiresIn }).then(({ data: 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 apiUnblockUser({ id }).then(({ data: 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) => {
@@ -119,12 +136,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 apiMuteUser({
+ id,
+ expiresIn,
+ credentials: useOAuthStore().token,
+ }).then(({ data: relationship }) => {
+ store.commit('updateUserRelationship', [relationship])
+ store.commit('addMuteId', id)
+ })
}
const unmuteUser = (store, id) => {
@@ -132,39 +151,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 apiUnmuteUser({ id }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).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: useOAuthStore().token,
+ }).then(() => store.commit('removeDomainMute', domain))
}
export const mutations = {
@@ -385,55 +408,70 @@ const users = {
})
},
fetchUser(store, id) {
- return store.rootState.api.backendInteractor
- .fetchUser({ id })
- .then((user) => {
+ return fetchUser({
+ id,
+ credentials: useOAuthStore().token,
+ })
+ .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 store.rootState.api.backendInteractor
- .fetchUserByName({ name })
- .then((user) => {
- store.commit('addNewUsers', [user])
- return user
- })
+ return fetchUserByName({
+ name,
+ credentials: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: 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,20 +495,21 @@ 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: useOAuthStore().token,
+ }).then(({ data: 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)
@@ -491,12 +530,12 @@ const users = {
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: useOAuthStore().token,
+ }).then(({ data: domainMutes }) => {
+ store.commit('saveDomainMutes', domainMutes)
+ return domainMutes
+ })
},
muteDomain(store, domain) {
return muteDomain(store, domain)
@@ -513,24 +552,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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: followers }) => {
+ commit('addNewUsers', followers)
+ commit('saveFollowerIds', { id, followerIds: map(followers, 'id') })
+ return followers
+ })
},
clearFriends({ commit }, userId) {
commit('clearFriends', userId)
@@ -539,18 +582,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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ data: relationship }) =>
+ commit('updateUserRelationship', [relationship]),
+ )
},
registerPushNotifications(store) {
const token = store.state.currentUser.credentials
@@ -611,12 +658,13 @@ const users = {
})
},
searchUsers({ rootState, commit }, { query }) {
- return rootState.api.backendInteractor
- .searchUsers({ query })
- .then((users) => {
- commit('addNewUsers', users)
- return users
- })
+ return searchUsers({
+ query,
+ credentials: useOAuthStore().token,
+ }).then(({ data: users }) => {
+ commit('addNewUsers', users)
+ return users
+ })
},
async signUp(store, userInfo) {
const oauthStore = useOAuthStore()
@@ -624,7 +672,7 @@ const users = {
try {
const token = await oauthStore.ensureAppToken()
- const data = await apiService.register({
+ const { data } = await register({
credentials: token,
params: { ...userInfo },
})
@@ -645,8 +693,10 @@ const users = {
throw e
}
},
- async getCaptcha(store) {
- return store.rootState.api.backendInteractor.getCaptcha()
+ getCaptcha(store) {
+ return getCaptcha({
+ credentials: useOAuthStore().token,
+ }).then(({ data }) => data)
},
logout(store) {
@@ -663,24 +713,21 @@ const users = {
token: oauth.userToken,
}
- return oauthApi.revokeToken(params)
+ return revokeToken(params)
})
.then(() => {
store.commit('clearCurrentUser')
store.dispatch('disconnectFromSocket')
- oauth.clearToken()
store.dispatch('stopFetchingTimeline', 'friends')
- store.commit(
- 'setBackendInteractor',
- backendInteractorService(oauth.getToken),
- )
store.dispatch('stopFetchingNotifications')
- store.dispatch('stopFetchingLists')
- store.dispatch('stopFetchingBookmarkFolders')
+ useListsStore().stopFetching()
+ useBookmarkFoldersStore().stopFetching()
store.dispatch('stopFetchingFollowRequests')
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())
@@ -690,141 +737,137 @@ const users = {
return new Promise((resolve, reject) => {
const commit = store.commit
const dispatch = store.dispatch
+
commit('beginLogin')
- store.rootState.api.backendInteractor
- .verifyCredentials(accessToken)
- .then((data) => {
- if (!data.error) {
- const user = data
- // 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])
+ verifyCredentials({
+ credentials: useOAuthStore().token,
+ })
+ .then(({ data: user }) => {
+ // user.credentials = userCredentials
+ user.credentials = accessToken
+ user.blockIds = []
+ user.muteIds = []
+ user.domainMutes = []
+ commit('setCurrentUser', user)
- useEmojiStore().fetchEmoji()
-
- getNotificationPermission().then((permission) =>
- useInterfaceStore().setNotificationPermission(permission),
- )
-
- // Set our new backend interactor
- commit(
- 'setBackendInteractor',
- backendInteractorService(accessToken),
- )
-
- // 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)
- dispatch('startFetchingLists')
- dispatch('startFetchingBookmarkFolders')
+ // 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', since: null })
- dispatch('fetchNotifications', { since: 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
- store.rootState.api.backendInteractor
- .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()
+ }
+
+ // Start fetching things that don't need to block the UI
+ useAnnouncementsStore().startFetchingAnnouncements()
+
+ dispatch('fetchMutes')
+ dispatch('loadDrafts')
+
+ 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/api/api.service.js b/src/services/api/api.service.js
deleted file mode 100644
index 5ab1fa18e..000000000
--- a/src/services/api/api.service.js
+++ /dev/null
@@ -1,2527 +0,0 @@
-import { concat, each, last, map } from 'lodash'
-
-import {
- parseAttachment,
- parseChat,
- parseLinkHeaderPagination,
- parseNotification,
- parseSource,
- parseStatus,
- parseUser,
-} from '../entity_normalizer/entity_normalizer.service.js'
-import { RegistrationError, StatusCodeError } from '../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'
-
-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) => `/api/v1/accounts/${id}/following`
-const MASTODON_FOLLOWERS_URL = (id) => `/api/v1/accounts/${id}/followers`
-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_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}`
-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 = '/api/v1/accounts/relationships'
-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 = '/api/v1/blocks/'
-const MASTODON_USER_MUTES_URL = '/api/v1/mutes/'
-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) => `/api/v1/pleroma/chats/${id}/messages`
-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`
-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 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 }) => {
- 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())
-}
-
-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 fetch(MASTODON_PROFILE_UPDATE_URL, {
- headers: authHeaders(credentials),
- method: 'PATCH',
- body: form,
- })
- .then((data) => data.json())
- .then((data) => {
- if (data.error) {
- throw new Error(data.error)
- }
- return parseUser(data)
- })
-}
-
-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 fetch(MASTODON_PROFILE_UPDATE_URL, {
- headers: authHeaders(credentials),
- method: 'PATCH',
- body: formData,
- })
- .then((data) => data.json())
- .then((data) => parseUser(data))
-}
-
-const updateProfileJSON = ({ credentials, params }) => {
- return promisedRequest({
- url: MASTODON_PROFILE_UPDATE_URL,
- credentials,
- payload: params,
- method: 'PATCH',
- }).then((data) => parseUser(data))
-}
-
-// Params needed:
-// nickname
-// email
-// fullname
-// password
-// password_confirm
-//
-// Optional
-// bio
-// homepage
-// location
-// token
-// language
-const register = ({ params, credentials }) => {
- const { nickname, ...rest } = params
- return fetch(MASTODON_REGISTRATION_URL, {
- method: 'POST',
- headers: {
- ...authHeaders(credentials),
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- 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())
-
-const authHeaders = (accessToken) => {
- if (accessToken) {
- return { Authorization: `Bearer ${accessToken}` }
- } else {
- return {}
- }
-}
-
-const followUser = ({ id, credentials, ...options }) => {
- const url = MASTODON_FOLLOW_URL(id)
- 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_PIN_OWN_STATUS(id),
- credentials,
- method: 'POST',
- }).then((data) => parseStatus(data))
-}
-
-const unpinOwnStatus = ({ id, credentials }) => {
- return promisedRequest({
- url: MASTODON_UNPIN_OWN_STATUS(id),
- credentials,
- method: 'POST',
- }).then((data) => parseStatus(data))
-}
-
-const muteConversation = ({ id, credentials }) => {
- return promisedRequest({
- url: MASTODON_MUTE_CONVERSATION(id),
- credentials,
- method: 'POST',
- }).then((data) => parseStatus(data))
-}
-
-const unmuteConversation = ({ id, credentials }) => {
- return promisedRequest({
- url: MASTODON_UNMUTE_CONVERSATION(id),
- credentials,
- method: 'POST',
- }).then((data) => parseStatus(data))
-}
-
-const blockUser = ({ id, expiresIn, credentials }) => {
- const payload = {}
- if (expiresIn) {
- payload.duration = expiresIn
- }
-
- return promisedRequest({
- url: MASTODON_BLOCK_USER_URL(id),
- credentials,
- method: 'POST',
- payload,
- })
-}
-
-const unblockUser = ({ id, credentials }) => {
- return fetch(MASTODON_UNBLOCK_USER_URL(id), {
- headers: authHeaders(credentials),
- method: 'POST',
- }).then((data) => data.json())
-}
-
-const removeUserFromFollowers = ({ id, credentials }) => {
- return fetch(MASTODON_REMOVE_USER_FROM_FOLLOWERS(id), {
- headers: authHeaders(credentials),
- method: 'POST',
- }).then((data) => data.json())
-}
-
-const editUserNote = ({ id, credentials, comment }) => {
- return promisedRequest({
- url: MASTODON_USER_NOTE_URL(id),
- credentials,
- payload: {
- comment,
- },
- method: 'POST',
- })
-}
-
-const approveUser = ({ id, credentials }) => {
- const url = MASTODON_APPROVE_USER_URL(id)
- return fetch(url, {
- headers: authHeaders(credentials),
- method: 'POST',
- }).then((data) => data.json())
-}
-
-const denyUser = ({ id, credentials }) => {
- const url = MASTODON_DENY_USER_URL(id)
- return fetch(url, {
- headers: authHeaders(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))
-}
-
-const fetchUserByName = ({ name, credentials }) => {
- return promisedRequest({
- url: MASTODON_USER_LOOKUP_URL,
- credentials,
- params: { acct: name },
- })
- .then((data) => data.id)
- .catch((error) => {
- if (error && error.statusCode === 404) {
- // Either the backend does not support lookup endpoint,
- // or there is no user with such name. Fallback and treat name as id.
- return name
- } else {
- throw error
- }
- })
- .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)
- }),
- )
- })
-}
-
-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 fetch(url, { headers: authHeaders(credentials) })
- .then((data) => data.json())
- .then((data) => data.map(parseUser))
-}
-
-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 })
- friends = concat(friends, users)
- if (users.length === 0) {
- more = false
- }
- }
- resolve(friends)
- } catch (err) {
- reject(err)
- }
- })
-}
-
-const fetchFollowers = ({ id, maxId, 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 fetch(url, { headers: authHeaders(credentials) })
- .then((data) => data.json())
- .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))
-}
-
-const fetchLists = ({ credentials }) => {
- const url = MASTODON_LISTS_URL
- return fetch(url, { headers: authHeaders(credentials) }).then((data) =>
- data.json(),
- )
-}
-
-const createList = ({ title, credentials }) => {
- const url = MASTODON_LISTS_URL
- const headers = authHeaders(credentials)
- headers['Content-Type'] = 'application/json'
-
- return fetch(url, {
- headers,
- method: 'POST',
- body: JSON.stringify({ title }),
- }).then((data) => data.json())
-}
-
-const getList = ({ listId, credentials }) => {
- const url = MASTODON_LIST_URL(listId)
- return fetch(url, { headers: authHeaders(credentials) }).then((data) =>
- data.json(),
- )
-}
-
-const updateList = ({ listId, title, credentials }) => {
- const url = MASTODON_LIST_URL(listId)
- const headers = authHeaders(credentials)
- headers['Content-Type'] = 'application/json'
-
- return fetch(url, {
- headers,
- method: 'PUT',
- body: JSON.stringify({ 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))
-}
-
-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,
- method: 'POST',
- body: JSON.stringify({ 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,
- method: 'DELETE',
- body: JSON.stringify({ account_ids: accountIds }),
- })
-}
-
-const deleteList = ({ listId, credentials }) => {
- const url = MASTODON_LIST_URL(listId)
- return fetch(url, {
- method: 'DELETE',
- headers: authHeaders(credentials),
- })
-}
-
-const fetchConversation = ({ id, credentials }) => {
- const urlContext = MASTODON_STATUS_CONTEXT_URL(id)
- return fetch(urlContext, { headers: authHeaders(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) })
- .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) })
- .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) => {
- 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 = ({
- timeline,
- credentials,
- since = false,
- minId = false,
- until = false,
- userId = false,
- listId = false,
- statusId = false,
- tag = false,
- withMuted = false,
- replyVisibility = 'all',
- includeTypes = [],
- bookmarkFolderId = false,
-}) => {
- const timelineUrls = {
- public: MASTODON_PUBLIC_TIMELINE,
- friends: MASTODON_USER_HOME_TIMELINE_URL,
- dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
- notifications: MASTODON_USER_NOTIFICATIONS_URL,
- publicAndExternal: MASTODON_PUBLIC_TIMELINE,
- 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,
- }
- const isNotifications = timeline === 'notifications'
- const params = []
-
- let url = timelineUrls[timeline]
-
- if (timeline === 'favorites' && userId) {
- url = timelineUrls.publicFavorites(userId)
- }
-
- if (timeline === 'user' || timeline === 'media') {
- url = url(userId)
- }
-
- if (timeline === 'list') {
- url = url(listId)
- }
-
- if (timeline === 'quotes') {
- 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])
- }
- if (timeline === 'public') {
- params.push(['local', true])
- }
- if (timeline === 'public' || timeline === 'publicAndExternal') {
- params.push(['only_media', false])
- }
- if (timeline !== 'favorites' && timeline !== 'bookmarks') {
- params.push(['with_muted', withMuted])
- }
- if (replyVisibility !== 'all') {
- params.push(['reply_visibility', replyVisibility])
- }
- if (includeTypes.size > 0) {
- includeTypes.forEach((type) => {
- params.push(['include_types[]', type])
- })
- }
- if (timeline === 'bookmarks' && bookmarkFolderId) {
- params.push(['folder_id', bookmarkFolderId])
- }
-
- params.push(['limit', 20])
-
- const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join(
- '&',
- )
- 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: MASTODON_FAVORITE_URL(id),
- method: 'POST',
- credentials,
- }).then((data) => parseStatus(data))
-}
-
-const unfavorite = ({ id, credentials }) => {
- return promisedRequest({
- url: MASTODON_UNFAVORITE_URL(id),
- method: 'POST',
- credentials,
- }).then((data) => parseStatus(data))
-}
-
-const retweet = ({ id, credentials }) => {
- return promisedRequest({
- url: MASTODON_RETWEET_URL(id),
- method: 'POST',
- credentials,
- }).then((data) => parseStatus(data))
-}
-
-const unretweet = ({ id, credentials }) => {
- return promisedRequest({
- url: MASTODON_UNRETWEET_URL(id),
- method: 'POST',
- credentials,
- }).then((data) => parseStatus(data))
-}
-
-const bookmarkStatus = ({ id, credentials, ...options }) => {
- return promisedRequest({
- url: MASTODON_BOOKMARK_STATUS_URL(id),
- headers: authHeaders(credentials),
- method: 'POST',
- payload: {
- folder_id: options.folder_id,
- },
- })
-}
-
-const unbookmarkStatus = ({ id, credentials }) => {
- return promisedRequest({
- url: MASTODON_UNBOOKMARK_STATUS_URL(id),
- headers: authHeaders(credentials),
- method: 'POST',
- })
-}
-
-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 postHeaders = authHeaders(credentials)
- if (idempotencyKey) {
- postHeaders['idempotency-key'] = idempotencyKey
- }
-
- return fetch(MASTODON_POST_STATUS_URL, {
- body: form,
- method: 'POST',
- headers: postHeaders,
- })
- .then((response) => {
- return response.json()
- })
- .then((data) => (data.error ? data : parseStatus(data)))
-}
-
-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)
- })
- }
-
- const putHeaders = authHeaders(credentials)
-
- return fetch(MASTODON_STATUS_URL(id), {
- body: form,
- method: 'PUT',
- headers: putHeaders,
- })
- .then((response) => {
- return response.json()
- })
- .then((data) => (data.error ? data : parseStatus(data)))
-}
-
-const deleteStatus = ({ id, credentials }) => {
- return promisedRequest({
- url: MASTODON_DELETE_URL(id),
- credentials,
- method: 'DELETE',
- })
-}
-
-const uploadMedia = ({ formData, credentials }) => {
- return fetch(MASTODON_MEDIA_UPLOAD_URL, {
- body: formData,
- method: 'POST',
- headers: authHeaders(credentials),
- })
- .then((data) => data.json())
- .then((data) => parseAttachment(data))
-}
-
-const setMediaDescription = ({ id, description, credentials }) => {
- return promisedRequest({
- url: `${MASTODON_MEDIA_UPLOAD_URL}/${id}`,
- method: 'PUT',
- headers: authHeaders(credentials),
- payload: {
- description,
- },
- }).then((data) => parseAttachment(data))
-}
-
-const importMutes = ({ file, credentials }) => {
- const formData = new FormData()
- formData.append('list', file)
- return fetch(MUTES_IMPORT_URL, {
- body: formData,
- method: 'POST',
- headers: authHeaders(credentials),
- }).then((response) => response.ok)
-}
-
-const importBlocks = ({ file, credentials }) => {
- const formData = new FormData()
- formData.append('list', file)
- return fetch(BLOCKS_IMPORT_URL, {
- body: formData,
- method: 'POST',
- headers: authHeaders(credentials),
- }).then((response) => response.ok)
-}
-
-const importFollows = ({ file, credentials }) => {
- const formData = new FormData()
- formData.append('list', file)
- return fetch(FOLLOW_IMPORT_URL, {
- body: formData,
- method: 'POST',
- headers: authHeaders(credentials),
- }).then((response) => response.ok)
-}
-
-const deleteAccount = ({ credentials, password }) => {
- const form = new FormData()
-
- form.append('password', password)
-
- return fetch(DELETE_ACCOUNT_URL, {
- body: form,
- method: 'POST',
- headers: authHeaders(credentials),
- }).then((response) => response.json())
-}
-
-const changeEmail = ({ credentials, email, password }) => {
- const form = new FormData()
-
- form.append('email', email)
- form.append('password', password)
-
- return fetch(CHANGE_EMAIL_URL, {
- body: form,
- method: 'POST',
- headers: authHeaders(credentials),
- }).then((response) => response.json())
-}
-
-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,
- method: 'POST',
- headers: authHeaders(credentials),
- }).then((response) => response.json())
-}
-
-const addAlias = ({ credentials, alias }) => {
- return promisedRequest({
- url: ALIASES_URL,
- method: 'PUT',
- credentials,
- payload: { alias },
- })
-}
-
-const deleteAlias = ({ credentials, alias }) => {
- return promisedRequest({
- url: ALIASES_URL,
- method: 'DELETE',
- credentials,
- payload: { alias },
- })
-}
-
-const listAliases = ({ credentials }) => {
- return promisedRequest({
- url: ALIASES_URL,
- method: 'GET',
- credentials,
- params: {
- _cacheBooster: new Date().getTime(),
- },
- })
-}
-
-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 fetch(CHANGE_PASSWORD_URL, {
- body: form,
- method: 'POST',
- headers: authHeaders(credentials),
- }).then((response) => response.json())
-}
-
-const settingsMFA = ({ credentials }) => {
- return fetch(MFA_SETTINGS_URL, {
- headers: authHeaders(credentials),
- method: 'GET',
- }).then((data) => data.json())
-}
-
-const mfaDisableOTP = ({ credentials, password }) => {
- const form = new FormData()
-
- form.append('password', password)
-
- return fetch(MFA_DISABLE_OTP_URL, {
- body: form,
- method: 'DELETE',
- headers: authHeaders(credentials),
- }).then((response) => response.json())
-}
-
-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),
- method: 'POST',
- }).then((data) => data.json())
-}
-const mfaSetupOTP = ({ credentials }) => {
- return fetch(MFA_SETUP_OTP_URL, {
- headers: authHeaders(credentials),
- method: 'GET',
- }).then((data) => data.json())
-}
-const generateMfaBackupCodes = ({ credentials }) => {
- return fetch(MFA_BACKUP_CODES_URL, {
- headers: authHeaders(credentials),
- method: 'GET',
- }).then((data) => data.json())
-}
-
-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()}`,
- credentials,
- }).then((users) => users.map(parseUser))
-}
-
-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,
- })
-}
-
-const unmuteUser = ({ id, credentials }) => {
- return promisedRequest({
- url: MASTODON_UNMUTE_USER_URL(id),
- credentials,
- method: 'POST',
- })
-}
-
-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()}`,
- credentials,
- }).then((users) => users.map(parseUser))
-}
-
-const addBackup = ({ credentials }) => {
- return promisedRequest({
- url: PLEROMA_BACKUP_URL,
- method: 'POST',
- credentials,
- })
-}
-
-const listBackups = ({ credentials }) => {
- return promisedRequest({
- url: PLEROMA_BACKUP_URL,
- method: 'GET',
- credentials,
- params: {
- _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)
- })
-}
-
-const revokeOAuthToken = ({ id, credentials }) => {
- const url = `/api/oauth_tokens/${id}`
-
- return fetch(url, {
- headers: authHeaders(credentials),
- method: 'DELETE',
- })
-}
-
-const suggestions = ({ credentials }) => {
- return fetch(SUGGESTIONS_URL, {
- headers: authHeaders(credentials),
- }).then((data) => data.json())
-}
-
-const markNotificationsAsSeen = ({ id, credentials, single = false }) => {
- const body = new FormData()
-
- if (single) {
- body.append('id', id)
- } else {
- body.append('max_id', id)
- }
-
- return fetch(NOTIFICATION_READ_URL, {
- body,
- headers: authHeaders(credentials),
- method: 'POST',
- }).then((data) => data.json())
-}
-
-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,
- },
- })
-}
-
-const fetchPoll = ({ pollId, credentials }) => {
- return promisedRequest({
- url: MASTODON_POLL_URL(encodeURIComponent(pollId)),
- method: 'GET',
- credentials,
- })
-}
-
-const fetchFavoritedByUsers = ({ id, credentials }) => {
- return promisedRequest({
- url: MASTODON_STATUS_FAVORITEDBY_URL(id),
- method: 'GET',
- credentials,
- }).then((users) => users.map(parseUser))
-}
-
-const fetchRebloggedByUsers = ({ id, credentials }) => {
- return promisedRequest({
- url: MASTODON_STATUS_REBLOGGEDBY_URL(id),
- method: 'GET',
- credentials,
- }).then((users) => users.map(parseUser))
-}
-
-const fetchEmojiReactions = ({ id, credentials }) => {
- return promisedRequest({
- url: PLEROMA_EMOJI_REACTIONS_URL(id),
- credentials,
- }).then((reactions) =>
- reactions.map((r) => {
- r.accounts = r.accounts.map(parseUser)
- return r
- }),
- )
-}
-
-const reactWithEmoji = ({ id, emoji, credentials }) => {
- return promisedRequest({
- url: PLEROMA_EMOJI_REACT_URL(id, emoji),
- method: 'PUT',
- credentials,
- }).then(parseStatus)
-}
-
-const unreactWithEmoji = ({ id, emoji, credentials }) => {
- return promisedRequest({
- url: PLEROMA_EMOJI_UNREACT_URL(id, emoji),
- method: 'DELETE',
- credentials,
- }).then(parseStatus)
-}
-
-const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
- return promisedRequest({
- url: MASTODON_REPORT_USER_URL,
- method: 'POST',
- payload: {
- account_id: userId,
- status_ids: statusIds,
- comment,
- forward,
- },
- credentials,
- })
-}
-
-const searchUsers = ({ credentials, query }) => {
- return promisedRequest({
- url: MASTODON_USER_SEARCH_URL,
- params: {
- q: query,
- resolve: true,
- },
- credentials,
- }).then((data) => data.map(parseUser))
-}
-
-const search2 = ({
- credentials,
- q,
- resolve,
- limit,
- offset,
- 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 fetch(url, { headers: authHeaders(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))
- return data
- })
-}
-
-const fetchKnownDomains = ({ credentials }) => {
- return promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials })
-}
-
-const fetchDomainMutes = ({ credentials }) => {
- return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials })
-}
-
-const muteDomain = ({ domain, credentials }) => {
- return promisedRequest({
- url: MASTODON_DOMAIN_BLOCKS_URL,
- method: 'POST',
- payload: { domain },
- credentials,
- })
-}
-
-const unmuteDomain = ({ domain, credentials }) => {
- return promisedRequest({
- url: MASTODON_DOMAIN_BLOCKS_URL,
- method: 'DELETE',
- payload: { domain },
- credentials,
- })
-}
-
-const dismissNotification = ({ credentials, id }) => {
- return promisedRequest({
- url: MASTODON_DISMISS_NOTIFICATION_URL(id),
- method: 'POST',
- payload: { id },
- credentials,
- })
-}
-
-const adminFetchAnnouncements = ({ credentials }) => {
- return promisedRequest({ url: PLEROMA_ANNOUNCEMENTS_URL, credentials })
-}
-
-const fetchAnnouncements = ({ credentials }) => {
- return promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials })
-}
-
-const dismissAnnouncement = ({ id, credentials }) => {
- return 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 = {} },
- 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
-}
-
-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,
-})
-
-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) }
- })
-}
-
-const getOrCreateChat = ({ accountId, credentials }) => {
- return promisedRequest({
- url: PLEROMA_CHAT_URL(accountId),
- method: 'POST',
- credentials,
- })
-}
-
-const chatMessages = ({ id, credentials, maxId, 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,
- method: 'GET',
- credentials,
- })
-}
-
-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,
- })
-}
-
-const readChat = ({ id, lastReadId, credentials }) => {
- return promisedRequest({
- url: PLEROMA_CHAT_READ_URL(id),
- method: 'POST',
- payload: {
- last_read_id: lastReadId,
- },
- credentials,
- })
-}
-
-const deleteChatMessage = ({ chatId, messageId, credentials }) => {
- return 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 }) => {
- 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,
- }
- }
- })
-}
-
-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, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- url: instance,
- name: packName,
- as,
- }),
- })
-}
-
-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), {
- method: 'PATCH',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ metadata: newData }),
- })
-}
-
-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, {
- method: 'DELETE',
- headers: authHeaders(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/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
deleted file mode 100644
index adc18ef7f..000000000
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ /dev/null
@@ -1,75 +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 apiService, {
- getMastodonSocketURI,
- ProcessedWS,
-} 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 = getMastodonSocketURI({}, serv)
- return 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 7b81c19dc..000000000
--- a/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import apiService 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 })
- .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/errors/errors.js b/src/services/errors/errors.js
index 41829eb19..ccbae9b3e 100644
--- a/src/services/errors/errors.js
+++ b/src/services/errors/errors.js
@@ -13,8 +13,10 @@ 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.statusText = body.error || body.errors || body
+ this.details = JSON && JSON.stringify ? JSON.stringify(body) : body
+ this.errorData = body.error || body.errors
+ this.message = this.statusCode + ' - ' + this.statusText
this.error = body // legacy attribute
this.options = options
this.response = response
diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js
index 209a5f0c0..759ba67d9 100644
--- a/src/services/follow_manipulate/follow_manipulate.js
+++ b/src/services/follow_manipulate/follow_manipulate.js
@@ -1,9 +1,19 @@
+import { useOAuthStore } from 'src/stores/oauth.js'
+
+import {
+ fetchUserRelationship,
+ followUser,
+ unfollowUser,
+} from 'src/api/user.js'
+
const fetchRelationship = (attempt, userId, store) =>
new Promise((resolve, reject) => {
setTimeout(() => {
- store.state.api.backendInteractor
- .fetchUserRelationship({ id: userId })
- .then((relationship) => {
+ fetchUserRelationship({
+ id: userId,
+ credentials: useOAuthStore().token,
+ })
+ .then(({ data: relationship }) => {
store.commit('updateUserRelationship', [relationship])
return relationship
})
@@ -25,40 +35,33 @@ 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])
-
- 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 { data: updated } = await followUser({
+ id: userId,
+ credentials: useOAuthStore().token,
})
-export const requestUnfollow = (userId, store) =>
- new Promise((resolve) => {
- store.state.api.backendInteractor
- .unfollowUser({ id: userId })
- .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 { data: updated } = await unfollowUser({
+ id: userId,
+ credentials: useOAuthStore().token,
})
+
+ return await store.commit('updateUserRelationship', [updated])
+}
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..e49206fcd 100644
--- a/src/services/follow_request_fetcher/follow_request_fetcher.service.js
+++ b/src/services/follow_request_fetcher/follow_request_fetcher.service.js
@@ -1,11 +1,10 @@
-import apiService from '../api/api.service.js'
-import { promiseInterval } from '../promise_interval/promise_interval.js'
+import { fetchFollowRequests } from 'src/api/user.js'
+import { promiseInterval } from 'src/services/promise_interval/promise_interval.js'
const fetchAndUpdate = ({ store, credentials }) => {
- return apiService
- .fetchFollowRequests({ credentials })
+ return fetchFollowRequests({ credentials })
.then(
- (requests) => {
+ ({ data: requests }) => {
store.commit('setFollowRequests', requests)
store.commit('addNewUsers', requests)
},
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 c395ef93b..000000000
--- a/src/services/lists_fetcher/lists_fetcher.service.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import apiService 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 })
- .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/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
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/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
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/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index c1a9e1a2f..8530c468c 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -1,11 +1,11 @@
-import apiService from '../api/api.service.js'
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'
+import { fetchTimeline } from 'src/api/timelines.js'
+
const update = ({ store, notifications, older }) => {
store.dispatch('addNewNotifications', { notifications, older })
}
@@ -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 })
}
@@ -80,29 +80,25 @@ 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 (
- 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',
diff --git a/src/services/promise_interval/promise_interval.js b/src/services/promise_interval/promise_interval.js
index 46ac68996..d9396c643 100644
--- a/src/services/promise_interval/promise_interval.js
+++ b/src/services/promise_interval/promise_interval.js
@@ -4,32 +4,34 @@
// 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 = async () => {
+ while (!stopped) {
+ await promiseCall()
+ const { timeoutId, promise } = wait(interval)
+ timeout = timeoutId
+ await promise()
+ }
+ }
+
+ loop().then()
return { stop: stopFetcher }
}
diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js
index 021c31ef8..9a26bd12f 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 'src/api/user.js'
const postStatus = ({
store,
@@ -18,37 +23,30 @@ 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,
- })
- .then((data) => {
- if (!data.error && !preview) {
- store.dispatch('addNewStatuses', {
- statuses: [data],
- timeline: 'friends',
- showImmediately: true,
- noIdUpdate: true, // To prevent missing notices on next pull.
- })
- }
- return data
- })
- .catch((err) => {
- return {
- error: err.message,
- }
- })
+ return apiPostStatus({
+ credentials: store.state.users.currentUser.credentials,
+ status,
+ spoilerText,
+ visibility,
+ sensitive,
+ mediaIds,
+ inReplyToStatusId,
+ quoteId,
+ contentType,
+ poll,
+ preview,
+ idempotencyKey,
+ }).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 = ({
@@ -63,26 +61,24 @@ const editStatus = ({
}) => {
const mediaIds = map(media, 'id')
- return apiService
- .editStatus({
- id: statusId,
- credentials: store.state.users.currentUser.credentials,
- status,
- spoilerText,
- sensitive,
- poll,
- 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.
- })
- }
+ return apiEditStatus({
+ id: statusId,
+ credentials: store.state.users.currentUser.credentials,
+ status,
+ spoilerText,
+ sensitive,
+ poll,
+ mediaIds,
+ contentType,
+ })
+ .then(({ data }) => {
+ store.dispatch('addNewStatuses', {
+ statuses: [data],
+ timeline: 'friends',
+ showImmediately: true,
+ noIdUpdate: true, // To prevent missing notices on next pull.
+ })
+
return data
})
.catch((err) => {
@@ -95,12 +91,14 @@ const editStatus = ({
const uploadMedia = ({ store, formData }) => {
const credentials = store.state.users.currentUser.credentials
- return apiService.uploadMedia({ credentials, formData })
+ return apiUploadMedia({ credentials, formData }).then(({ data }) => data)
}
const setMediaDescription = ({ store, id, description }) => {
const credentials = store.state.users.currentUser.credentials
- return apiService.setMediaDescription({ 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 68991addf..80dbc75d0 100644
--- a/src/services/timeline_fetcher/timeline_fetcher.service.js
+++ b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -1,13 +1,13 @@
import { camelCase } from 'lodash'
-import apiService from '../api/api.service.js'
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'
+import { fetchTimeline } from 'src/api/timelines.js'
+
const update = ({
store,
statuses,
@@ -35,13 +35,13 @@ const fetchAndUpdate = ({
timeline = 'friends',
older = false,
showImmediately = false,
- userId = false,
- listId = false,
- statusId = false,
- bookmarkFolderId = false,
- tag = false,
- until,
- since,
+ userId,
+ listId,
+ statusId,
+ bookmarkFolderId,
+ tag,
+ maxId,
+ sinceId,
}) => {
const args = { timeline, credentials }
const rootState = store.rootState || store.state
@@ -51,12 +51,13 @@ const fetchAndUpdate = ({
const loggedIn = !!rootState.users.currentUser
if (older) {
- args.until = until || timelineData.minId
+ // When minId = 0 we need to fetch without maxId param
+ args.maxId = maxId || timelineData.minId || null
} 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
}
}
@@ -75,17 +76,8 @@ const fetchAndUpdate = ({
const numStatusesBeforeFetch = timelineData.statuses.length
- return apiService
- .fetchTimeline(args)
+ 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 &&
@@ -107,6 +99,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',
@@ -120,11 +116,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)]
diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js
index b07315034..5aa91c819 100644
--- a/src/stores/admin_settings.js
+++ b/src/stores/admin_settings.js
@@ -1,6 +1,38 @@
import { cloneDeep, differenceWith, flatten, get, isEqual, set } from 'lodash'
import { defineStore } from 'pinia'
+import { useOAuthStore } from 'src/stores/oauth.js'
+
+import {
+ addNewEmojiFile,
+ changeStatusScope,
+ createEmojiPack,
+ deleteAccounts,
+ deleteEmojiPack,
+ disableMFA,
+ downloadRemoteEmojiPack,
+ downloadRemoteEmojiPackZIP,
+ getAvailableFrontends,
+ getInstanceConfigDescriptions,
+ getInstanceDBConfig,
+ getUserData,
+ importEmojiFromFS,
+ installFrontend,
+ listRemoteEmojiPacks,
+ listStatuses,
+ listUsers,
+ pushInstanceDBConfig,
+ reloadEmoji,
+ requirePasswordChange,
+ resendConfirmationEmail,
+ setUsersActivationStatus,
+ setUsersApprovalStatus,
+ setUsersConfirmationStatus,
+ setUsersRight,
+ setUsersSuggestionStatus,
+ setUsersTags,
+} 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 = {
@@ -21,7 +53,6 @@ export const newUserFlags = {
export const useAdminSettingsStore = defineStore('adminSettings', {
state: () => ({
...cloneDeep(defaultState),
- backendInteractor: window.vuex.state.api.backendInteractor,
}),
actions: {
// Configuration Stuff
@@ -54,25 +85,31 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
},
loadAdminStuff() {
- this.backendInteractor.fetchInstanceDBConfig().then((backendDbConfig) => {
- if (backendDbConfig.error) {
- if (backendDbConfig.error.status === 400) {
- backendDbConfig.error.json().then((errorJson) => {
- if (/configurable_from_database/.test(errorJson.error)) {
- this.setInstanceAdminNoDbConfig()
- }
- })
- }
- } else {
- this.setInstanceAdminSettings({ backendDbConfig })
- }
+ getInstanceDBConfig({
+ credentials: useOAuthStore().token,
})
+ .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) {
- this.backendInteractor
- .fetchInstanceConfigDescriptions()
- .then((backendDescriptions) =>
- this.setInstanceAdminDescriptions({ backendDescriptions }),
- )
+ getInstanceConfigDescriptions({
+ credentials: useOAuthStore().token,
+ }).then(({ data: backendDescriptions }) =>
+ this.setInstanceAdminDescriptions({
+ credentials: useOAuthStore().token,
+ backendDescriptions,
+ }),
+ )
}
},
setInstanceAdminSettings({ backendDbConfig }) {
@@ -203,17 +240,23 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
}
})
- window.vuex.state.api.backendInteractor
- .pushInstanceDBConfig({
- payload: {
- configs: changed,
- },
- })
+ pushInstanceDBConfig({
+ credentials: useOAuthStore().token,
+ payload: {
+ configs: changed,
+ },
+ })
.then(() =>
- window.vuex.state.api.backendInteractor.fetchInstanceDBConfig(),
+ getInstanceDBConfig({
+ credentials: useOAuthStore().token,
+ }).then(({ data }) => data),
)
.then((backendDbConfig) =>
- this.setInstanceAdminSettings({ backendDbConfig }),
+ this.setInstanceAdminSettings({
+ credentials: useOAuthStore().token,
+
+ backendDbConfig,
+ }),
)
},
pushAdminSetting({ path, value }) {
@@ -234,23 +277,28 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
}
}
- window.vuex.state.api.backendInteractor
- .pushInstanceDBConfig({
- payload: {
- configs: [
- {
- group,
- key,
- value: convert(clone),
- },
- ],
- },
- })
+ pushInstanceDBConfig({
+ credentials: useOAuthStore().token,
+ payload: {
+ configs: [
+ {
+ group,
+ key,
+ value: convert(clone),
+ },
+ ],
+ },
+ })
.then(() =>
- window.vuex.state.api.backendInteractor.fetchInstanceDBConfig(),
+ getInstanceDBConfig({
+ credentials: useOAuthStore().token,
+ }).then(({ data }) => data),
)
.then((backendDbConfig) =>
- this.setInstanceAdminSettings({ backendDbConfig }),
+ this.setInstanceAdminSettings({
+ credentials: useOAuthStore().token,
+ backendDbConfig,
+ }),
)
},
resetAdminSetting({ path }) {
@@ -260,21 +308,23 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
this.modifiedPaths.delete(path)
- return window.vuex.state.api.backendInteractor
- .pushInstanceDBConfig({
- payload: {
- configs: [
- {
- group,
- key,
- delete: true,
- subkeys: [subkey],
- },
- ],
- },
- })
+ return pushInstanceDBConfig({
+ credentials: useOAuthStore().token,
+ payload: {
+ configs: [
+ {
+ group,
+ key,
+ delete: true,
+ subkeys: [subkey],
+ },
+ ],
+ },
+ })
.then(() =>
- window.vuex.state.api.backendInteractor.fetchInstanceDBConfig(),
+ getInstanceDBConfig({
+ credentials: useOAuthStore().token,
+ }).then(({ data }) => data),
)
.then((backendDbConfig) =>
this.setInstanceAdminSettings({ backendDbConfig }),
@@ -283,9 +333,11 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
// Frontends Stuff
loadFrontendsStuff() {
- this.backendInteractor
- .fetchAvailableFrontends()
- .then((frontends) => this.setAvailableFrontends({ frontends }))
+ getAvailableFrontends({
+ credentials: useOAuthStore().token,
+ }).then(({ data: frontends }) =>
+ this.setAvailableFrontends({ frontends }),
+ )
},
setAvailableFrontends({ frontends }) {
@@ -300,12 +352,20 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
})
},
+ installFrontend() {
+ return installFrontend({
+ credentials: useOAuthStore().token,
+ }).then(({ data }) => data)
+ },
+
// Statuses stuff
async fetchStatuses(opts) {
- const { total, activities } =
- await this.backendInteractor.adminListStatuses({
- opts,
- })
+ const {
+ data: { total, activities },
+ } = await listStatuses({
+ credentials: useOAuthStore().token,
+ opts,
+ })
const statuses = activities.map(parseStatus)
@@ -317,17 +377,21 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
}
},
async changeStatusScope(opts) {
- const raw = await this.backendInteractor.adminChangeStatusScope({
+ 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 this.backendInteractor.adminListUsers({
+ const {
+ data: { users, count },
+ } = await listUsers({
+ credentials: useOAuthStore().token,
opts,
})
@@ -344,19 +408,26 @@ 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 })
- window.vuex.commit('updateUserAdminData', { user: result })
+ const result = await api({
+ credentials: useOAuthStore().token,
+ screen_name,
+ })
+
+ window.vuex.commit('updateUserAdminData', { user: result.data })
},
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: useOAuthStore().token,
+ screen_names,
+ })
- resultUserIds.forEach((userId) => {
+ resultUserIds.data.forEach((userId) => {
window.vuex.dispatch(
'markStatusesAsDeleted',
(status) => userId === status.user.id,
@@ -369,28 +440,34 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
resendConfirmationEmail({ users }) {
const screen_names = users.map((u) => u.screen_name)
- return this.backendInteractor.adminResendConfirmationEmail({
+ return resendConfirmationEmail({
+ credentials: useOAuthStore().token,
screen_names,
- })
+ }).then(({ data }) => data)
},
requirePasswordChange({ users }) {
const screen_names = users.map((u) => u.screen_name)
- return this.backendInteractor.adminRequirePasswordChange({
+ return requirePasswordChange({
+ credentials: useOAuthStore().token,
screen_names,
- })
+ }).then(({ data }) => data)
},
// Singular only!
disableMFA({ user }) {
const { screen_name } = user
- return this.backendInteractor.adminDisableMFA({ screen_name })
+ return disableMFA({
+ credentials: useOAuthStore().token,
+ screen_name,
+ }).then(({ data }) => data)
},
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: useOAuthStore().token,
screen_names,
tags,
value,
@@ -402,9 +479,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: useOAuthStore().token,
screen_names,
right,
value,
@@ -416,35 +494,40 @@ 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: useOAuthStore().token,
screen_names,
value,
})
- resultUsers.forEach((user) => {
+ resultUsers.data.forEach((user) => {
window.vuex.commit('updateUserAdminData', { user })
})
},
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: useOAuthStore().token,
screen_names,
value,
})
- resultUsers.forEach((user) => {
+ resultUsers.data.forEach((user) => {
window.vuex.commit('updateUserAdminData', { user })
})
},
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: useOAuthStore().token,
+ screen_names,
+ })
users.forEach((user) => {
this.getUserData({ user })
@@ -452,15 +535,81 @@ 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: useOAuthStore().token,
screen_names,
})
- resultUsers.forEach((user) => {
+ resultUsers.data.forEach((user) => {
window.vuex.commit('updateUserAdminData', { user })
})
},
+ reloadEmoji() {
+ return reloadEmoji({ credentials: useOAuthStore().token }).then(
+ ({ data }) => data,
+ )
+ },
+ importEmojiFromFS() {
+ return importEmojiFromFS({ credentials: useOAuthStore().token }).then(
+ ({ data }) => data,
+ )
+ },
+ listEmojiPacks(params) {
+ return listEmojiPacks({
+ ...params,
+ credentials: useOAuthStore().token,
+ }).then(({ data }) => data)
+ },
+ listRemoteEmojiPacks(params) {
+ return listRemoteEmojiPacks({
+ ...params,
+ credentials: useOAuthStore().token,
+ }).then(({ data }) => data)
+ },
+ addNewEmojiFile({ packName, file, shortcode, filename }) {
+ return addNewEmojiFile({
+ packName,
+ file,
+ shortcode,
+ filename,
+ credentials: useOAuthStore().token,
+ }).then(({ data }) => data)
+ },
+ downloadRemoteEmojiPack({ instance, packName, as }) {
+ return downloadRemoteEmojiPack({
+ instance,
+ 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 deleteEmojiPack({
+ 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 cb325dadd..a5f3e4d8e 100644
--- a/src/stores/announcements.js
+++ b/src/stores/announcements.js
@@ -1,5 +1,9 @@
import { defineStore } from 'pinia'
+import { useOAuthStore } from 'src/stores/oauth.js'
+
+import { dismissAnnouncement, getAnnouncements } from 'src/api/user.js'
+
const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5
export const useAnnouncementsStore = defineStore('announcements', {
@@ -7,6 +11,8 @@ export const useAnnouncementsStore = defineStore('announcements', {
announcements: [],
supportsAnnouncements: true,
fetchAnnouncementsTimer: undefined,
+ adminActions: {},
+ userActions: {},
}),
getters: {
unreadAnnouncementCount() {
@@ -21,29 +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 getAnnouncements = async () => {
- if (!isAdmin) {
- return window.vuex.state.api.backendInteractor.fetchAnnouncements()
+ try {
+ if (isAdmin) {
+ this.adminActions = await import('src/api/admin.js')
+ } else {
+ const all = await getAnnouncements({
+ credentials: useOAuthStore().token,
+ })
+ return all.data
}
- const all =
- await window.vuex.state.api.backendInteractor.adminFetchAnnouncements()
- const visible =
- await window.vuex.state.api.backendInteractor.fetchAnnouncements()
+ 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]
@@ -56,35 +70,30 @@ 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 getAnnouncements()
- .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 window.vuex.state.api.backendInteractor
- .dismissAnnouncement({ id })
- .then(() => {
- const index = this.announcements.findIndex((a) => a.id === id)
+ return 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) {
@@ -105,22 +114,38 @@ export const useAnnouncementsStore = defineStore('announcements', {
clearInterval(interval)
},
postAnnouncement({ content, startsAt, endsAt, allDay }) {
- return window.vuex.state.api.backendInteractor
- .postAnnouncement({ content, startsAt, endsAt, allDay })
+ return this.adminActions
+ .postAnnouncement({
+ credentials: useOAuthStore().token,
+ 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 })
+ return this.adminActions
+ .editAnnouncement({
+ id,
+ content,
+ startsAt,
+ endsAt,
+ allDay,
+ credentials: useOAuthStore().token,
+ })
.then(() => {
return this.fetchAnnouncements()
})
},
deleteAnnouncement(id) {
- return window.vuex.state.api.backendInteractor
- .deleteAnnouncement({ id })
+ return this.adminActions
+ .deleteAnnouncement({
+ id,
+ credentials: useOAuthStore().token,
+ })
.then(() => {
return this.fetchAnnouncements()
})
diff --git a/src/stores/bookmark_folders.js b/src/stores/bookmark_folders.js
index 028322f9d..713a21d00 100644
--- a/src/stores/bookmark_folders.js
+++ b/src/stores/bookmark_folders.js
@@ -1,6 +1,16 @@
import { find, remove } from 'lodash'
import { defineStore } from 'pinia'
+import { useOAuthStore } from 'src/stores/oauth.js'
+
+import {
+ createBookmarkFolder,
+ deleteBookmarkFolder,
+ fetchBookmarkFolders,
+ updateBookmarkFolder,
+} from 'src/api/user.js'
+import { promiseInterval } from 'src/services/promise_interval/promise_interval.js'
+
export const useBookmarkFoldersStore = defineStore('bookmarkFolders', {
state: () => ({
allFolders: [],
@@ -16,6 +26,20 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', {
},
},
actions: {
+ startFetching() {
+ this.fetcher = promiseInterval(() => {
+ fetchBookmarkFolders({
+ credentials: useOAuthStore().token,
+ })
+ .then(({ data: folders }) => this.setBookmarkFolders(folders))
+ .catch((e) => {
+ console.error(e)
+ })
+ }, 240000)
+ },
+ stopFetching() {
+ this.fetcher?.stop()
+ },
setBookmarkFolders(value) {
this.allFolders = value
},
@@ -30,23 +54,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: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ folderId,
+ name,
+ emoji,
+ }).then(({ data: folder }) => {
+ this.setBookmarkFolder(folder)
+ return folder
+ })
},
deleteBookmarkFolder({ folderId }) {
- window.vuex.state.api.backendInteractor.deleteBookmarkFolder({ folderId })
+ deleteBookmarkFolder({
+ folderId,
+ credentials: useOAuthStore().token,
+ })
remove(this.allFolders, (folder) => folder.id === folderId)
},
},
diff --git a/src/stores/emoji.js b/src/stores/emoji.js
index e28143300..3316f8328 100644
--- a/src/stores/emoji.js
+++ b/src/stores/emoji.js
@@ -2,7 +2,9 @@ import { merge } from 'lodash'
import { defineStore } from 'pinia'
import { useInstanceStore } from 'src/stores/instance.js'
+import { useOAuthStore } from 'src/stores/oauth.js'
+import { listEmojiPacks } from 'src/api/public.js'
import { ensureFinalFallback } from 'src/i18n/languages.js'
import { annotationsLoader } from 'virtual:pleroma-fe/emoji-annotations'
@@ -183,13 +185,14 @@ 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,
+ (params) =>
+ listEmojiPacks({
+ ...params,
+ credentials: useOAuthStore().token,
+ }).then(({ data }) => data),
)
this.adminPacksLocalLoading = false
},
@@ -206,12 +209,7 @@ export const useEmojiStore = defineStore('emoji', {
page: 1,
pageSize: 0,
})
- .then((data) => data.json())
.then((data) => {
- if (data.error !== undefined) {
- return Promise.reject(data.error)
- }
-
const promises = []
for (let i = 0; i < Math.ceil(data.count / pageSize); i++) {
@@ -220,15 +218,9 @@ export const useEmojiStore = defineStore('emoji', {
instance,
page: i,
pageSize,
- })
- .then((data) => data.json())
- .then((pageData) => {
- if (pageData.error !== undefined) {
- return Promise.reject(pageData.error)
- }
-
- return pageData.packs
- }),
+ }).then((pageData) => {
+ return pageData.packs
+ }),
)
}
@@ -247,7 +239,7 @@ export const useEmojiStore = defineStore('emoji', {
}, {})
})
.catch((data) => {
- this.displayError(data)
+ console.error(data)
})
},
diff --git a/src/stores/instance.js b/src/stores/instance.js
index 54b3cf43c..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 {
@@ -11,10 +11,11 @@ import {
LOCAL_DEFAULT_CONFIG_DEFINITIONS,
validateSetting,
} from '../modules/default_config_state.js'
-import apiService from '../services/api/api.service.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 = {
@@ -210,9 +211,10 @@ export const useInstanceStore = defineStore('instance', {
},
async getKnownDomains() {
try {
- this.knownDomains = await apiService.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 b33a119cc..37471d46e 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 { useOAuthStore } from 'src/stores/oauth.js'
+
+import {
+ addAccountsToList,
+ createList,
+ deleteList,
+ fetchLists,
+ getList,
+ getListAccounts,
+ removeAccountsFromList,
+ updateList,
+} from 'src/api/user.js'
+import { promiseInterval } from 'src/services/promise_interval/promise_interval.js'
+
export const useListsStore = defineStore('lists', {
state: () => ({
+ fetcher: null,
allLists: [],
allListsObject: {},
}),
@@ -18,34 +33,57 @@ export const useListsStore = defineStore('lists', {
},
},
actions: {
+ startFetching() {
+ this.fetcher = promiseInterval(() => {
+ fetchLists({
+ credentials: useOAuthStore().token,
+ })
+ .then(({ data: lists }) => this.setLists(lists))
+ .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
- })
+ async createList({ title }) {
+ return await createList({
+ title,
+ credentials: useOAuthStore().token,
+ }).then(({ data: 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 }))
+ async fetchList({ listId }) {
+ return await getList({
+ listId,
+ credentials: useOAuthStore().token,
+ }).then(({ data: 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
- })
+ async fetchListAccounts({ listId }) {
+ return await getListAccounts({
+ listId,
+ credentials: useOAuthStore().token,
+ }).then(({ data: 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 })
+ async setList({ listId, title }) {
+ await updateList({
+ listId,
+ title,
+ credentials: useOAuthStore().token,
+ })
if (!this.allListsObject[listId]) {
this.allListsObject[listId] = { accountIds: [] }
@@ -59,7 +97,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))
@@ -67,47 +105,62 @@ export const useListsStore = defineStore('lists', {
this.allListsObject[listId] = { accountIds: [] }
}
this.allListsObject[listId].accountIds = accountIds
+ const promises = []
if (added.length > 0) {
- window.vuex.state.api.backendInteractor.addAccountsToList({
- listId,
- accountIds: added,
- })
+ promises.push(
+ addAccountsToList({
+ listId,
+ accountIds: added,
+ credentials: useOAuthStore().token,
+ }),
+ )
}
if (removed.length > 0) {
- window.vuex.state.api.backendInteractor.removeAccountsFromList({
- listId,
- accountIds: removed,
- })
+ promises.push(
+ removeAccountsFromList({
+ listId,
+ accountIds: removed,
+ credentials: useOAuthStore().token,
+ }),
+ )
}
+ await Promise.all(promises)
},
- 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
- })
+ async addListAccount({ listId, accountId }) {
+ return await addAccountsToList({
+ listId,
+ accountIds: [accountId],
+ credentials: useOAuthStore().token,
+ }).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]
+ async removeListAccount({ listId, accountId }) {
+ return await removeAccountsFromList({
+ listId,
+ accountIds: [accountId],
+ credentials: useOAuthStore().token,
+ }).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 })
+ async deleteList({ listId }) {
+ await deleteList({
+ listId,
+ credentials: useOAuthStore().token,
+ })
delete this.allListsObject[listId]
remove(this.allLists, (list) => list.id === listId)
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/oauth.js b/src/stores/oauth.js
index 2a79c2fa9..35cd67afb 100644
--- a/src/stores/oauth.js
+++ b/src/stores/oauth.js
@@ -2,11 +2,7 @@ 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, verifyAppToken } from 'src/api/oauth.js'
// status codes about verifyAppToken (GET /api/v1/apps/verify_credentials)
const isAppTokenRejected = (error) =>
@@ -41,10 +37,7 @@ export const useOAuthStore = defineStore('oauth', {
userToken: false,
}),
getters: {
- getToken() {
- return this.userToken || this.appToken
- },
- getUserToken() {
+ token() {
return this.userToken
},
},
@@ -64,9 +57,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.
@@ -88,8 +81,8 @@ export const useOAuthStore = defineStore('oauth', {
clientSecret: this.clientSecret,
instance,
})
- this.setAppToken(res.access_token)
- return res.access_token
+ this.setAppToken(res.data.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)
@@ -97,8 +90,7 @@ export const useOAuthStore = defineStore('oauth', {
if (this.appToken) {
try {
await verifyAppToken({
- instance: useInstanceStore().server,
- appToken: this.appToken,
+ credentials: this.appToken,
})
return this.appToken
} catch (e) {
diff --git a/src/stores/oauth_tokens.js b/src/stores/oauth_tokens.js
index ae9b396ac..4d18ffae7 100644
--- a/src/stores/oauth_tokens.js
+++ b/src/stores/oauth_tokens.js
@@ -1,25 +1,35 @@
import { defineStore } from 'pinia'
+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: [],
}),
actions: {
fetchTokens() {
- window.vuex.state.api.backendInteractor
- .fetchOAuthTokens()
- .then((tokens) => {
- this.swapTokens(tokens)
- })
+ fetchOAuthTokens({
+ credentials: useOAuthStore().token,
+ }).then(({ data: 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: useOAuthStore().token,
+ }).then(({ status }) => {
+ if (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..e2f4b0ab2 100644
--- a/src/stores/polls.js
+++ b/src/stores/polls.js
@@ -1,6 +1,11 @@
import { merge } from 'lodash'
import { defineStore } from 'pinia'
+import { useOAuthStore } from 'src/stores/oauth.js'
+
+import { fetchPoll } from 'src/api/public.js'
+import { vote } from 'src/api/user.js'
+
export const usePollsStore = defineStore('polls', {
state: () => ({
// Contains key = id, value = number of trackers for this poll
@@ -19,16 +24,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: useOAuthStore().token,
+ }).then(({ data: poll }) => {
+ setTimeout(() => {
+ if (this.trackedPolls[pollId]) {
+ this.updateTrackedPoll(pollId)
+ }
+ }, 30 * 1000)
+ this.mergeOrAddPoll(poll)
+ })
},
trackPoll(pollId) {
if (!this.trackedPolls[pollId]) {
@@ -50,12 +56,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: useOAuthStore().token,
+ }).then(({ data: poll }) => {
+ this.mergeOrAddPoll(poll)
+ return poll
+ })
},
},
})
diff --git a/src/stores/reports.js b/src/stores/reports.js
index d3acebcb4..7d319d8e3 100644
--- a/src/stores/reports.js
+++ b/src/stores/reports.js
@@ -2,6 +2,9 @@ import { filter } from 'lodash'
import { defineStore } from 'pinia'
import { useInterfaceStore } from 'src/stores/interface.js'
+import { useOAuthStore } from 'src/stores/oauth.js'
+
+import { setReportState } from 'src/api/admin.js'
export const useReportsStore = defineStore('reports', {
state: () => ({
@@ -38,18 +41,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: useOAuthStore().token,
+ }).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
diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js
index 3010fc738..47c6978d5 100644
--- a/src/stores/sync_config.js
+++ b/src/stores/sync_config.js
@@ -20,9 +20,10 @@ 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'
+import { updateProfileJSON } from 'src/api/user.js'
import { storage } from 'src/lib/storage.js'
import {
makeUndefined,
@@ -231,9 +232,17 @@ 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)
if (path.startsWith('collections')) {
const lastRemoveIndex = findLastIndex(
@@ -268,9 +277,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)
}
@@ -789,7 +805,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: useOAuthStore().token,
+ })
},
},
persist: {
diff --git a/src/stores/user_highlight.js b/src/stores/user_highlight.js
index f41c41628..759ebd509 100644
--- a/src/stores/user_highlight.js
+++ b/src/stores/user_highlight.js
@@ -1,19 +1,18 @@
import {
merge as _merge,
- clamp,
clone,
cloneDeep,
- findLastIndex,
flatten,
- get,
groupBy,
isEqual,
takeRight,
- uniqWith,
} from 'lodash'
import { defineStore } from 'pinia'
import { toRaw } from 'vue'
+import { useOAuthStore } from 'src/stores/oauth.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
@@ -30,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,
@@ -344,12 +332,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: useOAuthStore().token,
+ }).then(({ data: user }) => {
+ this.initUserHighlight(user)
+ this.dirty = false
+ })
},
},
persist: {
diff --git a/test/e2e-playwright/playwright.config.mjs b/test/e2e-playwright/playwright.config.mjs
index 04747ee77..51a4de21e 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,13 @@ 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 --port $PORT --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:
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
diff --git a/test/fixtures/mock_api.js b/test/fixtures/mock_api.js
index 03fb01a64..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'
-// https://mswjs.io/docs/recipes/vitest-browser-mode
-export const injectMswToTest = (defaultHandlers) => {
- const worker = setupWorker(...defaultHandlers)
+import { worker } from './worker.js'
- return testBase.extend({
- worker: [
- // biome-ignore lint: required by vitest
- async ({}, use) => {
- await worker.start()
+export const test = 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 testServer = 'https://test.server.example'
-
-export const authApis = [
- http.post(`${testServer}/api/v1/apps`, () => {
- return HttpResponse.json({
- client_id: 'test-id',
- client_secret: 'test-secret',
- })
- }),
- http.get(`${testServer}/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(`${testServer}/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..e6ed89dc9
--- /dev/null
+++ b/test/fixtures/worker.js
@@ -0,0 +1,5 @@
+import { setupWorker } from 'msw/browser'
+
+export const worker = setupWorker()
+
+window.__test__ = window.__test__ || 'TEST'
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: '',
},
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..11874790a
--- /dev/null
+++ b/test/unit/specs/services/api/helpers.spec.js
@@ -0,0 +1,90 @@
+import { paramsString } from 'src/api/helpers.js'
+
+describe('API Helpers', () => {
+ describe('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..afd17e56b 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,10 @@
+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(
diff --git a/test/unit/specs/stores/lists.spec.js b/test/unit/specs/stores/lists.spec.js
index 083730856..bb1ef12b4 100644
--- a/test/unit/specs/stores/lists.spec.js
+++ b/test/unit/specs/stores/lists.spec.js
@@ -1,22 +1,23 @@
-import { createPinia, setActivePinia } from 'pinia'
-import { createStore } from 'vuex'
+import { createTestingPinia } from '@pinia/testing'
+import { HttpResponse, http } from 'msw'
+import { setActivePinia } from 'pinia'
+
+import { test as it } from '/test/fixtures/mock_api.js'
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,
- },
-})
+import { MASTODON_LIST_ACCOUNTS_URL, MASTODON_LIST_URL } from 'src/api/user.js'
describe('The lists store', () => {
+ let store
+
+ beforeEach(() => {
+ setActivePinia(createTestingPinia({ stubActions: false }))
+ store = useListsStore()
+ })
+
describe('actions', () => {
it('updates array of all lists', () => {
- store.$reset()
const list = { id: '1', title: 'testList' }
store.setLists([list])
@@ -24,12 +25,19 @@ 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()
+ 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' }
- store.setList({ listId: list.id, title: list.title })
+ worker.use(
+ http.put(MASTODON_LIST_URL(':id'), () =>
+ HttpResponse.json({ ok: true }),
+ ),
+ )
+
+ await store.setList({ listId: list.id, title: list.title })
expect(store.allListsObject[list.id]).to.eql({
title: list.title,
accountIds: [],
@@ -37,7 +45,7 @@ describe('The lists store', () => {
expect(store.allLists).to.have.length(1)
expect(store.allLists[0]).to.eql(list)
- store.setList({ listId: modList.id, title: modList.title })
+ await store.setList({ listId: modList.id, title: modList.title })
expect(store.allListsObject[modList.id]).to.eql({
title: modList.title,
accountIds: [],
@@ -46,24 +54,38 @@ describe('The lists store', () => {
expect(store.allLists[0]).to.eql(modList)
})
- it('adds a new list with an array of IDs, updating the IDs for existing lists', () => {
- store.$reset()
+ 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'] }
- store.setListAccounts({ listId: list.id, accountIds: list.accountIds })
+ 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)
- store.setListAccounts({
+ await store.setListAccounts({
listId: modList.id,
accountIds: modList.accountIds,
})
+
expect(store.allListsObject[modList.id].accountIds).to.eql(
modList.accountIds,
)
})
- it('deletes a list', () => {
+ it('deletes a list', async ({ worker }) => {
store.$patch({
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
@@ -72,7 +94,13 @@ 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({})
})
diff --git a/test/unit/specs/stores/oauth.spec.js b/test/unit/specs/stores/oauth.spec.js
index 4664bba02..a06cbb2fd 100644
--- a/test/unit/specs/stores/oauth.spec.js
+++ b/test/unit/specs/stores/oauth.spec.js
@@ -1,27 +1,80 @@
import { createTestingPinia } from '@pinia/testing'
import { HttpResponse, http } from 'msw'
-import { createPinia, setActivePinia } from 'pinia'
+import { setActivePinia } from 'pinia'
-import {
- authApis,
- injectMswToTest,
- testServer,
-} from '/test/fixtures/mock_api.js'
+import { test as it } from '/test/fixtures/mock_api.js'
-import { useInstanceStore } from 'src/stores/instance.js'
import { useOAuthStore } from 'src/stores/oauth.js'
-const test = injectMswToTest(authApis)
+import {
+ MASTODON_APP_URL,
+ MASTODON_APP_VERIFY_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(() => {
setActivePinia(createTestingPinia({ stubActions: false }))
- useInstanceStore().server = testServer
})
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,
+ }) => {
+ worker.use(
+ http.post(MASTODON_APP_URL, () => {
+ return HttpResponse.text('Throttled', { status: 429 })
+ }),
+ )
+
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')
@@ -29,9 +82,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(`${testServer}/api/v1/apps`, () => {
+ http.post(MASTODON_APP_URL, () => {
return HttpResponse.text('Throttled', { status: 429 })
}),
)
@@ -45,7 +98,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')
@@ -54,9 +108,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(`${testServer}/api/v1/apps`, () => {
+ http.post(MASTODON_APP_URL, () => {
return HttpResponse.text('Should not call this API', { status: 400 })
}),
)
@@ -74,7 +128,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'
@@ -84,7 +139,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'
@@ -95,14 +153,16 @@ 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())
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'
@@ -111,11 +171,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(`${testServer}/api/v1/apps`, () => {
+ ...authApis(),
+ http.post(MASTODON_APP_URL, () => {
return HttpResponse.text('Should not call this API', { status: 400 })
}),
)
@@ -129,11 +190,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(`${testServer}/api/v1/apps`, () => {
+ ...authApis(),
+ http.post(MASTODON_APP_URL, () => {
return HttpResponse.text('Should not call this API', { status: 400 })
}),
)
@@ -146,7 +208,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'
@@ -158,7 +223,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'
@@ -171,9 +239,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(`${testServer}/api/v1/apps`, () => {
+ http.post(MASTODON_APP_URL, () => {
return HttpResponse.text('Throttled', { status: 429 })
}),
)
@@ -182,17 +250,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(`${testServer}/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/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 5b7c22d57..beff5fe76 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,8 +1,8 @@
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'
import { defineConfig } from 'vite'
import eslint from 'vite-plugin-eslint2'
import stylelint from 'vite-plugin-stylelint'
@@ -72,6 +72,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': {
@@ -79,6 +80,14 @@ export default defineConfig(async ({ mode, command }) => {
changeOrigin: true,
cookieDomainRewrite: 'localhost',
ws: true,
+ rewriteWsOrigin: true,
+ },
+ '/auth': {
+ // Mastodon password reset lives here
+ target,
+ changeOrigin: true,
+ cookieDomainRewrite: 'localhost',
+ ws: true,
},
'/nodeinfo': {
target,
@@ -90,20 +99,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'
@@ -233,8 +243,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..b60f88b7b 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"
@@ -7586,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"
@@ -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"