-
Notifications
You must be signed in to change notification settings - Fork 0
/
background.js
359 lines (359 loc) · 15.8 KB
/
background.js
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
"use strict";
// @ts-nocheck
// https://stackoverflow.com/questions/47075437/cannot-find-namespace-name-chrome
// These make sure that our function is run every time the browser is opened.
chrome.runtime.onInstalled.addListener(function () {
// called when you manually reload the extension within chrome://extensions, or when the extension calls chrome.runtime.reload()
initializeAlertCheckAlarm();
initializeUserAnalyticsAlarm();
});
chrome.runtime.onStartup.addListener(function () {
// only called when Chrome starts, not when the extension starts.
initializeAlertCheckAlarm();
initializeUserAnalyticsAlarm();
});
// To test in the future if background.js doesn't wake up when needed:
// initializeAlertCheckAlarm();
const clearAllAlarmsAsync = () => {
return new Promise((resolve) => {
chrome.alarms.clearAll(resolve);
});
};
const clearAlarmAsync = (alarmName) => {
return new Promise((resolve) => {
chrome.alarms.clear(alarmName, resolve);
});
};
chrome.runtime.onMessage.addListener((request) => {
(async () => {
if (request === 'refreshAlarms') {
await clearAlarmAsync('checkForTriggeredAlerts');
initializeAlertCheckAlarm();
}
})();
});
async function initializeAlertCheckAlarm() {
const storageLocalObjects = await asyncGetStorageLocal(null);
const settingsObject = storageLocalObjects.redmineTaskNotificationsExtensionSettings;
let alertCheckFrequencyInMinutes = 5;
if (settingsObject) {
alertCheckFrequencyInMinutes = settingsObject.refreshIntervalInMinutes;
}
// console.log(`Alarm set with a refresh interval of ${alertCheckFrequencyInMinutes}`);
// https://developer.chrome.com/docs/extensions/reference/alarms/#type-Alarm
// "Chrome limits alarms to at most once every 1 minute"
// To help you debug your app or extension, when you've loaded it unpacked, there's no limit to how often the alarm can fire.
chrome.alarms.create('checkForTriggeredAlerts', {
periodInMinutes: parseInt(alertCheckFrequencyInMinutes)
});
}
async function initializeUserAnalyticsAlarm() {
// console.log('initializeUserAnalyticsAlarm ran')
chrome.alarms.create('activeUserAnalytics', {
periodInMinutes: 3 // Debug mode
// periodInMinutes: 60 * 2
});
}
// A listener should only be added once (not included in a function)
chrome.alarms.onAlarm.addListener((alarmObject) => {
console.log(`Alarm triggered: ${alarmObject.name}`);
if (alarmObject.name === 'checkForTriggeredAlerts') {
checkForTriggeredAlerts();
}
if (alarmObject.name === 'activeUserAnalytics') {
sendWeeklyActiveUserData();
}
});
// Dev mode:
// function initializeAlertCheckAlarm() {
// chrome.alarms.get('checkForTriggeredAlerts', alarm => {
// if (!alarm) {
// chrome.alarms.create('checkForTriggeredAlerts', { periodInMinutes: 0.2 });
// }
// })
// }
// View all alarms
// chrome.alarms.getAll((alarms) => {
// console.log(alarms);
// });
const checkForTriggeredAlerts = async () => {
try {
const storageLocalObjects = await asyncGetStorageLocal(null);
let wasArrayUpdated = false;
let d = new Date();
let newDateFormatted = ('0' + d.getDate()).slice(-2) +
'-' +
('0' + (d.getMonth() + 1)).slice(-2) +
'-' +
d.getFullYear() +
' ' +
('0' + d.getHours()).slice(-2) +
':' +
('0' + d.getMinutes()).slice(-2);
let alertObjectArray = storageLocalObjects.redmineTaskNotificationsExtension;
const extensionSettingsObject = storageLocalObjects.redmineTaskNotificationsExtensionSettings;
const domainName = extensionSettingsObject.domainName;
const redmineIssueUrl = `${domainName}/issues/`;
if (!!alertObjectArray.length) {
let editedObjectsOfAlertObjectArray = [];
for (const alertObject of alertObjectArray) {
if (alertObject.triggeredInThePast === false) {
// console.log('found an active alert')
let redmineTaskTextDom = await sendRequestAndGetTextDom(redmineIssueUrl, alertObject.redmineTaskId);
const parsedValue = getValueFromTextDom(redmineTaskTextDom, alertObject.fieldToCheckValue);
// console.log('value to check: ' + alertObject.valueToCheckValue)
// console.log('value parsed from text dom: ' + parsedValue)
if (parsedValue === alertObject.valueToCheckValue ||
((parsedValue !== '' && parsedValue !== '-' && parsedValue !== ' ') && alertObject.valueToCheckValue === 'notEmpty')) {
if (wasArrayUpdated === false) {
wasArrayUpdated = true;
}
// Create an updated alert object
alertObject.triggeredInThePast = true;
alertObject.triggeredAtTimestamp = new Date().getTime();
alertObject.triggeredAtReadableDate = newDateFormatted;
editedObjectsOfAlertObjectArray.push(alertObject);
if (extensionSettingsObject) {
// Raise a browser alert in the currently active tab (either newly created or present one depending on user preference)
if (extensionSettingsObject.newTabEnabled === true) {
chrome.tabs.create({
url: redmineIssueUrl + alertObject.redmineTaskId
});
}
const sendMessageToActiveTabContentScript = (action, requestData) => {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, new Object({
action: action,
data: requestData
}), function (response) {
if (response) {
// console.log('background.js worker received a response from content.js...');
}
});
});
};
// Create and focus on a new Redmine tab with the triggered task page
if (extensionSettingsObject.browserAlertEnabled === true) {
await sleep(2 * 1000);
sendMessageToActiveTabContentScript('raiseAlert', new Object({
text: `#${alertObject.redmineTaskId} triggered an alert because "${alertObject.fieldToCheckLabel}" value has changed to "${alertObject.valueToCheckLabel}" (at ${alertObject.triggeredAtReadableDate}).`
}));
}
if (extensionSettingsObject.iconBadgeEnabled === true) {
chrome.action.setBadgeText({ text: ' ' });
chrome.action.setBadgeBackgroundColor({ color: '#FF3C3C' }, () => {
/* ... */
});
}
}
}
}
}
if (wasArrayUpdated === true) {
const updatedAlertObjectArray = replaceObjectsInOriginalArrayWithOtherArrayObjects(alertObjectArray, editedObjectsOfAlertObjectArray, 'uniqueTimestampId');
asyncSetStorageLocal('redmineTaskNotificationsExtension', updatedAlertObjectArray);
console.log('At least one alert was triggered during checkForTriggeredAlerts() check...');
}
else if (wasArrayUpdated === false) {
console.log('No alerts were triggered during checkForTriggeredAlerts() check...');
}
await sleep(1 * 1000);
}
else {
console.log('No active alerts were found, therefore none were checked...');
}
}
catch (e) {
sendErrorLog('Error in background.ts: ' + e);
}
};
const googleFormUrl = 'https://docs.google.com/forms/u/0/d/e/1FAIpQLSeCG85Vno3ZbydBiJjwP6P-nYj-1ZElDBEznt7n4LK5cfJFag/formResponse';
// Send data about active users
const sendWeeklyActiveUserData = async () => {
try {
let isUserActive = 'Error'; // User if active if they created at least 1 alert during the last week
const storageLocalObjects = await asyncGetStorageLocal(null);
const settings = storageLocalObjects.redmineTaskNotificationsExtensionSettings;
const alertsArray = storageLocalObjects.redmineTaskNotificationsExtension;
if (settings === undefined) {
// console.log('sendWeeklyActiveUserData -> settings === undefined')
return;
}
const oneWeekInMiliseconds = 604800 * 1000;
// const oneWeekInMiliseconds = 60 * 1000; // Debug mode
const oneWeekAgoTimestamp = new Date().getTime() - oneWeekInMiliseconds;
// Continue only if the value is older than a week. Otherwise wait until a week has passed
if (parseInt(settings.lastAnalyticsDataSendTimestamp) > oneWeekAgoTimestamp) {
// console.log('sendWeeklyActiveUserData -> parseInt(settings.lastAnalyticsDataSendTimestamp) < oneWeekAgoTimestamp')
return;
}
let weeklyCreatedAlertCount = 0;
let weeklyTriggeredAlertCount = 0;
alertsArray.forEach((object) => {
if (parseInt(object.uniqueTimestampId) > oneWeekAgoTimestamp) {
weeklyCreatedAlertCount++;
}
if (parseInt(object.triggeredAtTimestamp) > oneWeekAgoTimestamp) {
weeklyTriggeredAlertCount++;
}
});
if (weeklyCreatedAlertCount > 0) {
isUserActive = true;
}
else {
isUserActive = false;
}
const userActivityDataObject = {
isUserActive: isUserActive,
weeklyCreatedAlertCount: weeklyCreatedAlertCount,
weeklyTriggeredAlertCount: weeklyTriggeredAlertCount,
userSettingsObject: settings
};
fetch(googleFormUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
'entry.1257070925': 'NA',
'entry.1232033723': 'NA',
'entry.1273942264': 'NA',
'entry.1822505748': 'NA',
'entry.1949912164': 'NA',
'entry.879864049': JSON.stringify(userActivityDataObject)
})
});
// Update the lastAnalyticsDataSendTimestamp by saving current date to the check interval
settings.lastAnalyticsDataSendTimestamp = new Date().getTime();
await asyncSetStorageLocal('redmineTaskNotificationsExtensionSettings', settings);
// console.log('sendWeeklyActiveUserData has ran');
}
catch (e) {
// console.log('Error in sendWeeklyActiveUserData: ' + e)
}
};
// Google form for user analytics and error logging (also in popup.ts [deprecated])
const sendErrorLog = async (errorMessage) => {
fetch(googleFormUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
'entry.1257070925': 'NA',
'entry.1232033723': 'NA',
'entry.1273942264': 'NA',
'entry.1822505748': 'NA',
'entry.1949912164': 'NA',
'entry.879864049': errorMessage
})
});
};
const sendRequestAndGetTextDom = async (domainName, taskId) => {
try {
const redmineResponse = await fetch(`${domainName}${taskId}`, {
method: 'GET',
headers: {},
body: null
});
let htmlString = await redmineResponse.text();
return htmlString;
}
catch (error) {
console.log('ERROR in sendRequestAndGetTextDom func' + error);
return 'error';
}
};
const getValueFromTextDom = (string, fieldId) => {
let regex = new RegExp(`id="${fieldId}"(.|\\n)+?selected="selected"\\svalue="(.*?)"`);
let matchGroup = 2;
if (fieldId === 'issue_assigned_to_id') {
regex = new RegExp(`id="${fieldId}"(.|\\n)+?value="([0-9]*?)"\\sselected="selected"`);
}
let match = regex.exec(string);
if (match) {
if (match[matchGroup]) {
return match[matchGroup]; // [1] is the 1st group that's found
}
else {
return '';
}
}
else {
return '';
}
};
function asyncGetStorageLocal(key) {
return new Promise((resolve) => {
chrome.storage.sync.get(key, resolve);
});
}
function asyncSetStorageLocal(key, newValue) {
return new Promise((resolve) => {
chrome.storage.sync.set({ [key]: newValue }, resolve);
});
}
const replaceObjectsInOriginalArrayWithOtherArrayObjects = (initialArray, replacementValueArray, key) => {
return initialArray.map((obj) => replacementValueArray.find((o) => o[key] === obj[key]) || obj);
};
const sleep = (ms) => {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};
// Alternative
// chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
// chrome.scripting.executeScript({
// target: {tabId: tabs[0].id},
// files: ['myscript.js'],
// });
// }
// // Raise an alert via Desktop notification
// // @feature - can add text with changes what happened to the ticket
// function raiseAlert(taskId: string | number) {
// // Source for notification standard: https://notifications.spec.whatwg.org/#using-events
// console.log("raiseAlert was run...");
// // // Let's check if the browser supports notifications
// // if (!("Notification" in window)) {
// // alert("This browser does not support desktop notifications.");
// // }
// // // Let's check whether notification permissions have already been granted
// // else if (Notification.permission === "granted") {
// // // If it's okay let's create a notification
// // var notification = new Notification(`
// // Task ID: ${taskId} has triggered an alert.
// // `);
// // window
// // .open(`https://redmine.tribepayments.com/issues/${taskId}`, "_blank")
// // ?.focus();
// // }
// // let statusName = "Status";
// // let triggerStatus = "New";
// // let currentStatus = "In progress";
// // setTimeout(() => {
// // alert(
// // `🌠 Redmine task notification: ${statusName} has just changed from ${triggerStatus} to ${currentStatus}!`
// // );
// // }, 3000);
// // Otherwise, we need to ask the user for permission
// // else if (Notification.permission !== "denied") {
// // Notification.requestPermission().then(function (permission) {
// // // If the user accepts, let's create a notification
// // if (permission === "granted") {
// // // var notification = new Notification("Hi there!");
// // }
// // });
// // }
// // At last, if the user has denied notifications, and you
// // want to be respectful there is no need to bother them any more.
// // // Plan B:
// // // Open Window on pop-up:
// // window.open(`https://redmine.tribepayments.com/issues/${taskId}`, '_blank')?.focus();
// // // Do not allow to close window without confirming:
// // window.addEventListener('beforeunload', function (e) {
// // // Cancel the event
// // e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
// // // Chrome requires returnValue to be set
// // e.returnValue = '';
// // });
// }