logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://anongit.hacktivis.me/git/pleroma-fe.git/

api.js (12047B)


  1. import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
  2. import { WSConnectionStatus } from '../services/api/api.service.js'
  3. import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'
  4. import { Socket } from 'phoenix'
  5. const retryTimeout = (multiplier) => 1000 * multiplier
  6. const api = {
  7. state: {
  8. retryMultiplier: 1,
  9. backendInteractor: backendInteractorService(),
  10. fetchers: {},
  11. socket: null,
  12. mastoUserSocket: null,
  13. mastoUserSocketStatus: null,
  14. followRequests: []
  15. },
  16. getters: {
  17. followRequestCount: state => state.followRequests.length
  18. },
  19. mutations: {
  20. setBackendInteractor (state, backendInteractor) {
  21. state.backendInteractor = backendInteractor
  22. },
  23. addFetcher (state, { fetcherName, fetcher }) {
  24. state.fetchers[fetcherName] = fetcher
  25. },
  26. removeFetcher (state, { fetcherName, fetcher }) {
  27. state.fetchers[fetcherName].stop()
  28. delete state.fetchers[fetcherName]
  29. },
  30. setWsToken (state, token) {
  31. state.wsToken = token
  32. },
  33. setSocket (state, socket) {
  34. state.socket = socket
  35. },
  36. setFollowRequests (state, value) {
  37. state.followRequests = value
  38. },
  39. setMastoUserSocketStatus (state, value) {
  40. state.mastoUserSocketStatus = value
  41. },
  42. incrementRetryMultiplier (state) {
  43. state.retryMultiplier = Math.max(++state.retryMultiplier, 3)
  44. },
  45. resetRetryMultiplier (state) {
  46. state.retryMultiplier = 1
  47. }
  48. },
  49. actions: {
  50. /**
  51. * Global MastoAPI socket control, in future should disable ALL sockets/(re)start relevant sockets
  52. *
  53. * @param {Boolean} [initial] - whether this enabling happened at boot time or not
  54. */
  55. enableMastoSockets (store, initial) {
  56. const { state, dispatch, commit } = store
  57. // Do not initialize unless nonexistent or closed
  58. if (
  59. state.mastoUserSocket &&
  60. ![
  61. WebSocket.CLOSED,
  62. WebSocket.CLOSING
  63. ].includes(state.mastoUserSocket.getState())
  64. ) {
  65. return
  66. }
  67. if (initial) {
  68. commit('setMastoUserSocketStatus', WSConnectionStatus.STARTING_INITIAL)
  69. } else {
  70. commit('setMastoUserSocketStatus', WSConnectionStatus.STARTING)
  71. }
  72. return dispatch('startMastoUserSocket')
  73. },
  74. disableMastoSockets (store) {
  75. const { state, dispatch, commit } = store
  76. if (!state.mastoUserSocket) return
  77. commit('setMastoUserSocketStatus', WSConnectionStatus.DISABLED)
  78. return dispatch('stopMastoUserSocket')
  79. },
  80. // MastoAPI 'User' sockets
  81. startMastoUserSocket (store) {
  82. return new Promise((resolve, reject) => {
  83. try {
  84. const { state, commit, dispatch, rootState } = store
  85. const timelineData = rootState.statuses.timelines.friends
  86. state.mastoUserSocket = state.backendInteractor.startUserSocket({ store })
  87. state.mastoUserSocket.addEventListener('pleroma:authenticated', () => {
  88. state.mastoUserSocket.subscribe('user')
  89. })
  90. state.mastoUserSocket.addEventListener(
  91. 'message',
  92. ({ detail: message }) => {
  93. if (!message) return // pings
  94. if (message.event === 'notification') {
  95. dispatch('addNewNotifications', {
  96. notifications: [message.notification],
  97. older: false
  98. })
  99. } else if (message.event === 'update') {
  100. dispatch('addNewStatuses', {
  101. statuses: [message.status],
  102. userId: false,
  103. showImmediately: timelineData.visibleStatuses.length === 0,
  104. timeline: 'friends'
  105. })
  106. } else if (message.event === 'status.update') {
  107. dispatch('addNewStatuses', {
  108. statuses: [message.status],
  109. userId: false,
  110. showImmediately: message.status.id in timelineData.visibleStatusesObject,
  111. timeline: 'friends'
  112. })
  113. } else if (message.event === 'delete') {
  114. dispatch('deleteStatusById', message.id)
  115. } else if (message.event === 'pleroma:chat_update') {
  116. // The setTimeout wrapper is a temporary band-aid to avoid duplicates for the user's own messages when doing optimistic sending.
  117. // The cause of the duplicates is the WS event arriving earlier than the HTTP response.
  118. // This setTimeout wrapper can be removed once the commit `8e41baff` is in the stable Pleroma release.
  119. // (`8e41baff` adds the idempotency key to the chat message entity, which PleromaFE uses when it's available, and it makes this artificial delay unnecessary).
  120. setTimeout(() => {
  121. dispatch('addChatMessages', {
  122. chatId: message.chatUpdate.id,
  123. messages: [message.chatUpdate.lastMessage]
  124. })
  125. dispatch('updateChat', { chat: message.chatUpdate })
  126. maybeShowChatNotification(store, message.chatUpdate)
  127. }, 100)
  128. }
  129. }
  130. )
  131. state.mastoUserSocket.addEventListener('open', () => {
  132. // Do not show notification when we just opened up the page
  133. if (state.mastoUserSocketStatus !== WSConnectionStatus.STARTING_INITIAL) {
  134. dispatch('pushGlobalNotice', {
  135. level: 'success',
  136. messageKey: 'timeline.socket_reconnected',
  137. timeout: 5000
  138. })
  139. }
  140. // Stop polling if we were errored or disabled
  141. if (new Set([
  142. WSConnectionStatus.ERROR,
  143. WSConnectionStatus.DISABLED
  144. ]).has(state.mastoUserSocketStatus)) {
  145. dispatch('stopFetchingTimeline', { timeline: 'friends' })
  146. dispatch('stopFetchingNotifications')
  147. dispatch('stopFetchingChats')
  148. }
  149. commit('resetRetryMultiplier')
  150. commit('setMastoUserSocketStatus', WSConnectionStatus.JOINED)
  151. })
  152. state.mastoUserSocket.addEventListener('error', ({ detail: error }) => {
  153. console.error('Error in MastoAPI websocket:', error)
  154. // TODO is this needed?
  155. dispatch('clearOpenedChats')
  156. })
  157. state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => {
  158. const ignoreCodes = new Set([
  159. 1000, // Normal (intended) closure
  160. 1001 // Going away
  161. ])
  162. const { code } = closeEvent
  163. if (ignoreCodes.has(code)) {
  164. console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`)
  165. commit('setMastoUserSocketStatus', WSConnectionStatus.CLOSED)
  166. } else {
  167. console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`)
  168. setTimeout(() => {
  169. dispatch('startMastoUserSocket')
  170. }, retryTimeout(state.retryMultiplier))
  171. commit('incrementRetryMultiplier')
  172. if (state.mastoUserSocketStatus !== WSConnectionStatus.ERROR) {
  173. dispatch('startFetchingTimeline', { timeline: 'friends' })
  174. dispatch('startFetchingNotifications')
  175. dispatch('startFetchingChats')
  176. dispatch('pushGlobalNotice', {
  177. level: 'error',
  178. messageKey: 'timeline.socket_broke',
  179. messageArgs: [code],
  180. timeout: 5000
  181. })
  182. }
  183. commit('setMastoUserSocketStatus', WSConnectionStatus.ERROR)
  184. }
  185. dispatch('clearOpenedChats')
  186. })
  187. resolve()
  188. } catch (e) {
  189. reject(e)
  190. }
  191. })
  192. },
  193. stopMastoUserSocket ({ state, dispatch }) {
  194. dispatch('startFetchingTimeline', { timeline: 'friends' })
  195. dispatch('startFetchingNotifications')
  196. dispatch('startFetchingChats')
  197. state.mastoUserSocket.close()
  198. },
  199. // Timelines
  200. startFetchingTimeline (store, {
  201. timeline = 'friends',
  202. tag = false,
  203. userId = false,
  204. listId = false,
  205. statusId = false,
  206. bookmarkFolderId = false
  207. }) {
  208. if (store.state.fetchers[timeline]) return
  209. const fetcher = store.state.backendInteractor.startFetchingTimeline({
  210. timeline, store, userId, listId, statusId, bookmarkFolderId, tag
  211. })
  212. store.commit('addFetcher', { fetcherName: timeline, fetcher })
  213. },
  214. stopFetchingTimeline (store, timeline) {
  215. const fetcher = store.state.fetchers[timeline]
  216. if (!fetcher) return
  217. store.commit('removeFetcher', { fetcherName: timeline, fetcher })
  218. },
  219. fetchTimeline (store, { timeline, ...rest }) {
  220. store.state.backendInteractor.fetchTimeline({
  221. store,
  222. timeline,
  223. ...rest
  224. })
  225. },
  226. // Notifications
  227. startFetchingNotifications (store) {
  228. if (store.state.fetchers.notifications) return
  229. const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
  230. store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
  231. },
  232. stopFetchingNotifications (store) {
  233. const fetcher = store.state.fetchers.notifications
  234. if (!fetcher) return
  235. store.commit('removeFetcher', { fetcherName: 'notifications', fetcher })
  236. },
  237. fetchNotifications (store, { ...rest }) {
  238. store.state.backendInteractor.fetchNotifications({
  239. store,
  240. ...rest
  241. })
  242. },
  243. // Follow requests
  244. startFetchingFollowRequests (store) {
  245. if (store.state.fetchers.followRequests) return
  246. const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })
  247. store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
  248. },
  249. stopFetchingFollowRequests (store) {
  250. const fetcher = store.state.fetchers.followRequests
  251. if (!fetcher) return
  252. store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher })
  253. },
  254. removeFollowRequest (store, request) {
  255. const requests = store.state.followRequests.filter((it) => it !== request)
  256. store.commit('setFollowRequests', requests)
  257. },
  258. // Lists
  259. startFetchingLists (store) {
  260. if (store.state.fetchers.lists) return
  261. const fetcher = store.state.backendInteractor.startFetchingLists({ store })
  262. store.commit('addFetcher', { fetcherName: 'lists', fetcher })
  263. },
  264. stopFetchingLists (store) {
  265. const fetcher = store.state.fetchers.lists
  266. if (!fetcher) return
  267. store.commit('removeFetcher', { fetcherName: 'lists', fetcher })
  268. },
  269. // Bookmark folders
  270. startFetchingBookmarkFolders (store) {
  271. if (store.state.fetchers.bookmarkFolders) return
  272. const fetcher = store.state.backendInteractor.startFetchingBookmarkFolders({ store })
  273. store.commit('addFetcher', { fetcherName: 'bookmarkFolders', fetcher })
  274. },
  275. stopFetchingBookmarkFolders (store) {
  276. const fetcher = store.state.fetchers.bookmarkFolders
  277. if (!fetcher) return
  278. store.commit('removeFetcher', { fetcherName: 'bookmarkFolders', fetcher })
  279. },
  280. // Pleroma websocket
  281. setWsToken (store, token) {
  282. store.commit('setWsToken', token)
  283. },
  284. initializeSocket ({ dispatch, commit, state, rootState }) {
  285. // Set up websocket connection
  286. const token = state.wsToken
  287. if (rootState.instance.shoutAvailable && typeof token !== 'undefined' && state.socket === null) {
  288. const socket = new Socket('/socket', { params: { token } })
  289. socket.connect()
  290. commit('setSocket', socket)
  291. dispatch('initializeShout', socket)
  292. }
  293. },
  294. disconnectFromSocket ({ commit, state }) {
  295. state.socket && state.socket.disconnect()
  296. commit('setSocket', null)
  297. }
  298. }
  299. }
  300. export default api