aboutsummaryrefslogtreecommitdiff
path: root/src/services
diff options
context:
space:
mode:
Diffstat (limited to 'src/services')
-rw-r--r--src/services/api/api.service.js154
-rw-r--r--src/services/backend_interactor_service/backend_interactor_service.js247
-rw-r--r--src/services/follow_manipulate/follow_manipulate.js2
-rw-r--r--src/services/new_api/mfa.js12
-rw-r--r--src/services/new_api/oauth.js4
-rw-r--r--src/services/timeline_fetcher/timeline_fetcher.service.js5
6 files changed, 174 insertions, 250 deletions
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 8f5eb416..ef0267aa 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1,4 +1,4 @@
-import { each, map, concat, last } from 'lodash'
+import { each, map, concat, last, get } from 'lodash'
import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
import 'whatwg-fetch'
import { RegistrationError, StatusCodeError } from '../errors/errors'
@@ -12,7 +12,8 @@ const CHANGE_EMAIL_URL = '/api/pleroma/change_email'
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
const TAG_USER_URL = '/api/pleroma/admin/users/tag'
const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
-const ACTIVATION_STATUS_URL = screenName => `/api/pleroma/admin/users/${screenName}/activation_status`
+const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate'
+const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate'
const ADMIN_USERS_URL = '/api/pleroma/admin/users'
const SUGGESTIONS_URL = '/api/v1/suggestions'
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
@@ -22,7 +23,7 @@ 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/account/mfa/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'
@@ -71,6 +72,7 @@ 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_STREAMING = '/api/v1/streaming'
const oldfetch = window.fetch
@@ -450,20 +452,26 @@ const deleteRight = ({ right, credentials, ...user }) => {
})
}
-const setActivationStatus = ({ status, credentials, ...user }) => {
- const screenName = user.screen_name
- const body = {
- status: status
- }
-
- const headers = authHeaders(credentials)
- headers['Content-Type'] = 'application/json'
+const activateUser = ({ credentials, user: { screen_name: nickname } }) => {
+ return promisedRequest({
+ url: ACTIVATE_USER_URL,
+ method: 'PATCH',
+ credentials,
+ payload: {
+ nicknames: [nickname]
+ }
+ }).then(response => get(response, 'users.0'))
+}
- return fetch(ACTIVATION_STATUS_URL(screenName), {
- method: 'PUT',
- headers: headers,
- body: JSON.stringify(body)
- })
+const deactivateUser = ({ credentials, user: { screen_name: nickname } }) => {
+ return promisedRequest({
+ url: DEACTIVATE_USER_URL,
+ method: 'PATCH',
+ credentials,
+ payload: {
+ nicknames: [nickname]
+ }
+ }).then(response => get(response, 'users.0'))
}
const deleteUser = ({ credentials, ...user }) => {
@@ -529,16 +537,24 @@ const fetchTimeline = ({
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
url += `?${queryString}`
-
+ let status = ''
+ let statusText = ''
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => {
- if (data.ok) {
+ status = data.status
+ statusText = data.statusText
+ return data
+ })
+ .then((data) => data.json())
+ .then((data) => {
+ if (!data.error) {
+ return data.map(isNotifications ? parseNotification : parseStatus)
+ } else {
+ data.status = status
+ data.statusText = statusText
return data
}
- throw new Error('Error fetching timeline', data)
})
- .then((data) => data.json())
- .then((data) => data.map(isNotifications ? parseNotification : parseStatus))
}
const fetchPinnedStatuses = ({ id, credentials }) => {
@@ -932,6 +948,99 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
})
}
+export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
+ return Object.entries({
+ ...(credentials
+ ? { access_token: credentials }
+ : {}
+ ),
+ stream,
+ ...args
+ }).reduce((acc, [key, val]) => {
+ return acc + `${key}=${val}&`
+ }, MASTODON_STREAMING + '?')
+}
+
+const MASTODON_STREAMING_EVENTS = new Set([
+ 'update',
+ 'notification',
+ 'delete',
+ 'filters_changed'
+])
+
+// 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'
+}) => {
+ 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)
+ })
+ 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
+ )
+ })
+ /**/
+
+ proxy(socket, 'open')
+ proxy(socket, 'close')
+ proxy(socket, 'message', preprocessor)
+ proxy(socket, 'error')
+
+ // 1000 = Normal Closure
+ eventTarget.close = () => { socket.close(1000, 'Shutting down socket') }
+
+ return eventTarget
+}
+
+export const handleMastoWS = (wsEvent) => {
+ const { data } = wsEvent
+ if (!data) return
+ const parsedEvent = JSON.parse(data)
+ const { event, payload } = parsedEvent
+ if (MASTODON_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 === 'update') {
+ return { event, status: parseStatus(data) }
+ } else if (event === 'notification') {
+ return { event, notification: parseNotification(data) }
+ }
+ } else {
+ console.warn('Unknown event', wsEvent)
+ return null
+ }
+}
+
const apiService = {
verifyCredentials,
fetchTimeline,
@@ -971,7 +1080,8 @@ const apiService = {
deleteUser,
addRight,
deleteRight,
- setActivationStatus,
+ activateUser,
+ deactivateUser,
register,
getCaptcha,
updateAvatar,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index c16bd1f1..b7372ed0 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -1,230 +1,39 @@
-import apiService from '../api/api.service.js'
+import apiService, { getMastodonSocketURI, ProcessedWS } from '../api/api.service.js'
import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js'
import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
-const backendInteractorService = credentials => {
- const fetchStatus = ({ id }) => {
- return apiService.fetchStatus({ id, credentials })
- }
-
- const fetchConversation = ({ id }) => {
- return apiService.fetchConversation({ id, credentials })
- }
-
- const fetchFriends = ({ id, maxId, sinceId, limit }) => {
- return apiService.fetchFriends({ id, maxId, sinceId, limit, credentials })
- }
-
- const exportFriends = ({ id }) => {
- return apiService.exportFriends({ id, credentials })
- }
-
- const fetchFollowers = ({ id, maxId, sinceId, limit }) => {
- return apiService.fetchFollowers({ id, maxId, sinceId, limit, credentials })
- }
-
- const fetchUser = ({ id }) => {
- return apiService.fetchUser({ id, credentials })
- }
-
- const fetchUserRelationship = ({ id }) => {
- return apiService.fetchUserRelationship({ id, credentials })
- }
-
- const followUser = ({ id, reblogs }) => {
- return apiService.followUser({ credentials, id, reblogs })
- }
-
- const unfollowUser = (id) => {
- return apiService.unfollowUser({ credentials, id })
- }
-
- const blockUser = (id) => {
- return apiService.blockUser({ credentials, id })
- }
-
- const unblockUser = (id) => {
- return apiService.unblockUser({ credentials, id })
- }
-
- const approveUser = (id) => {
- return apiService.approveUser({ credentials, id })
- }
-
- const denyUser = (id) => {
- return apiService.denyUser({ credentials, id })
- }
-
- const startFetchingTimeline = ({ timeline, store, userId = false, tag }) => {
+const backendInteractorService = credentials => ({
+ startFetchingTimeline ({ timeline, store, userId = false, tag }) {
return timelineFetcherService.startFetching({ timeline, store, credentials, userId, tag })
- }
+ },
- const startFetchingNotifications = ({ store }) => {
+ startFetchingNotifications ({ store }) {
return notificationsFetcher.startFetching({ store, credentials })
- }
-
- const startFetchingFollowRequest = ({ store }) => {
- return followRequestFetcher.startFetching({ store, credentials })
- }
-
- // eslint-disable-next-line camelcase
- const tagUser = ({ screen_name }, tag) => {
- return apiService.tagUser({ screen_name, tag, credentials })
- }
+ },
- // eslint-disable-next-line camelcase
- const untagUser = ({ screen_name }, tag) => {
- return apiService.untagUser({ screen_name, tag, credentials })
- }
+ fetchAndUpdateNotifications ({ store }) {
+ return notificationsFetcher.fetchAndUpdate({ store, credentials })
+ },
- // eslint-disable-next-line camelcase
- const addRight = ({ screen_name }, right) => {
- return apiService.addRight({ screen_name, right, credentials })
- }
-
- // eslint-disable-next-line camelcase
- const deleteRight = ({ screen_name }, right) => {
- return apiService.deleteRight({ screen_name, right, credentials })
- }
-
- // eslint-disable-next-line camelcase
- const setActivationStatus = ({ screen_name }, status) => {
- return apiService.setActivationStatus({ screen_name, status, credentials })
- }
-
- // eslint-disable-next-line camelcase
- const deleteUser = ({ screen_name }) => {
- return apiService.deleteUser({ screen_name, credentials })
- }
-
- const vote = (pollId, choices) => {
- return apiService.vote({ credentials, pollId, choices })
- }
-
- const fetchPoll = (pollId) => {
- return apiService.fetchPoll({ credentials, pollId })
- }
-
- const updateNotificationSettings = ({ settings }) => {
- return apiService.updateNotificationSettings({ credentials, settings })
- }
-
- const fetchMutes = () => apiService.fetchMutes({ credentials })
- const muteUser = (id) => apiService.muteUser({ credentials, id })
- const unmuteUser = (id) => apiService.unmuteUser({ credentials, id })
- const subscribeUser = (id) => apiService.subscribeUser({ credentials, id })
- const unsubscribeUser = (id) => apiService.unsubscribeUser({ credentials, id })
- const fetchBlocks = () => apiService.fetchBlocks({ credentials })
- const fetchOAuthTokens = () => apiService.fetchOAuthTokens({ credentials })
- const revokeOAuthToken = (id) => apiService.revokeOAuthToken({ id, credentials })
- const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({ credentials, id })
- const pinOwnStatus = (id) => apiService.pinOwnStatus({ credentials, id })
- const unpinOwnStatus = (id) => apiService.unpinOwnStatus({ credentials, id })
- const muteConversation = (id) => apiService.muteConversation({ credentials, id })
- const unmuteConversation = (id) => apiService.unmuteConversation({ credentials, id })
-
- const getCaptcha = () => apiService.getCaptcha()
- const register = (params) => apiService.register({ credentials, params })
- const updateAvatar = ({ avatar }) => apiService.updateAvatar({ credentials, avatar })
- const updateBg = ({ background }) => apiService.updateBg({ credentials, background })
- const updateBanner = ({ banner }) => apiService.updateBanner({ credentials, banner })
- const updateProfile = ({ params }) => apiService.updateProfile({ credentials, params })
-
- const importBlocks = (file) => apiService.importBlocks({ file, credentials })
- const importFollows = (file) => apiService.importFollows({ file, credentials })
-
- const deleteAccount = ({ password }) => apiService.deleteAccount({ credentials, password })
- const changeEmail = ({ email, password }) => apiService.changeEmail({ credentials, email, password })
- const changePassword = ({ password, newPassword, newPasswordConfirmation }) =>
- apiService.changePassword({ credentials, password, newPassword, newPasswordConfirmation })
-
- const fetchSettingsMFA = () => apiService.settingsMFA({ credentials })
- const generateMfaBackupCodes = () => apiService.generateMfaBackupCodes({ credentials })
- const mfaSetupOTP = () => apiService.mfaSetupOTP({ credentials })
- const mfaConfirmOTP = ({ password, token }) => apiService.mfaConfirmOTP({ credentials, password, token })
- const mfaDisableOTP = ({ password }) => apiService.mfaDisableOTP({ credentials, password })
-
- const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({ id })
- const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({ id })
- const reportUser = (params) => apiService.reportUser({ credentials, ...params })
-
- const favorite = (id) => apiService.favorite({ id, credentials })
- const unfavorite = (id) => apiService.unfavorite({ id, credentials })
- const retweet = (id) => apiService.retweet({ id, credentials })
- const unretweet = (id) => apiService.unretweet({ id, credentials })
- const search2 = ({ q, resolve, limit, offset, following }) =>
- apiService.search2({ credentials, q, resolve, limit, offset, following })
- const searchUsers = (query) => apiService.searchUsers({ query, credentials })
-
- const backendInteractorServiceInstance = {
- fetchStatus,
- fetchConversation,
- fetchFriends,
- exportFriends,
- fetchFollowers,
- followUser,
- unfollowUser,
- blockUser,
- unblockUser,
- fetchUser,
- fetchUserRelationship,
- verifyCredentials: apiService.verifyCredentials,
- startFetchingTimeline,
- startFetchingNotifications,
- startFetchingFollowRequest,
- fetchMutes,
- muteUser,
- unmuteUser,
- subscribeUser,
- unsubscribeUser,
- fetchBlocks,
- fetchOAuthTokens,
- revokeOAuthToken,
- fetchPinnedStatuses,
- pinOwnStatus,
- unpinOwnStatus,
- muteConversation,
- unmuteConversation,
- tagUser,
- untagUser,
- addRight,
- deleteRight,
- deleteUser,
- setActivationStatus,
- register,
- getCaptcha,
- updateAvatar,
- updateBg,
- updateBanner,
- updateProfile,
- importBlocks,
- importFollows,
- deleteAccount,
- changeEmail,
- changePassword,
- fetchSettingsMFA,
- generateMfaBackupCodes,
- mfaSetupOTP,
- mfaConfirmOTP,
- mfaDisableOTP,
- approveUser,
- denyUser,
- vote,
- fetchPoll,
- fetchFavoritedByUsers,
- fetchRebloggedByUsers,
- reportUser,
- favorite,
- unfavorite,
- retweet,
- unretweet,
- updateNotificationSettings,
- search2,
- searchUsers
- }
-
- return backendInteractorServiceInstance
-}
+ startFetchingFollowRequest ({ store }) {
+ return followRequestFetcher.startFetching({ store, credentials })
+ },
+
+ startUserSocket ({ store }) {
+ const serv = store.rootState.instance.server.replace('http', 'ws')
+ const url = serv + getMastodonSocketURI({ credentials, stream: 'user' })
+ return ProcessedWS({ url, id: 'User' })
+ },
+
+ ...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/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js
index 598cb5f7..29b38a0f 100644
--- a/src/services/follow_manipulate/follow_manipulate.js
+++ b/src/services/follow_manipulate/follow_manipulate.js
@@ -39,7 +39,7 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
})
export const requestUnfollow = (user, store) => new Promise((resolve, reject) => {
- store.state.api.backendInteractor.unfollowUser(user.id)
+ store.state.api.backendInteractor.unfollowUser({ id: user.id })
.then((updated) => {
store.commit('updateUserRelationship', [updated])
resolve({
diff --git a/src/services/new_api/mfa.js b/src/services/new_api/mfa.js
index cbba06d5..c944667c 100644
--- a/src/services/new_api/mfa.js
+++ b/src/services/new_api/mfa.js
@@ -1,9 +1,9 @@
-const verifyOTPCode = ({ app, instance, mfaToken, code }) => {
+const verifyOTPCode = ({ clientId, clientSecret, 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('client_id', clientId)
+ form.append('client_secret', clientSecret)
form.append('mfa_token', mfaToken)
form.append('code', code)
form.append('challenge_type', 'totp')
@@ -14,12 +14,12 @@ const verifyOTPCode = ({ app, instance, mfaToken, code }) => {
}).then((data) => data.json())
}
-const verifyRecoveryCode = ({ app, instance, mfaToken, code }) => {
+const verifyRecoveryCode = ({ clientId, clientSecret, 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('client_id', clientId)
+ form.append('client_secret', clientSecret)
form.append('mfa_token', mfaToken)
form.append('code', code)
form.append('challenge_type', 'recovery')
diff --git a/src/services/new_api/oauth.js b/src/services/new_api/oauth.js
index d0d18c03..3c8e64bd 100644
--- a/src/services/new_api/oauth.js
+++ b/src/services/new_api/oauth.js
@@ -12,7 +12,7 @@ export const getOrCreateApp = ({ clientId, clientSecret, instance, commit }) =>
form.append('client_name', `PleromaFE_${window.___pleromafe_commit_hash}_${(new Date()).toISOString()}`)
form.append('redirect_uris', REDIRECT_URI)
- form.append('scopes', 'read write follow')
+ form.append('scopes', 'read write follow push admin')
return window.fetch(url, {
method: 'POST',
@@ -28,7 +28,7 @@ const login = ({ instance, clientId }) => {
response_type: 'code',
client_id: clientId,
redirect_uri: REDIRECT_URI,
- scope: 'read write follow'
+ scope: 'read write follow push admin'
}
const dataString = reduce(data, (acc, v, k) => {
diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
index 9eb30c2d..c6b28ad5 100644
--- a/src/services/timeline_fetcher/timeline_fetcher.service.js
+++ b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -6,6 +6,7 @@ const update = ({ store, statuses, timeline, showImmediately, userId }) => {
const ccTimeline = camelCase(timeline)
store.dispatch('setError', { value: false })
+ store.dispatch('setErrorData', { value: null })
store.dispatch('addNewStatuses', {
timeline: ccTimeline,
@@ -45,6 +46,10 @@ const fetchAndUpdate = ({
return apiService.fetchTimeline(args)
.then((statuses) => {
+ if (statuses.error) {
+ store.dispatch('setErrorData', { value: statuses })
+ return
+ }
if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) {
store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId })
}