-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTimeTracker.js
235 lines (214 loc) · 6.8 KB
/
TimeTracker.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
/**
*
* The TimeTracker class listens for chrome events. It is
* responsible for tracking the currently active site and updating
* local storage with timings.
*
* @class TimeTracker
*
**/
class TimeTracker {
constructor() {
this._activeSite = null;
this._startTime = null;
this.localStorageInitialState = {
_blacklist: [
"chrome://",
"about:blank",
"chrome-extension://",
"localhost",
"chrome-devtools",
"mailto:",
"file://"
],
_settings: {
timeseriesFilter: "alltime"
}
};
this.handleNewSite = this.handleNewSite.bind(this);
this.handleNewWindow = this.handleNewWindow.bind(this);
this.handleNewState = this.handleNewState.bind(this);
/* LISTEN FOR NEW ACTIVE TABS */
chrome.tabs.onActivated.addListener(this.handleNewSite);
/* LISTEN FOR CHANGE OF BASE URL */
chrome.webNavigation.onCommitted.addListener(this.handleNewSite);
/* LISTEN FOR WINDOW FOCUS */
chrome.windows.onFocusChanged.addListener(this.handleNewWindow);
/* LISTEN FOR IDLE STATE */
chrome.idle.onStateChanged.addListener(this.handleNewState);
}
set activeSite(newSite) {
validateNewSite(newSite).then(newSiteIsValid => {
if (newSiteIsValid) {
this._activeSite = newSite;
this._startTime = Date.now();
chrome.browserAction.setBadgeText({
text: " "
});
chrome.browserAction.setBadgeBackgroundColor({ color: "limegreen" });
} else {
this._activeSite = null;
this._startTime = null;
chrome.browserAction.setBadgeText({
text: ""
});
}
});
}
get activeSite() {
return this._activeSite;
}
get startTime() {
return this._startTime;
}
handleNewSite(incomingSite) {
let incomingSiteId = incomingSite.id || incomingSite.tabId;
// Query for more info about the new tab (like URL),
// since our chrome event listeners only give us a tab & window id
chrome.tabs.get(incomingSiteId, newSite => {
// https://stackoverflow.com/questions/28431505/unchecked-runtime-lasterror-when-using-chrome-api
if (chrome.runtime.lastError) {
console.log(chrome.runtime.lastError.message);
} else {
// If there is an active site already and that active site url is different
// from the new tab's url, set the end time for the previous site
// and save the timing to local storage
if (
this.activeSite &&
getBaseUrl(newSite.url) !== getBaseUrl(this.activeSite.url)
) {
saveToLocalStorage(this.activeSite, this.startTime);
this.activeSite = newSite;
} else if (!this.activeSite) {
this.activeSite = newSite;
}
}
});
}
handleNewWindow(newWindowId) {
// If the chrome window looses focus, stop tracking time and save the current timing.
// newWindowId is an integer: a window has lost focus if it returns -1.
if (this.activeSite && newWindowId < 0) {
saveToLocalStorage(this.activeSite, this.startTime);
this.activeSite = null;
} else if (newWindowId > 0) {
// If a different window is focused, query for the currently selected
// tab in that new window and call the new site handler
chrome.tabs.query({ active: true, currentWindow: true }, newTab => {
if (newTab.length === 1) {
this.handleNewSite(newTab[0]);
}
});
}
}
handleNewState(newState) {
if (newState !== "active" && this.activeSite) {
saveToLocalStorage(this.activeSite, this.startTime);
this.activeSite = null;
} else if (newState === "active") {
chrome.tabs.query({ active: true, currentWindow: true }, newTab => {
if (newTab.length === 1) {
this.handleNewSite(newTab[0]);
}
});
}
}
}
new TimeTracker();
/**
* Fetches chrome.storage
* @returns Promise
**/
function fetchStorage() {
return new Promise(function(resolve, reject) {
chrome.storage.local.get("timetracker", function(result) {
if (!Object.keys(result).length) {
chrome.storage.local.set({
timetracker: {
_blacklist: [
"chrome://",
"about:blank",
"chrome-extension://",
"localhost",
"chrome-devtools",
"mailto:",
"file://"
],
_settings: {
timeseriesFilter: "alltime"
}
}
});
} else {
resolve(result);
}
});
});
}
/**
* Validates a site by first checking it's transition type, which indicates
* whether or not the user initiated the transiton, then check the site
* against the user's blacklist.
* @param {object} newSite
* @returns {boolean}
*/
async function validateNewSite(newSite) {
if (!newSite) return false;
if (newSite.transitionType) {
let isTransitionValid =
newSite.transitionType === "link" ||
newSite.transitionType === "typed" ||
newSite.transitionType === "auto_bookmark" ||
newSite.transitionType === "generated" ||
newSite.transitionType === "start_page" ||
newSite.transitionType === "reload";
// If the transition isn't valid, don't bother with the rest of the
// validation and return
if (!isTransitionValid) return false;
}
const {
timetracker: { _blacklist }
} = await fetchStorage();
// Get blacklist from localstorage
let isSiteValid = _blacklist.every(site => !newSite.url.includes(site));
return isSiteValid;
}
/**
* Takes a site and a start time, calculates the new timing, and
* updates that site's local storage entry.
* @param {object} activeSite
* @param {number} startTime
*/
async function saveToLocalStorage(site, startTime) {
const url = getBaseUrl(site.url);
const endTime = Date.now();
const newTiming = endTime - startTime;
const { timetracker = {} } = await fetchStorage();
const localStorageVal = timetracker[url] || null;
if (localStorageVal) {
const previousTimings = localStorageVal[0];
const previousTimestamps = localStorageVal[1];
timetracker[url] = [
[newTiming, ...previousTimings],
[endTime, ...previousTimestamps]
];
} else {
timetracker[url] = [[newTiming], [endTime]];
}
chrome.storage.local.set({ timetracker }, function() {
console.log("Value is set to", url, timetracker[url]);
});
}
/**
* A utility function that takes a url and creates a temporary element
* in the DOM for the purpose of extracting a clean base url using the
* `origin` property. It also strips out `www` using regex.
* @param {string} url
* @returns {string}
*/
function getBaseUrl(url) {
let temp = document.createElement("a");
temp.href = url;
let baseUrl = temp.origin.replace(/www.(?!^https?:\/\/)/, ""); // https://regexr.com/3ufss
return baseUrl;
}