-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Expand file tree
/
Copy pathBrowserNotifications.ts
More file actions
201 lines (184 loc) · 6.6 KB
/
Copy pathBrowserNotifications.ts
File metadata and controls
201 lines (184 loc) · 6.6 KB
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Web 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 {translateLocal} from '@libs/Localize';
import {getForReportAction} from '@libs/ModifiedExpenseMessage';
import {getTextFromHtml} from '@libs/ReportActionsUtils';
import {getReportName} from '@libs/ReportNameUtils';
import * as ReportUtils from '@libs/ReportUtils';
import playSound, {SOUNDS} from '@libs/Sound';
import type {Report, ReportAction, ReportAttributesDerivedValue} from '@src/types/onyx';
import SafeString from '@src/utils/SafeString';
import type {LocalNotificationClickHandler, LocalNotificationData, LocalNotificationModifiedExpensePushParams} 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: SafeString(icon),
data,
silent: true,
tag,
});
if (!silent) {
playSound(SOUNDS.RECEIVE);
}
notificationCache[notificationID].onclick = () => {
onClick();
window.parent.focus();
window.focus();
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,
reportAttributes?: ReportAttributesDerivedValue['reports'],
) {
let title;
let body;
const icon = usesIcon ? EXPENSIFY_ICON_URL : '';
const isRoomOrGroupChat = ReportUtils.isChatRoom(report) || ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isGroupChat(report);
const {person, message} = reportAction;
const plainTextPerson = person?.map((f) => Str.removeSMSDomain(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 (isRoomOrGroupChat) {
const roomName = getReportName(report, reportAttributes);
title = roomName;
body = `${plainTextPerson}: ${plainTextMessage}`;
} else {
title = plainTextPerson;
body = plainTextMessage;
}
const data = {
reportID: report.reportID,
};
push(title, body, icon, data, onClick);
},
pushModifiedExpenseNotification({
report,
reportAction,
movedFromReport,
movedToReport,
onClick,
usesIcon = false,
policyTags,
policy,
currentUserLogin,
reportAttributes,
}: LocalNotificationModifiedExpensePushParams) {
const title = reportAction.person?.map((f) => f.text).join(', ') ?? '';
const bodyWithHTML = getForReportAction({
translate: translateLocal,
reportAction,
policy,
movedFromReport,
movedToReport,
policyTags,
currentUserLogin,
reportAttributes,
});
// Strip HTML tags for plain text notification body
const body = getTextFromHtml(bodyWithHTML);
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) {
for (const notification of Object.values(notificationCache)) {
if (!shouldClearNotification(notification.data as LocalNotificationData)) {
continue;
}
notification.close();
}
},
};