import { map, includes, filter, get } from 'lodash' import { Observable, Observer } from 'rxjs' import { combineEpics, ActionsObservable } from 'redux-observable' import { Store } from 'redux' import * as ConversationAction from './actions' import * as api from './api' import * as selectors from './selectors' import { IInbox, IConversationWithUnread, IReadReceiptPayload, IMessageNewPayload, IConversation, IMessage, IFetchConversationsByIdOptions, ConversationStatus, } from './interfaces' const { BULK_SENT, CLOSED } = ConversationStatus import * as ActivityGroupsActions from '../activityGroups/actions' import * as UserSelectors from '../auth/selectors' import * as ConversationsSelectors from '../conversations/selectors' import * as SocketConstants from '../socket/constants' export const fetchOpenAndUnreadConversations = (action$: ActionsObservable, { getState }: Store) => { return action$.ofType(ActivityGroupsActions.fetchActivityGroupsSuccess.toString()) .map(() => (UserSelectors.accessToken(getState()))) .mergeMap((accessToken) => { const userId = UserSelectors.userId(getState()) return api.fetchInboxes(accessToken) .flatMap((inboxes: IInbox[]) => map(inboxes, 'id')) .flatMap((inbox: number) => { return api.getAllOpenAndPendingConvos({ accessToken, userId, inbox }) .flatMap((conversations) => { return [ ConversationAction.fetchConversationsFromInbox({ listOfConversationIds: map(conversations, 'id') }), ConversationAction.storeParticipantIds(conversations), ] }) }) .startWith(ConversationAction.fetchConversationsPending()) .catch(() => Observable.of(ConversationAction.fetchConversationsFailed())) }) } export const fetchConversationsWithUnreadCounts = (action$: ActionsObservable, { getState }: Store) => { return action$.ofType(ConversationAction.fetchConversationsFromInbox.toString()) .pluck('payload') .map(listOfConversationIds => ({ ...listOfConversationIds, accessToken: UserSelectors.accessToken(getState()) })) .mergeMap((options: IFetchConversationsByIdOptions) => { return api.fetchConversationsById(options) .map((conversationsById: IConversationWithUnread[]) => ConversationAction.fetchConversationsFromInboxSuccess(map(conversationsById, ({ conversation, unreadMessages }) => ({ ...conversation, unreadMessageCount: unreadMessages })))) }) .startWith(ConversationAction.fetchConversationsFromInboxPending()) .catch(() => Observable.of(ConversationAction.fetchConversationsFromInboxFailed())) } export const fetchMessagesForConversations = (action$: ActionsObservable, { getState }: Store) => { return action$.ofType(ConversationAction.fetchConversationsFromInboxSuccess.toString()) .pluck('payload') .mergeMap((conversations: IConversation[]) => map(conversations, 'id')) .flatMap((conversationId) => { return api.getMessagesForConversation({ conversationId, accessToken: UserSelectors.accessToken(getState()) }) .map((conversations) => { const filteredConversations = filter(conversations, (message: IMessage) => message.type === 'CHAT') return ConversationAction.fetchMessagesForConversationSuccess({ [conversationId]: filteredConversations }) }) }) } export const joinOrgChannelPendingEpic = (action$: ActionsObservable, { getState }: Store) => { return action$ .ofType(SocketConstants.JOIN_CHANNEL_PENDING) .pluck('payload') .filter((payload: any) => payload.clusterName === SocketConstants.SocketCluster.MESSAGES) .filter((payload: any) => payload.channelName === SocketConstants.SocketChannel.ORG_PRIVATE) .switchMap(({ channel }) => { return Observable.create((observer: Observer) => { channel.on('phx_reply', (payload: any) => { if (payload.status === 'ok') { observer.next(ConversationAction.resetConversations()) } }) channel.on('message-recipient-status', (payload: IReadReceiptPayload) => { const userId = UserSelectors.user(getState()).id if (payload.recipientType === 'USER' && payload.recipientId === userId) { observer.next(ConversationAction.receiveReadReceipt(payload)) } }) channel.on('message-new', (payload: IMessageNewPayload) => { const conversationIds = selectors.conversationIds(getState()) const userId = UserSelectors.user(getState()).id if (includes(conversationIds, payload.conversationId) && payload.contentType === 'CHAT') { observer.next(ConversationAction.newMessageInConversation(payload)) } if (payload.contentType === 'CONVERSATION_EVENT' && payload.conversationEvent.conversationStatus !== BULK_SENT) { if (includes(conversationIds, payload.conversationId)) { if (includes(['ADDED_PARTICIPANTS', 'REMOVED_PARTICIPANTS'], payload.conversationEvent.type)) { observer.next(ConversationAction.participantChange(payload)) } } else { if (payload.conversationEvent.type === 'ADDED_PARTICIPANTS' && includes(payload.conversationEvent.currentParticipantIds, userId)) { observer.next(ConversationAction.participantChange(payload)) observer.next(ConversationAction.fetchConversationsFromInbox({ listOfConversationIds: [payload.conversationId] })) } } } }) channel.on('conversation-new', (payload: IConversation) => { const userId = UserSelectors.user(getState()).id if (includes(payload.participantIds, userId) && payload.status !== BULK_SENT) { observer.next(ConversationAction.fetchConversationsFromInbox({ listOfConversationIds: [payload.id] })) observer.next(ConversationAction.storeParticipantIds([payload])) } }) channel.on('conversation-updated', (payload: IConversation) => { const state = getState() const userId = UserSelectors.user(state).id const participants = ConversationsSelectors.participants(state) const participantsForConvo = get(participants, [payload.id, 'participantIds'], payload.participantIds) const conversationIds = selectors.conversationIds(getState()) if (includes(participantsForConvo, userId) && payload.status !== BULK_SENT) { if (includes(conversationIds, payload.id)) { if (payload.status === CLOSED) { observer.next(ConversationAction.deleteMessagesByConversationId(payload)) observer.next(ConversationAction.deleteConversation(payload)) } else { observer.next(ConversationAction.updateConversation(payload)) } } else { observer.next(ConversationAction.fetchConversationsFromInbox({ listOfConversationIds: [payload.id] })) observer.next(ConversationAction.storeParticipantIds([payload])) } } }) }) }) } export const epic = combineEpics( fetchOpenAndUnreadConversations, fetchMessagesForConversations, fetchConversationsWithUnreadCounts, joinOrgChannelPendingEpic, )