Merge branch 'api-refactor' into shigusegubu-themes3

This commit is contained in:
Henry Jameson 2026-06-16 23:20:30 +03:00
commit 6753de2986
9 changed files with 305 additions and 188 deletions

View file

@ -106,7 +106,7 @@ export const buildSwPlugin = ({ swSrc, swDest }) => {
const swBundle = await build(config) const swBundle = await build(config)
return swBundle.output[0] return swBundle.output[0]
} catch (e) { } catch (e) {
console.error('Error building ServiceWorker:' , e) console.error('Error building ServiceWorker:', e)
} }
}, },
}, },
@ -119,7 +119,7 @@ export const buildSwPlugin = ({ swSrc, swDest }) => {
try { try {
await build(config) await build(config)
} catch (e) { } catch (e) {
console.error('Error building ServiceWorker:' , e) console.error('Error building ServiceWorker:', e)
} }
}, },
}, },

View file

@ -798,8 +798,11 @@ const users = {
} }
if (useMergedConfigStore().mergedConfig.useStreamingApi) { if (useMergedConfigStore().mergedConfig.useStreamingApi) {
dispatch('fetchTimeline', { timeline: 'friends', since: null }) dispatch('fetchTimeline', {
dispatch('fetchNotifications', { since: null }) timeline: 'friends',
sinceId: null,
})
dispatch('fetchNotifications', { sinceId: null })
dispatch('enableMastoSockets', true) dispatch('enableMastoSockets', true)
.catch((error) => { .catch((error) => {
console.error( console.error(

View file

@ -9,7 +9,7 @@ import {
parseStatus, parseStatus,
parseUser, parseUser,
} from '../entity_normalizer/entity_normalizer.service.js' } from '../entity_normalizer/entity_normalizer.service.js'
import { promisedRequest } from './helpers.js' import { paramsString, promisedRequest } from './helpers.js'
import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' import { RegistrationError, StatusCodeError } from 'src/services/errors/errors'
@ -46,8 +46,16 @@ const MASTODON_UNRETWEET_URL = (id) => `/api/v1/statuses/${id}/unreblog`
const MASTODON_DELETE_URL = (id) => `/api/v1/statuses/${id}` const MASTODON_DELETE_URL = (id) => `/api/v1/statuses/${id}`
const MASTODON_FOLLOW_URL = (id) => `/api/v1/accounts/${id}/follow` const MASTODON_FOLLOW_URL = (id) => `/api/v1/accounts/${id}/follow`
const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow` const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow`
const MASTODON_FOLLOWING_URL = (id) => `/api/v1/accounts/${id}/following` const MASTODON_FOLLOWING_URL = (
const MASTODON_FOLLOWERS_URL = (id) => `/api/v1/accounts/${id}/followers` id,
{ minId, maxId, sinceId, limit, withRelationships },
) =>
`/api/v1/accounts/${id}/following${paramsString({ minId, maxId, sinceId, limit, withRelationships })}`
const MASTODON_FOLLOWERS_URL = (
id,
{ minId, maxId, sinceId, limit, withRelationships },
) =>
`/api/v1/accounts/${id}/followers${paramsString({ minId, maxId, sinceId, limit, withRelationships })}`
const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests' const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests'
const MASTODON_APPROVE_USER_URL = (id) => const MASTODON_APPROVE_USER_URL = (id) =>
`/api/v1/follow_requests/${id}/authorize` `/api/v1/follow_requests/${id}/authorize`
@ -61,7 +69,8 @@ const MASTODON_STATUS_SOURCE_URL = (id) => `/api/v1/statuses/${id}/source`
const MASTODON_STATUS_HISTORY_URL = (id) => `/api/v1/statuses/${id}/history` const MASTODON_STATUS_HISTORY_URL = (id) => `/api/v1/statuses/${id}/history`
const MASTODON_USER_URL = '/api/v1/accounts' const MASTODON_USER_URL = '/api/v1/accounts'
const MASTODON_USER_LOOKUP_URL = '/api/v1/accounts/lookup' const MASTODON_USER_LOOKUP_URL = '/api/v1/accounts/lookup'
const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships' const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) =>
`/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}`
const MASTODON_USER_TIMELINE_URL = (id) => `/api/v1/accounts/${id}/statuses` const MASTODON_USER_TIMELINE_URL = (id) => `/api/v1/accounts/${id}/statuses`
const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists` const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists`
const MASTODON_LIST_URL = (id) => `/api/v1/lists/${id}` const MASTODON_LIST_URL = (id) => `/api/v1/lists/${id}`
@ -70,8 +79,20 @@ const MASTODON_LIST_ACCOUNTS_URL = (id) => `/api/v1/lists/${id}/accounts`
const MASTODON_TAG_TIMELINE_URL = (tag) => `/api/v1/timelines/tag/${tag}` const MASTODON_TAG_TIMELINE_URL = (tag) => `/api/v1/timelines/tag/${tag}`
const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks' const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks'
const AKKOMA_BUBBLE_TIMELINE_URL = '/api/v1/timelines/bubble' const AKKOMA_BUBBLE_TIMELINE_URL = '/api/v1/timelines/bubble'
const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/' const MASTODON_USER_BLOCKS_URL = ({
const MASTODON_USER_MUTES_URL = '/api/v1/mutes/' 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_BLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/block`
const MASTODON_UNBLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/unblock` const MASTODON_UNBLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/unblock`
const MASTODON_MUTE_USER_URL = (id) => `/api/v1/accounts/${id}/mute` const MASTODON_MUTE_USER_URL = (id) => `/api/v1/accounts/${id}/mute`
@ -113,12 +134,14 @@ const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) =>
`/api/v1/pleroma/statuses/${id}/reactions/${emoji}` `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats' const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats'
const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}` const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}`
const PLEROMA_CHAT_MESSAGES_URL = (id) => `/api/v1/pleroma/chats/${id}/messages` const PLEROMA_CHAT_MESSAGES_URL = (id, { maxId, sinceId, limit }) =>
`/api/v1/pleroma/chats/${id}/messages${paramsString({ maxId, sinceId, limit })}`
const PLEROMA_CHAT_READ_URL = (id) => `/api/v1/pleroma/chats/${id}/read` const PLEROMA_CHAT_READ_URL = (id) => `/api/v1/pleroma/chats/${id}/read`
const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) =>
`/api/v1/pleroma/chats/${chatId}/messages/${messageId}` `/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups' const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
const PLEROMA_SCROBBLES_URL = (id) => `/api/v1/pleroma/accounts/${id}/scrobbles` const PLEROMA_SCROBBLES_URL = (id, { maxId, sinceId, minId, limit, offset }) =>
`/api/v1/pleroma/accounts/${id}/scrobbles${paramsString({ maxId, sinceId, minId, limit, offset })}`
const PLEROMA_STATUS_QUOTES_URL = (id) => const PLEROMA_STATUS_QUOTES_URL = (id) =>
`/api/v1/pleroma/statuses/${id}/quotes` `/api/v1/pleroma/statuses/${id}/quotes`
const PLEROMA_USER_FAVORITES_TIMELINE_URL = (id) => const PLEROMA_USER_FAVORITES_TIMELINE_URL = (id) =>
@ -128,20 +151,14 @@ const PLEROMA_BOOKMARK_FOLDER_URL = (id) =>
`/api/v1/pleroma/bookmark_folders/${id}` `/api/v1/pleroma/bookmark_folders/${id}`
const EMOJI_PACKS_URL = (page, pageSize) => const EMOJI_PACKS_URL = (page, pageSize) =>
`/api/v1/pleroma/emoji/packs?page=${page}&page_size=${pageSize}` `/api/v1/pleroma/emoji/packs${paramsString({ page, pageSize })}`
export const updateNotificationSettings = ({ credentials, settings }) => { export const updateNotificationSettings = ({ credentials, settings }) => {
const form = new FormData()
each(settings, (value, key) => {
form.append(key, value)
})
return promisedRequest({ return promisedRequest({
url: `${NOTIFICATION_SETTINGS_URL}?${new URLSearchParams(settings)}`, url: NOTIFICATION_SETTINGS_URL,
credentials, credentials,
method: 'PUT', method: 'PUT',
formData: form, payload: settings,
}) })
} }
@ -245,19 +262,19 @@ export const getCaptcha = () =>
}) })
export const followUser = ({ id, credentials, ...options }) => { export const followUser = ({ id, credentials, ...options }) => {
const form = {} const payload = {}
if (options.reblogs !== undefined) { if (options.reblogs !== undefined) {
form.reblogs = options.reblogs payload.reblogs = options.reblogs
} }
if (options.notify !== undefined) { if (options.notify !== undefined) {
form.notify = options.notify payload.notify = options.notify
} }
return promisedRequest({ return promisedRequest({
url: MASTODON_FOLLOW_URL(id), url: MASTODON_FOLLOW_URL(id),
formData: form, payload,
credentials, credentials,
method: 'POST', method: 'POST',
}) })
@ -380,35 +397,17 @@ export const fetchUserByName = ({ name, credentials }) =>
}) })
.then((id) => fetchUser({ id, credentials })) .then((id) => fetchUser({ id, credentials }))
export const fetchUserRelationship = ({ id, credentials }) => export const fetchUserRelationship = ({ id, withSuspended, credentials }) =>
promisedRequest({ promisedRequest({
url: `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}`, url: MASTODON_USER_RELATIONSHIPS_URL({ id, withSuspended }),
credentials, credentials,
}) })
export const fetchFriends = ({ export const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) =>
id, promisedRequest({
maxId, url: MASTODON_FOLLOWING_URL(id, { maxId, sinceId, limit }),
sinceId,
limit = 20,
credentials,
}) => {
let url = MASTODON_FOLLOWING_URL(id)
const args = [
maxId && `max_id=${maxId}`,
sinceId && `since_id=${sinceId}`,
limit && `limit=${limit}`,
'with_relationships=true',
]
.filter((_) => _)
.join('&')
url = url + (args ? '?' + args : '')
return promisedRequest({
url,
credentials, credentials,
}).then((data) => data.map(parseUser)) }).then((data) => data.map(parseUser))
}
export const exportFriends = ({ id, credentials }) => { export const exportFriends = ({ id, credentials }) => {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO refactor this // biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO refactor this
@ -418,7 +417,12 @@ export const exportFriends = ({ id, credentials }) => {
let more = true let more = true
while (more) { while (more) {
const maxId = friends.length > 0 ? last(friends).id : undefined const maxId = friends.length > 0 ? last(friends).id : undefined
const users = await fetchFriends({ id, maxId, credentials }) const users = await fetchFriends({
id,
maxId,
credentials,
withRelationships: true,
})
friends = concat(friends, users) friends = concat(friends, users)
if (users.length === 0) { if (users.length === 0) {
more = false more = false
@ -437,23 +441,16 @@ export const fetchFollowers = ({
sinceId, sinceId,
limit = 20, limit = 20,
credentials, credentials,
}) => { }) =>
let url = MASTODON_FOLLOWERS_URL(id) promisedRequest({
const args = [ url: MASTODON_FOLLOWERS_URL(id, {
maxId && `max_id=${maxId}`, maxId,
sinceId && `since_id=${sinceId}`, sinceId,
limit && `limit=${limit}`, limit,
'with_relationships=true', withRelationships: true,
] }),
.filter((_) => _)
.join('&')
url += args ? '?' + args : ''
return promisedRequest({
url,
credentials, credentials,
}).then((data) => data.map(parseUser)) }).then((data) => data.map(parseUser))
}
export const fetchFollowRequests = ({ credentials }) => export const fetchFollowRequests = ({ credentials }) =>
promisedRequest({ promisedRequest({
@ -567,17 +564,17 @@ export const fetchStatusHistory = ({ status, credentials }) =>
export const fetchTimeline = ({ export const fetchTimeline = ({
timeline, timeline,
credentials, credentials,
since = false, sinceId,
minId = false, minId,
until = false, maxId,
userId = false, userId,
listId = false, listId,
statusId = false, statusId,
tag = false, tag,
withMuted = false, withMuted,
replyVisibility = 'all', replyVisibility = 'all',
includeTypes = [], includeTypes = [],
bookmarkFolderId = false, bookmarkFolderId,
}) => { }) => {
const timelineUrls = { const timelineUrls = {
public: MASTODON_PUBLIC_TIMELINE, public: MASTODON_PUBLIC_TIMELINE,
@ -595,8 +592,14 @@ export const fetchTimeline = ({
quotes: PLEROMA_STATUS_QUOTES_URL, quotes: PLEROMA_STATUS_QUOTES_URL,
bubble: AKKOMA_BUBBLE_TIMELINE_URL, bubble: AKKOMA_BUBBLE_TIMELINE_URL,
} }
const isNotifications = timeline === 'notifications' const isNotifications = timeline === 'notifications'
const params = [] const params = {
minId,
sinceId,
maxId,
limit: 20,
}
let url = timelineUrls[timeline] let url = timelineUrls[timeline]
@ -616,51 +619,34 @@ export const fetchTimeline = ({
url = url(statusId) 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) { if (tag) {
url = url(tag) url = url(tag)
} }
if (timeline === 'media') { if (timeline === 'media') {
params.push(['only_media', 1]) params.onlyMedia = 1
} }
if (timeline === 'public') { if (timeline === 'public') {
params.push(['local', true]) params.local = true
} }
if (timeline === 'public' || timeline === 'publicAndExternal') { if (timeline === 'public' || timeline === 'publicAndExternal') {
params.push(['only_media', false]) params.onlyMedia = false
} }
if (timeline !== 'favorites' && timeline !== 'bookmarks') { if (timeline !== 'favorites' && timeline !== 'bookmarks') {
params.push(['with_muted', withMuted]) params.withMuted = withMuted
} }
if (replyVisibility !== 'all') { if (replyVisibility !== 'all') {
params.push(['reply_visibility', replyVisibility]) params.replyVisibility = replyVisibility
} }
if (includeTypes.size > 0) { if (includeTypes.size > 0) {
includeTypes.forEach((type) => { params.includeTypes = includeTypes
params.push(['include_types[]', type])
})
} }
if (timeline === 'bookmarks' && bookmarkFolderId) { if (timeline === 'bookmarks' && bookmarkFolderId) {
params.push(['folder_id', bookmarkFolderId]) params.folderId = bookmarkFolderId
} }
params.push(['limit', 20])
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join(
'&',
)
url += `?${queryString}`
return promisedRequest({ return promisedRequest({
url, url: url + paramsString(params),
credentials, credentials,
}).then(async (data) => { }).then(async (data) => {
const pagination = parseLinkHeaderPagination( const pagination = parseLinkHeaderPagination(
@ -1036,16 +1022,11 @@ export const generateMfaBackupCodes = ({ credentials }) =>
method: 'GET', method: 'GET',
}) })
export const fetchMutes = ({ maxId, credentials }) => { export const fetchMutes = ({ maxId, credentials }) =>
const query = new URLSearchParams({ with_relationships: true }) promisedRequest({
if (maxId) { url: MASTODON_USER_MUTES_URL({ maxId, withRelationships: true }),
query.append('max_id', maxId)
}
return promisedRequest({
url: `${MASTODON_USER_MUTES_URL}?${query.toString()}`,
credentials, credentials,
}).then((users) => users.map(parseUser)) }).then((users) => users.map(parseUser))
}
export const muteUser = ({ id, expiresIn, credentials }) => { export const muteUser = ({ id, expiresIn, credentials }) => {
const payload = {} const payload = {}
@ -1068,16 +1049,11 @@ export const unmuteUser = ({ id, credentials }) =>
method: 'POST', method: 'POST',
}) })
export const fetchBlocks = ({ maxId, credentials }) => { export const fetchBlocks = ({ maxId, credentials }) =>
const query = new URLSearchParams({ with_relationships: true }) promisedRequest({
if (maxId) { url: MASTODON_USER_BLOCKS_URL({ maxId, withRelationships: true }),
query.append('max_id', maxId)
}
return promisedRequest({
url: `${MASTODON_USER_BLOCKS_URL}?${query.toString()}`,
credentials, credentials,
}).then((users) => users.map(parseUser)) }).then((users) => users.map(parseUser))
}
export const addBackup = ({ credentials }) => export const addBackup = ({ credentials }) =>
promisedRequest({ promisedRequest({
@ -1519,19 +1495,8 @@ export const chatMessages = ({
sinceId, sinceId,
limit = 20, 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({ return promisedRequest({
url, url: PLEROMA_CHAT_MESSAGES_URL(id, { maxId, sinceId, limit }),
method: 'GET', method: 'GET',
credentials, credentials,
}) })
@ -1584,15 +1549,10 @@ export const deleteChatMessage = ({ chatId, messageId, credentials }) =>
credentials, credentials,
}) })
export const fetchScrobbles = ({ accountId, limit = 1 }) => { export const fetchScrobbles = ({ accountId, limit = 1 }) =>
let url = PLEROMA_SCROBBLES_URL(accountId) promisedRequest({
const params = [['limit', limit]] url: PLEROMA_SCROBBLES_URL(accountId, { limit }),
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join( })
'&',
)
url += `?${queryString}`
return promisedRequest({ url })
}
export const fetchBookmarkFolders = ({ credentials }) => export const fetchBookmarkFolders = ({ credentials }) =>
promisedRequest({ promisedRequest({

View file

@ -1,5 +1,71 @@
import { snakeCase } from 'lodash'
import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' import { RegistrationError, StatusCodeError } from 'src/services/errors/errors'
export const paramsString = (params = {}) => {
if (params == null || params === undefined) return ''
if (typeof params !== 'object' || Array.isArray(params)) {
throw new Error('Params are not an object!')
}
const entries = (() => {
if (params instanceof Map) {
return params.entries()
} else {
return Object.entries(params)
}
})()
if (entries.length === 0) return ''
const arrays = []
const nonArrays = []
entries.forEach(([k, v]) => {
if (v == null) return // Drop nulls
if (
(typeof v === 'object' && !Array.isArray(v)) ||
typeof v === 'function'
) {
throw new Error('Param cannot be non-primitive!')
}
if (Array.isArray(v)) {
arrays.push([k, v])
} else {
nonArrays.push([k, v])
}
})
arrays.forEach(([k, array]) => {
array.forEach((v) => {
if (
typeof v === 'object' ||
typeof v === 'function' ||
typeof v === 'undefined'
)
throw new Error('Array param cannot contain non-primitives!')
})
})
return (
'?' +
[
...nonArrays.map(([k, v]) => [snakeCase(k), v]),
// turning [a,[1,2,3]] into [[a[],1],[a[],2],[a[],3]]
...arrays.reduce(
(acc, [k, arrayValue]) => [
...acc,
...arrayValue.map((v) => [snakeCase(k) + '[]', v]),
],
[],
),
]
.map(([k, v]) => `${k}=${window.encodeURIComponent(v)}`)
.join('&')
)
}
export const promisedRequest = ({ export const promisedRequest = ({
method, method,
url, url,
@ -60,6 +126,12 @@ export const promisedRequest = ({
) )
} }
if (typeof json !== 'object') {
return resolve({
_response: response,
_value: json,
})
}
json._response = response json._response = response
return resolve(json) return resolve(json)

View file

@ -35,42 +35,33 @@ const fetchRelationship = (attempt, userId, store) =>
} }
}) })
export const requestFollow = (userId, store) => export const requestFollow = async (userId, store) => {
new Promise((resolve) => { const updated = await followUser({
followUser({ id: userId,
id: userId, credentials: useOAuthStore().token,
credentials: useOAuthStore().token,
}).then((updated) => {
store.commit('updateUserRelationship', [updated])
if (updated.following || (updated.locked && updated.requested)) {
// If we get result immediately or the account is locked, just stop.
resolve()
return
}
// But usually we don't get result immediately, so we ask server
// for updated user profile to confirm if we are following them
// Sometimes it takes several tries. Sometimes we end up not following
// user anyway, probably because they locked themselves and we
// don't know that yet.
// Recursive Promise, it will call itself up to 3 times.
return fetchRelationship(1, updated, store).then(() => {
resolve()
})
})
}) })
export const requestUnfollow = (userId, store) => store.commit('updateUserRelationship', [updated])
new Promise((resolve) => {
unfollowUser({ if (updated.following || (updated.locked && updated.requested)) {
id: userId, // If we get result immediately or the account is locked, just stop.
credentials: useOAuthStore().token, return
}).then((updated) => { }
store.commit('updateUserRelationship', [updated])
resolve({ // But usually we don't get result immediately, so we ask server
updated, // for updated user profile to confirm if we are following them
}) // Sometimes it takes several tries. Sometimes we end up not following
}) // user anyway, probably because they locked themselves and we
// don't know that yet.
// Recursive Promise, it will call itself up to 3 times.
return await fetchRelationship(1, updated, store)
}
export const requestUnfollow = async (userId, store) => {
const updated = await unfollowUser({
id: userId,
credentials: useOAuthStore().token,
}) })
return await store.commit('updateUserRelationship', [updated])
}

View file

@ -25,7 +25,7 @@ const mastoApiNotificationTypes = new Set([
'pleroma:report', 'pleroma:report',
]) ])
const fetchAndUpdate = ({ store, credentials, older = false, since }) => { const fetchAndUpdate = ({ store, credentials, older = false, sinceId }) => {
const args = { credentials } const args = { credentials }
const rootState = store.rootState || store.state const rootState = store.rootState || store.state
const timelineData = rootState.notifications const timelineData = rootState.notifications
@ -35,24 +35,24 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => {
mastoApiNotificationTypes.add('pleroma:chat_mention') mastoApiNotificationTypes.add('pleroma:chat_mention')
} }
args.includeTypes = mastoApiNotificationTypes args.includeTypes = [...mastoApiNotificationTypes]
args.withMuted = !hideMutedPosts args.withMuted = !hideMutedPosts
args.timeline = 'notifications' args.timeline = 'notifications'
if (older) { if (older) {
if (timelineData.minId !== Number.POSITIVE_INFINITY) { if (timelineData.minId !== Number.POSITIVE_INFINITY) {
args.until = timelineData.minId args.maxId = timelineData.minId
} }
return fetchNotifications({ store, args, older }) return fetchNotifications({ store, args, older })
} else { } else {
// fetch new notifications // fetch new notifications
if ( if (
since === undefined && sinceId === undefined &&
timelineData.maxId !== Number.POSITIVE_INFINITY timelineData.maxId !== Number.POSITIVE_INFINITY
) { ) {
args.since = timelineData.maxId args.sinceId = timelineData.maxId
} else if (since !== null) { } else if (sinceId !== null) {
args.since = since args.sinceId = sinceId
} }
const result = fetchNotifications({ store, args, older }) const result = fetchNotifications({ store, args, older })
@ -69,7 +69,7 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => {
if (readNotifsIds.length > 0 && readNotifsIds.length > 0) { if (readNotifsIds.length > 0 && readNotifsIds.length > 0) {
const minId = Math.min(...unreadNotifsIds) // Oldest known unread notification const minId = Math.min(...unreadNotifsIds) // Oldest known unread notification
if (minId !== Infinity) { 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 args.minId = minId - 1 // go beyond
fetchNotifications({ store, args, older }) fetchNotifications({ store, args, older })
} }

View file

@ -41,7 +41,7 @@ const fetchAndUpdate = ({
bookmarkFolderId = false, bookmarkFolderId = false,
tag = false, tag = false,
until, until,
since, sinceId,
}) => { }) => {
const args = { timeline, credentials } const args = { timeline, credentials }
const rootState = store.rootState || store.state const rootState = store.rootState || store.state
@ -53,10 +53,10 @@ const fetchAndUpdate = ({
if (older) { if (older) {
args.until = until || timelineData.minId args.until = until || timelineData.minId
} else { } else {
if (since === undefined) { if (sinceId === undefined) {
args.since = timelineData.maxId args.sinceId = timelineData.maxId
} else if (since !== null) { } else if (sinceId !== null) {
args.since = since args.sinceId = sinceId
} }
} }

View file

@ -0,0 +1,90 @@
import { paramsString } from 'src/services/api/helpers.js'
describe('API Helpers', () => {
describe.only('paramsString', () => {
it('should return empty string when given empty object', () => {
const string = paramsString({})
expect(string).to.eq('')
})
it('should return empty string when given null', () => {
const string = paramsString(null)
expect(string).to.eq('')
})
it('should return empty string when given undefined', () => {
const string = paramsString(undefined)
expect(string).to.eq('')
})
it('should return URI param string for normal object', () => {
const string = paramsString({ a: 1, b: '3' })
expect(string).to.eq('?a=1&b=3')
})
it('should encode objects correctly', () => {
const string = paramsString({ foo: true, bar: [1, 2, 3] })
expect(string).to.eq('?foo=true&bar[]=1&bar[]=2&bar[]=3')
})
it('should drop nullish params', () => {
const string = paramsString({ present: 'yes', missing: null })
expect(string).to.eq('?present=yes')
})
it('should convert camelCase keys to snake_keys objects correctly', () => {
const string = paramsString({ isActive: true, MaybeNot: false })
expect(string).to.eq('?is_active=true&maybe_not=false')
})
it('should work with maps', () => {
const string = paramsString(
new Map([
['key', 'yes'],
['key2', 'also yes'],
]),
)
expect(string).to.eq('?key=yes&key_2=also%20yes')
})
it('should escape components correctly', () => {
const string = paramsString({ gachi: '♂', muchi: 'Билли Геррингтон' })
expect(string).to.eq(
'?gachi=%E2%99%82&muchi=%D0%91%D0%B8%D0%BB%D0%BB%D0%B8%20%D0%93%D0%B5%D1%80%D1%80%D0%B8%D0%BD%D0%B3%D1%82%D0%BE%D0%BD',
)
})
it('should throw when passed a non-object', () => {
expect(() => {
paramsString('Totally an object')
}).to.throw()
})
it('should throw when passed an array', () => {
expect(() => {
paramsString(['Totally an object'])
}).to.throw()
})
it('should throw when array param is non-primitive', () => {
expect(() => {
paramsString({ a: [() => ''] })
}).to.throw()
})
it('should throw when array param is nullish', () => {
expect(() => {
paramsString({ a: [1, null, 3] })
}).to.throw()
})
})
})

View file

@ -1,10 +1,11 @@
import mastoapidata from '../../../../fixtures/mastoapi.json'
import { import {
parseLinkHeaderPagination, parseLinkHeaderPagination,
parseNotification, parseNotification,
parseStatus, parseStatus,
parseUser, parseUser,
} from '../../../../../src/services/entity_normalizer/entity_normalizer.service.js' } from 'src/services/entity_normalizer/entity_normalizer.service.js'
import mastoapidata from '../../../../fixtures/mastoapi.json'
const makeMockUserMasto = (overrides = {}) => { const makeMockUserMasto = (overrides = {}) => {
return Object.assign( return Object.assign(