Summary: Changelog: Fixed application crash notifications not opening the crash log Crashlog notifications were quite blatantly broken: 1. Because I force all GK's to true locally during testing, and the feature gate is negative, I never had any crash reports in the first place. The GK was empty so removed it. 2. The notifications overview would always cause a NPE when the notification came from a device plugin (it assumed a client to be present). This made the button do nothing and the device name not show up near the notification 3. The OS level notifications would link to the notification screen, which was a static screen in the classic UI and would always render to an empty page in Sandy. Removed that screen from the code base. Instead the click the notification will now trigger the notification action. Reviewed By: passy Differential Revision: D28119719 fbshipit-source-id: 5b28dd854260fd479d09e3ee6206301cc669ab40
205 lines
6.6 KiB
TypeScript
205 lines
6.6 KiB
TypeScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
*/
|
|
|
|
import {Store} from '../reducers/index';
|
|
import {Logger} from '../fb-interfaces/Logger';
|
|
import {PluginNotification} from '../reducers/notifications';
|
|
import {PluginDefinition} from '../plugin';
|
|
import {ipcRenderer, IpcRendererEvent} from 'electron';
|
|
import {
|
|
setActiveNotifications,
|
|
updatePluginBlocklist,
|
|
updateCategoryBlocklist,
|
|
} from '../reducers/notifications';
|
|
import {textContent} from '../utils/index';
|
|
import {deconstructPluginKey} from '../utils/clientUtils';
|
|
import {getPluginTitle, isSandyPlugin} from '../utils/pluginUtils';
|
|
import {sideEffect} from '../utils/sideEffect';
|
|
import {openNotification} from '../sandy-chrome/notification/Notification';
|
|
|
|
type NotificationEvents = 'show' | 'click' | 'close' | 'reply' | 'action';
|
|
const NOTIFICATION_THROTTLE = 5 * 1000; // in milliseconds
|
|
|
|
export default (store: Store, logger: Logger) => {
|
|
const knownNotifications: Set<string> = new Set();
|
|
const knownPluginStates: Map<string, Object> = new Map();
|
|
const lastNotificationTime: Map<string, number> = new Map();
|
|
|
|
ipcRenderer.on(
|
|
'notificationEvent',
|
|
(
|
|
_event: IpcRendererEvent,
|
|
eventName: NotificationEvents,
|
|
pluginNotification: PluginNotification,
|
|
arg: null | string | number,
|
|
) => {
|
|
if (eventName === 'click' || (eventName === 'action' && arg === 0)) {
|
|
openNotification(store, pluginNotification);
|
|
} else if (eventName === 'action') {
|
|
if (arg === 1 && pluginNotification.notification.category) {
|
|
// Hide similar (category)
|
|
logger.track(
|
|
'usage',
|
|
'notification-hide-category',
|
|
pluginNotification,
|
|
);
|
|
|
|
const {category} = pluginNotification.notification;
|
|
const {blocklistedCategories} = store.getState().notifications;
|
|
if (category && blocklistedCategories.indexOf(category) === -1) {
|
|
store.dispatch(
|
|
updateCategoryBlocklist([...blocklistedCategories, category]),
|
|
);
|
|
}
|
|
} else if (arg === 2) {
|
|
// Hide plugin
|
|
logger.track('usage', 'notification-hide-plugin', pluginNotification);
|
|
|
|
const {blocklistedPlugins} = store.getState().notifications;
|
|
if (blocklistedPlugins.indexOf(pluginNotification.pluginId) === -1) {
|
|
store.dispatch(
|
|
updatePluginBlocklist([
|
|
...blocklistedPlugins,
|
|
pluginNotification.pluginId,
|
|
]),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
sideEffect(
|
|
store,
|
|
{name: 'notifications', throttleMs: 500},
|
|
({notifications, pluginStates, plugins}) => ({
|
|
notifications,
|
|
pluginStates,
|
|
devicePlugins: plugins.devicePlugins,
|
|
clientPlugins: plugins.clientPlugins,
|
|
}),
|
|
({notifications, pluginStates, devicePlugins, clientPlugins}, store) => {
|
|
function getPlugin(name: string) {
|
|
return devicePlugins.get(name) ?? clientPlugins.get(name);
|
|
}
|
|
|
|
Object.keys(pluginStates).forEach((key) => {
|
|
if (knownPluginStates.get(key) !== pluginStates[key]) {
|
|
knownPluginStates.set(key, pluginStates[key]);
|
|
const plugin = deconstructPluginKey(key);
|
|
const pluginName = plugin.pluginName;
|
|
const client = plugin.client;
|
|
|
|
if (!pluginName) {
|
|
return;
|
|
}
|
|
|
|
const persistingPlugin: undefined | PluginDefinition = getPlugin(
|
|
pluginName,
|
|
);
|
|
if (
|
|
persistingPlugin &&
|
|
!isSandyPlugin(persistingPlugin) &&
|
|
persistingPlugin.getActiveNotifications
|
|
) {
|
|
try {
|
|
const notifications = persistingPlugin.getActiveNotifications(
|
|
pluginStates[key],
|
|
);
|
|
store.dispatch(
|
|
setActiveNotifications({
|
|
notifications,
|
|
client,
|
|
pluginId: pluginName,
|
|
}),
|
|
);
|
|
} catch (e) {
|
|
console.error(
|
|
'Failed to compute notifications for plugin ' + pluginName,
|
|
e,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
const {
|
|
activeNotifications,
|
|
blocklistedPlugins,
|
|
blocklistedCategories,
|
|
} = notifications;
|
|
|
|
activeNotifications
|
|
.map((n) => ({
|
|
...n,
|
|
notification: {
|
|
...n.notification,
|
|
message: textContent(n.notification.message),
|
|
},
|
|
}))
|
|
.forEach((n: PluginNotification) => {
|
|
if (
|
|
store.getState().connections.selectedPlugin !== 'notifications' &&
|
|
!knownNotifications.has(n.notification.id) &&
|
|
blocklistedPlugins.indexOf(n.pluginId) === -1 &&
|
|
(!n.notification.category ||
|
|
blocklistedCategories.indexOf(n.notification.category) === -1)
|
|
) {
|
|
const prevNotificationTime: number =
|
|
lastNotificationTime.get(n.pluginId) || 0;
|
|
lastNotificationTime.set(n.pluginId, new Date().getTime());
|
|
knownNotifications.add(n.notification.id);
|
|
|
|
if (
|
|
new Date().getTime() - prevNotificationTime <
|
|
NOTIFICATION_THROTTLE
|
|
) {
|
|
// Don't send a notification if the plugin has sent a notification
|
|
// within the NOTIFICATION_THROTTLE.
|
|
return;
|
|
}
|
|
const plugin = getPlugin(n.pluginId);
|
|
ipcRenderer.send('sendNotification', {
|
|
payload: {
|
|
title: n.notification.title,
|
|
body: n.notification.message,
|
|
actions: [
|
|
{
|
|
type: 'button',
|
|
text: 'Show',
|
|
},
|
|
{
|
|
type: 'button',
|
|
text: 'Hide similar',
|
|
},
|
|
{
|
|
type: 'button',
|
|
text: `Hide all ${
|
|
plugin != null ? getPluginTitle(plugin) : ''
|
|
}`,
|
|
},
|
|
],
|
|
closeButtonText: 'Hide',
|
|
},
|
|
closeAfter: 10000,
|
|
pluginNotification: n,
|
|
});
|
|
logger.track('usage', 'native-notification', {
|
|
...n.notification,
|
|
message:
|
|
typeof n.notification.message === 'string'
|
|
? n.notification.message
|
|
: '<ReactNode>',
|
|
});
|
|
}
|
|
});
|
|
},
|
|
);
|
|
};
|