-
Notifications
You must be signed in to change notification settings - Fork 2.8k
/
BrowserNotifications.ts
165 lines (148 loc) · 5.61 KB
/
BrowserNotifications.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Web and desktop implementation only. Do not import for direct use. Use LocalNotification.
import {Str} from 'expensify-common';
import type {ImageSourcePropType} from 'react-native';
import EXPENSIFY_ICON_URL from '@assets/images/expensify-logo-round-clearspace.png';
import * as AppUpdate from '@libs/actions/AppUpdate';
import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage';
import {getTextFromHtml} from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import type {Report, ReportAction} from '@src/types/onyx';
import focusApp from './focusApp';
import type {LocalNotificationClickHandler, LocalNotificationData} from './types';
const notificationCache: Record<string, Notification> = {};
/**
* Checks if the user has granted permission to show browser notifications
*/
function canUseBrowserNotifications(): Promise<boolean> {
return new Promise((resolve) => {
// They have no browser notifications so we can't use this feature
if (!window.Notification) {
resolve(false);
return;
}
// Check if they previously granted or denied us access to send a notification
const permissionGranted = Notification.permission === 'granted';
if (permissionGranted || Notification.permission === 'denied') {
resolve(permissionGranted);
return;
}
// Check their global preferences for browser notifications and ask permission if they have none
Notification.requestPermission().then((status) => {
resolve(status === 'granted');
});
});
}
/**
* Light abstraction around browser push notifications.
* Checks for permission before determining whether to send.
*
* @param icon Path to icon
* @param data extra data to attach to the notification
*/
function push(
title: string,
body = '',
icon: string | ImageSourcePropType = '',
data: LocalNotificationData = {},
onClick: LocalNotificationClickHandler = () => {},
silent = false,
tag = '',
) {
canUseBrowserNotifications().then((canUseNotifications) => {
if (!canUseNotifications) {
return;
}
// We cache these notifications so that we can clear them later
const notificationID = Str.guid();
notificationCache[notificationID] = new Notification(title, {
body,
icon: String(icon),
data,
silent,
tag,
});
notificationCache[notificationID].onclick = () => {
onClick();
window.parent.focus();
window.focus();
focusApp();
notificationCache[notificationID].close();
};
notificationCache[notificationID].onclose = () => {
delete notificationCache[notificationID];
};
});
}
/**
* BrowserNotification
* @namespace
*/
export default {
/**
* Create a report comment notification
*
* @param usesIcon true if notification uses right circular icon
*/
pushReportCommentNotification(report: Report, reportAction: ReportAction, onClick: LocalNotificationClickHandler, usesIcon = false) {
let title;
let body;
const icon = usesIcon ? EXPENSIFY_ICON_URL : '';
const isChatRoom = ReportUtils.isChatRoom(report);
const {person, message} = reportAction;
const plainTextPerson = person?.map((f) => f.text).join() ?? '';
// Specifically target the comment part of the message
let plainTextMessage = '';
if (Array.isArray(message)) {
plainTextMessage = getTextFromHtml(message?.find((f) => f?.type === 'COMMENT')?.html);
} else {
plainTextMessage = message?.type === 'COMMENT' ? getTextFromHtml(message?.html) : '';
}
if (isChatRoom) {
const roomName = ReportUtils.getReportName(report);
title = roomName;
body = `${plainTextPerson}: ${plainTextMessage}`;
} else {
title = plainTextPerson;
body = plainTextMessage;
}
const data = {
reportID: report.reportID,
};
push(title, body, icon, data, onClick, true);
},
pushModifiedExpenseNotification(report: Report, reportAction: ReportAction, onClick: LocalNotificationClickHandler, usesIcon = false) {
const title = reportAction.person?.map((f) => f.text).join(', ') ?? '';
const body = ModifiedExpenseMessage.getForReportAction(report.reportID, reportAction);
const icon = usesIcon ? EXPENSIFY_ICON_URL : '';
const data = {
reportID: report.reportID,
};
push(title, body, icon, data, onClick);
},
/**
* Create a notification to indicate that an update is available.
*/
pushUpdateAvailableNotification() {
push(
'Update available',
'A new version of this app is available!',
'',
{},
() => {
AppUpdate.triggerUpdateAvailable();
},
false,
'UpdateAvailable',
);
},
/**
* Clears all open notifications where shouldClearNotification returns true
*
* @param shouldClearNotification a function that receives notification.data and returns true/false if the notification should be cleared
*/
clearNotifications(shouldClearNotification: (notificationData: LocalNotificationData) => boolean) {
Object.values(notificationCache)
.filter((notification) => shouldClearNotification(notification.data as LocalNotificationData))
.forEach((notification) => notification.close());
},
};