import { Observable } from 'rxjs' import { combineEpics, ActionsObservable } from 'redux-observable' import { Store } from 'redux' import { chain } from 'lodash' import * as selectors from './selectors' import * as helpers from './helpers' import * as ActivityGroupActions from '../activityGroups/actions' import * as ConversationsActions from '../conversations/actions' import * as PopupActions from '../popup/actions' import * as AvatarSelectors from '../avatar/selectors' import * as AuthSelectors from '../auth/selectors' import { ActivityType } from '../activity/interfaces' import { IActivityGroup, INotificationSettingsToActivityType } from '../activityGroups/interfaces' import * as SettingsSelectors from '../settings/selectors' const notificationClick$ = Observable.fromEventPattern( (handler: any) => chrome.notifications.onClicked.addListener(handler), (handler: any) => chrome.notifications.onClicked.removeListener(handler), ) export const notificationEpic = (action$: ActionsObservable, store: Store) => { return action$ .ofType(ActivityGroupActions.receiveActivityGroups.toString()) .pluck('payload') .flatMap((activityGroups: IActivityGroup[]) => activityGroups) .filter((activityGroups: IActivityGroup) => { return ( activityGroups.type === ActivityType.CLICK || activityGroups.type === ActivityType.OPEN || activityGroups.type === ActivityType.PAGE_VIEW ) }) .bufferTime(5000) .filter(activityGroups => !!activityGroups.length) .flatMap((activityGroups) => { return chain(activityGroups) .groupBy(activityGroups => activityGroups.email) .map((activityGroups: IActivityGroup[], email: string) => { return { email, activityGroups } }) .value() }) .mergeMap(({ email, activityGroups }) => { const state = store.getState() const muteList = SettingsSelectors.notificationMuteList(state) const domainMuteList = SettingsSelectors.domainMuteList(state) const NotificationSettingsToActivityType: INotificationSettingsToActivityType = { [ActivityType.PAGE_VIEW]: SettingsSelectors.notificationVisitedSite(state), [ActivityType.CLICK]: SettingsSelectors.notificationClickedEmailLink(state), [ActivityType.OPEN]: SettingsSelectors.notificationOpenedEmail(state), } const reducedActivityGroups = activityGroups.filter(activity => NotificationSettingsToActivityType[activity.type]) if ( reducedActivityGroups.length === 0 || helpers.emailOnMuteList(email, muteList) || helpers.emailOnDomainMuteList(email, domainMuteList) ) { return Observable.empty() } const iconUrl = AvatarSelectors.makeAvatarDataUrl(email)(store.getState()) || 'bw_icon_128.png' const message = helpers.toNotification(reducedActivityGroups) const gmailId = helpers.findGmailId(reducedActivityGroups) let url: string if (gmailId) { url = `https://mail.google.com/mail/u/0/#sent/${gmailId}` } else { url = 'https://mail.google.com/' } let notificationId: string chrome.notifications.create( { iconUrl, message, type: 'basic', title: email, }, (id) => { notificationId = id }, ) return notificationClick$ .filter((id: string) => id === notificationId) .first() .mergeMap(() => { chrome.notifications.clear(notificationId, () => { chrome.tabs.create({ url }, (tab) => { chrome.tabs.onUpdated.addListener(function waitForComplete(tabId, info) { if (info.status === 'complete') { chrome.tabs.onUpdated.removeListener(waitForComplete) chrome.tabs.sendMessage(tab.id, { email, type: '@@driftext-content/show_modal' }) } }) }) // TODO: switch to an active tab? // TODO: pop up an in-page notification? // chrome.tabs.query({ url: '*://mail.google.com/*' }, (result: any) => { // if (result.length) { // // switch to tab and send event to show modal // chrome.tabs.update(result[0].id, { active: true }) // //chrome.tabs.sendMessage(result[0].id, { type: '@@driftext/show_modal' }) // } else { // // create tab to gmail, wait for page to load, and then send event to show modal // chrome.tabs.create({ url: 'https://mail.google.com' }, (tab) => { // chrome.tabs.onUpdated.addListener(function waitForComplete(tabId, info) { // if (info.status === 'complete') { // chrome.tabs.onUpdated.removeListener(waitForComplete) // //chrome.tabs.sendMessage(tab.id, { type: '@@driftext/show_modal' }) // } // }) // }) // } // }) }) return Observable.empty() }) }) } export const notificationBadgeCountEpic = (action$: ActionsObservable, store: Store) => { return action$ .ofType( ActivityGroupActions.receiveActivityGroups.toString(), ActivityGroupActions.fetchActivityGroupsSuccess.toString(), ConversationsActions.newConversation.toString(), ConversationsActions.updateConversation.toString(), ConversationsActions.receiveReadReceipt.toString(), ConversationsActions.fetchConversationsSuccess.toString(), PopupActions.updateLastSeenAt.toString(), ) .debounceTime(2000) .filter(() => !!AuthSelectors.accessToken(store.getState())) .map(() => selectors.unseenActivityCount(store.getState())) .mergeMap((count) => { if (count > 0) { chrome.browserAction.setBadgeText({ text: `${count}` }) chrome.browserAction.setBadgeBackgroundColor({ color: '#E6314F' }) } else { chrome.browserAction.setBadgeText({ text: '' }) } return Observable.empty() }) } export const epic = combineEpics(notificationEpic, notificationBadgeCountEpic)