Your approach is somewhat correct. However, with the kind of activity you want to track I would change the type TAB_USAGE to:
export type FocusPeriod = {
startTime: string,
endTime: string
}
export type TAB_USAGE = {
tabId: number;
index?: number;
active?: boolean; //we're using active instead of highlighted
url?: string; //notice url is now optional
startTime: string;
endTime?: string;
focus?: Array<FocusPeriod>; //storing multiple focus events of a tab
};
since one would be switching between tabs a lot and there would be multiple focus periods for a specific tab. Also note that url is now optional as we'll be listening to .onActivated event where url might not be available in the callback.
Now, in order to track the switching between tabs, I'd use .onActivated event instead of .onHighlighted event. Please mind the difference between an active tab and a highlighted tab.
An active tab is always highlighted. There is only one active tab per window. You can have several highlighted tabs in a same window. But then all highlighted tabs are not active. Example: select a tab, hold your Shift key, and select another tab. The last tab clicked will become the active tab for this window, but all tabs between them (including the two limit ones) are now highlighted.
A listener on .onActivated event can be added like this:
chrome.tabs.onActivated.addListener(
(activeInfo: object) => {
const previousActiveTabIndex = currentPages.findIndex((page) => page.active);
if(previousActiveTabIndex !== -1) {
currentPages[previousActiveTabIndex].active = false;
const focusLength = currentPages[previousActiveTabIndex].focus.length;
currentPages[previousActiveTabIndex].focus[focusLength - 1].endTime = new Date().toISOString();
}
const newActiveTabIndex = currentPages.findIndex((page) => page.tabId === activeInfo.tabId);
if(newActiveTabIndex > 0) {
currentPages[newActiveTabIndex].active = true;
currentPages[newActiveTabIndex].focus.push({
startTime: new Date().toISOString(),
endTime: ''
});
}
else {
currentPages = [
..currentPages,
{
tabId: activeInfo.tabId;
active: true;
startTime: new Date().toISOString();
focus: [{ startTime: new Date().toISOString(), endTime: '' }];
}
]
}
}
)
If you're only interested in tracking the tabs that the user actually visits the above code should work just fine for you. Though there are cases where we do Cmd+Click or Ctrl+Click to open a url in a new tab which will not make the newly created tab active. And what if the user never selects that tab, you won't be able to track it even when the user closes that tab due to this condition: if (tabExists) in your .onRemoved event handler function. For those cases, we should add the .onCreated event handler.
chrome.tabs.onCreated.addListener(
(tab: Tab) => {
const tabIndex = currentPages.findIndex((page) => page.tabId === tab.id);
if(tabIndex === -1) {
currentPages = [
..currentPages,
{
tabId: tab.id;
startTime: new Date().toISOString();
focus: [];
}
]
}
}
)
So far so good. We've added the above mentioned event handlers. But at the time the .onActivated or .onCreated events are fired, it's not guaranteed that there'd be a url. So, when the url of the tabs are updated, the .onUpdated event is fired. That's why we need to change the code a little bit. We add an entry to the visitedPages only if the tabExisting.url exists (not null, undefined or '');
Lastly, in order to get all the tabs (an initialisation) from all the windows when the extension is installed, we should add another event handler like this:
chrome.runtime.onInstalled.addListener((details) => {
if(['install', 'chrome_update'].includes(details.reason)) {
chrome.windows.getAll((windows: Window[]) => {
windows.forEach(window => {
chrome.tabs.getAllInWindow(window.id, (tabs: Tab[]) => {
tabs.forEach(tab => {
currentPages = [
..currentPages,
{
tabId: tab.id;
startTime: new Date().toISOString();
focus: [];
url: tab.url;
}
]
});
});
chrome.tabs.query({active: true, windowId: window.id}, (tab: Tab) => {
const tabIndex = currentPages.findIndex((page) => page.tabId === tab.id);
if(tabIndex > 0) {
currentPages[tabIndex - 1].active = true;
}
});
});
});
}
else if(details.reason == 'update') {
//do whatever necessary
}
});
There can be many correct approaches to achieve this. This is how I think I'd do it. Please comment if anything is not clear.