2016-11-26 09:57:08 -08:00
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
2020-05-07 06:10:53 -07:00
import { WSConnectionStatus } from '../services/api/api.service.js'
2020-07-12 14:06:45 -07:00
import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'
2017-12-05 02:47:10 -08:00
import { Socket } from 'phoenix'
2016-11-26 09:57:08 -08:00
2021-03-08 16:38:10 -08:00
const retryTimeout = ( multiplier ) => 1000 * multiplier
2016-11-26 09:57:08 -08:00
const api = {
state : {
2021-03-08 12:24:39 -08:00
retryMultiplier : 1 ,
2017-02-16 02:17:47 -08:00
backendInteractor : backendInteractorService ( ) ,
2017-12-05 02:47:10 -08:00
fetchers : { } ,
2017-12-07 08:20:44 -08:00
socket : null ,
2019-12-08 06:05:41 -08:00
mastoUserSocket : null ,
2020-05-07 06:10:53 -07:00
mastoUserSocketStatus : null ,
2018-06-06 18:24:31 -07:00
followRequests : [ ]
2016-11-26 09:57:08 -08:00
} ,
mutations : {
setBackendInteractor ( state , backendInteractor ) {
state . backendInteractor = backendInteractor
2017-02-16 02:17:47 -08:00
} ,
2019-04-04 09:06:53 -07:00
addFetcher ( state , { fetcherName , fetcher } ) {
2019-04-04 09:03:56 -07:00
state . fetchers [ fetcherName ] = fetcher
2017-02-16 02:17:47 -08:00
} ,
2019-12-08 06:05:41 -08:00
removeFetcher ( state , { fetcherName , fetcher } ) {
2020-09-04 01:19:53 -07:00
state . fetchers [ fetcherName ] . stop ( )
2019-04-04 09:03:56 -07:00
delete state . fetchers [ fetcherName ]
2017-12-05 02:47:10 -08:00
} ,
2019-01-29 07:16:25 -08:00
setWsToken ( state , token ) {
state . wsToken = token
} ,
2017-12-05 02:47:10 -08:00
setSocket ( state , socket ) {
state . socket = socket
2017-12-07 08:20:44 -08:00
} ,
2018-06-06 18:24:31 -07:00
setFollowRequests ( state , value ) {
state . followRequests = value
2020-05-07 06:10:53 -07:00
} ,
setMastoUserSocketStatus ( state , value ) {
state . mastoUserSocketStatus = value
2021-03-08 12:24:39 -08:00
} ,
2021-03-08 16:38:10 -08:00
incrementRetryMultiplier ( state ) {
state . retryMultiplier = Math . max ( ++ state . retryMultiplier , 3 )
2021-03-08 12:24:39 -08:00
} ,
2021-03-08 16:38:10 -08:00
resetRetryMultiplier ( state ) {
state . retryMultiplier = 1
2017-02-16 02:17:47 -08:00
}
} ,
actions : {
2021-03-08 16:38:10 -08:00
/ * *
* Global MastoAPI socket control , in future should disable ALL sockets / ( re ) start relevant sockets
*
* @ param { Boolean } [ initial ] - whether this enabling happened at boot time or not
* /
enableMastoSockets ( store , initial ) {
2021-03-08 12:24:39 -08:00
const { state , dispatch , commit } = store
2021-01-13 11:33:20 -08:00
// Do not initialize unless nonexistent or closed
if (
state . mastoUserSocket &&
! [
WebSocket . CLOSED ,
WebSocket . CLOSING
] . includes ( state . mastoUserSocket . getState ( ) )
) {
return
}
2021-03-08 16:38:10 -08:00
if ( initial ) {
commit ( 'setMastoUserSocketStatus' , WSConnectionStatus . STARTING _INITIAL )
} else {
commit ( 'setMastoUserSocketStatus' , WSConnectionStatus . STARTING )
}
2019-12-26 04:12:35 -08:00
return dispatch ( 'startMastoUserSocket' )
2019-12-10 11:30:27 -08:00
} ,
disableMastoSockets ( store ) {
2021-03-08 12:24:39 -08:00
const { state , dispatch , commit } = store
2019-12-10 11:30:27 -08:00
if ( ! state . mastoUserSocket ) return
2021-03-08 16:38:10 -08:00
commit ( 'setMastoUserSocketStatus' , WSConnectionStatus . DISABLED )
2019-12-26 04:12:35 -08:00
return dispatch ( 'stopMastoUserSocket' )
2019-12-10 11:30:27 -08:00
} ,
2019-12-08 06:05:41 -08:00
// MastoAPI 'User' sockets
startMastoUserSocket ( store ) {
2019-12-26 04:12:35 -08:00
return new Promise ( ( resolve , reject ) => {
try {
2020-05-07 06:10:53 -07:00
const { state , commit , dispatch , rootState } = store
2019-12-26 04:35:46 -08:00
const timelineData = rootState . statuses . timelines . friends
2019-12-26 04:12:35 -08:00
state . mastoUserSocket = state . backendInteractor . startUserSocket ( { store } )
state . mastoUserSocket . addEventListener (
'message' ,
( { detail : message } ) => {
if ( ! message ) return // pings
if ( message . event === 'notification' ) {
dispatch ( 'addNewNotifications' , {
notifications : [ message . notification ] ,
older : false
} )
} else if ( message . event === 'update' ) {
dispatch ( 'addNewStatuses' , {
statuses : [ message . status ] ,
userId : false ,
2019-12-26 04:35:46 -08:00
showImmediately : timelineData . visibleStatuses . length === 0 ,
2019-12-26 04:12:35 -08:00
timeline : 'friends'
} )
2020-09-06 05:28:09 -07:00
} else if ( message . event === 'delete' ) {
dispatch ( 'deleteStatusById' , message . id )
2020-05-07 06:10:53 -07:00
} else if ( message . event === 'pleroma:chat_update' ) {
2020-10-29 03:33:06 -07:00
// The setTimeout wrapper is a temporary band-aid to avoid duplicates for the user's own messages when doing optimistic sending.
// The cause of the duplicates is the WS event arriving earlier than the HTTP response.
// This setTimeout wrapper can be removed once the commit `8e41baff` is in the stable Pleroma release.
// (`8e41baff` adds the idempotency key to the chat message entity, which PleromaFE uses when it's available, and it makes this artificial delay unnecessary).
setTimeout ( ( ) => {
dispatch ( 'addChatMessages' , {
chatId : message . chatUpdate . id ,
messages : [ message . chatUpdate . lastMessage ]
} )
dispatch ( 'updateChat' , { chat : message . chatUpdate } )
maybeShowChatNotification ( store , message . chatUpdate )
} , 100 )
2019-12-26 04:12:35 -08:00
}
}
)
2020-05-07 06:10:53 -07:00
state . mastoUserSocket . addEventListener ( 'open' , ( ) => {
2021-01-13 11:31:57 -08:00
// Do not show notification when we just opened up the page
2021-03-08 16:38:10 -08:00
if ( state . mastoUserSocketStatus !== WSConnectionStatus . STARTING _INITIAL ) {
2021-01-13 11:31:57 -08:00
dispatch ( 'pushGlobalNotice' , {
level : 'success' ,
messageKey : 'timeline.socket_reconnected' ,
timeout : 5000
} )
}
2021-03-08 16:38:10 -08:00
// Stop polling if we were errored or disabled
if ( new Set ( [
WSConnectionStatus . ERROR ,
WSConnectionStatus . DISABLED
] ) . has ( state . mastoUserSocketStatus ) ) {
dispatch ( 'stopFetchingTimeline' , { timeline : 'friends' } )
dispatch ( 'stopFetchingNotifications' )
dispatch ( 'stopFetchingChats' )
}
commit ( 'resetRetryMultiplier' )
2020-05-07 06:10:53 -07:00
commit ( 'setMastoUserSocketStatus' , WSConnectionStatus . JOINED )
} )
2019-12-26 04:12:35 -08:00
state . mastoUserSocket . addEventListener ( 'error' , ( { detail : error } ) => {
console . error ( 'Error in MastoAPI websocket:' , error )
2021-03-08 16:38:10 -08:00
// TODO is this needed?
2020-05-07 06:10:53 -07:00
dispatch ( 'clearOpenedChats' )
2019-12-26 04:12:35 -08:00
} )
state . mastoUserSocket . addEventListener ( 'close' , ( { detail : closeEvent } ) => {
const ignoreCodes = new Set ( [
1000 , // Normal (intended) closure
1001 // Going away
] )
const { code } = closeEvent
if ( ignoreCodes . has ( code ) ) {
console . debug ( ` Not restarting socket becasue of closure code ${ code } is in ignore list ` )
2021-03-08 16:38:10 -08:00
commit ( 'setMastoUserSocketStatus' , WSConnectionStatus . CLOSED )
2019-12-26 04:12:35 -08:00
} else {
console . warn ( ` MastoAPI websocket disconnected, restarting. CloseEvent code: ${ code } ` )
2021-03-08 12:24:39 -08:00
setTimeout ( ( ) => {
2021-03-08 16:38:10 -08:00
dispatch ( 'startMastoUserSocket' )
} , retryTimeout ( state . retryMultiplier ) )
commit ( 'incrementRetryMultiplier' )
if ( state . mastoUserSocketStatus !== WSConnectionStatus . ERROR ) {
dispatch ( 'startFetchingTimeline' , { timeline : 'friends' } )
dispatch ( 'startFetchingNotifications' )
dispatch ( 'startFetchingChats' )
2021-03-08 12:24:39 -08:00
dispatch ( 'pushGlobalNotice' , {
level : 'error' ,
messageKey : 'timeline.socket_broke' ,
messageArgs : [ code ] ,
timeout : 5000
} )
}
2021-03-08 16:38:10 -08:00
commit ( 'setMastoUserSocketStatus' , WSConnectionStatus . ERROR )
2019-12-26 04:12:35 -08:00
}
2020-05-07 06:10:53 -07:00
dispatch ( 'clearOpenedChats' )
2019-12-26 04:12:35 -08:00
} )
resolve ( )
} catch ( e ) {
reject ( e )
2019-12-08 06:05:41 -08:00
}
2019-11-24 12:01:12 -08:00
} )
2019-11-24 08:50:28 -08:00
} ,
2019-12-10 11:30:27 -08:00
stopMastoUserSocket ( { state , dispatch } ) {
dispatch ( 'startFetchingTimeline' , { timeline : 'friends' } )
dispatch ( 'startFetchingNotifications' )
2020-05-07 06:10:53 -07:00
dispatch ( 'startFetchingChats' )
2019-12-10 11:30:27 -08:00
state . mastoUserSocket . close ( )
} ,
2019-12-08 06:05:41 -08:00
// Timelines
startFetchingTimeline ( store , {
timeline = 'friends' ,
tag = false ,
userId = false
} ) {
2019-02-07 15:23:18 -08:00
if ( store . state . fetchers [ timeline ] ) return
2019-12-08 06:05:41 -08:00
const fetcher = store . state . backendInteractor . startFetchingTimeline ( {
timeline , store , userId , tag
} )
2019-04-04 09:03:56 -07:00
store . commit ( 'addFetcher' , { fetcherName : timeline , fetcher } )
2017-02-16 02:17:47 -08:00
} ,
2019-12-08 06:05:41 -08:00
stopFetchingTimeline ( store , timeline ) {
const fetcher = store . state . fetchers [ timeline ]
if ( ! fetcher ) return
store . commit ( 'removeFetcher' , { fetcherName : timeline , fetcher } )
} ,
2021-01-13 12:17:10 -08:00
fetchTimeline ( store , timeline , { ... rest } ) {
store . state . backendInteractor . fetchTimeline ( {
store ,
timeline ,
... rest
} )
} ,
2019-04-04 09:03:56 -07:00
2019-12-08 06:05:41 -08:00
// Notifications
startFetchingNotifications ( store ) {
if ( store . state . fetchers . notifications ) return
2019-04-04 09:03:56 -07:00
const fetcher = store . state . backendInteractor . startFetchingNotifications ( { store } )
store . commit ( 'addFetcher' , { fetcherName : 'notifications' , fetcher } )
} ,
2019-12-08 06:05:41 -08:00
stopFetchingNotifications ( store ) {
const fetcher = store . state . fetchers . notifications
if ( ! fetcher ) return
store . commit ( 'removeFetcher' , { fetcherName : 'notifications' , fetcher } )
} ,
2021-01-13 12:17:10 -08:00
fetchNotifications ( store , { ... rest } ) {
store . state . backendInteractor . fetchNotifications ( {
store ,
... rest
} )
} ,
2019-11-19 06:07:15 -08:00
2019-12-08 06:05:41 -08:00
// Follow requests
startFetchingFollowRequests ( store ) {
if ( store . state . fetchers [ 'followRequests' ] ) return
const fetcher = store . state . backendInteractor . startFetchingFollowRequests ( { store } )
2020-01-21 07:51:49 -08:00
2019-12-08 06:05:41 -08:00
store . commit ( 'addFetcher' , { fetcherName : 'followRequests' , fetcher } )
2019-11-19 06:07:15 -08:00
} ,
2019-12-08 06:05:41 -08:00
stopFetchingFollowRequests ( store ) {
const fetcher = store . state . fetchers . followRequests
if ( ! fetcher ) return
store . commit ( 'removeFetcher' , { fetcherName : 'followRequests' , fetcher } )
} ,
removeFollowRequest ( store , request ) {
let requests = store . state . followRequests . filter ( ( it ) => it !== request )
store . commit ( 'setFollowRequests' , requests )
2017-12-05 02:47:10 -08:00
} ,
2019-12-08 06:05:41 -08:00
// Pleroma websocket
2019-01-29 07:16:25 -08:00
setWsToken ( store , token ) {
store . commit ( 'setWsToken' , token )
} ,
2019-08-17 01:18:42 -07:00
initializeSocket ( { dispatch , commit , state , rootState } ) {
2017-12-05 02:47:10 -08:00
// Set up websocket connection
2019-08-17 01:18:42 -07:00
const token = state . wsToken
if ( rootState . instance . chatAvailable && typeof token !== 'undefined' && state . socket === null ) {
2019-06-18 13:28:31 -07:00
const socket = new Socket ( '/socket' , { params : { token } } )
2017-12-07 08:20:44 -08:00
socket . connect ( )
2019-08-17 01:18:42 -07:00
commit ( 'setSocket' , socket )
dispatch ( 'initializeChat' , socket )
2017-12-07 08:20:44 -08:00
}
} ,
2019-08-17 01:18:42 -07:00
disconnectFromSocket ( { commit , state } ) {
state . socket && state . socket . disconnect ( )
commit ( 'setSocket' , null )
2016-11-26 09:57:08 -08:00
}
}
}
export default api