Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(browser): Browser activity tracker add #65

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
14 changes: 14 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,19 @@
"lint-staged": {
"*.{js,jsx}": "eslint --cache --fix",
"*.{js,css,md}": "prettier --write"
},
"pkg": {
"assets": [
"./node_modules/**/*.node",
"./packages/**/.babel/*.env",
"./packages/server/.babel/static/",
"./packages/desktop/node_modules/active-win/main"
],
"targets": [
"node14-win-x64",
"node14-linux-x64",
"node14-macos"
],
"outputPath": "build"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions packages/Activity-Tracker-browser-extension/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "Browser Activity Tracker",
"description": "Shows you your digital time based on your browser activity",
"version": "1.0.0",
"manifest_version": 3,

"action": {
"default_icon": {
"16": "images/icon16.png",
"32": "images/icon32.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
},
"default_popup": "popup.html"
},

"background": {
"service_worker": "src/background.js",
"type": "module"
},

"author": "",
"homepage_url": "https://github.com/OpenLake/Activity-Tracker#readme",

"permissions": ["tabs", "activeTab", "storage"]
}
19 changes: 19 additions & 0 deletions packages/Activity-Tracker-browser-extension/popup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style/popup.css" />
<title>Document</title>
</head>
<body>
<script src="src/background.js"></script>
<h1 class="textBody">Activity</h1>
<div>
<a class="textBody" id="activityTitle">Title: Loading ...</a>
</div>
<a class="textBody" href="" id="activityUrl">URL: Loading ...</a>
<div><img id="activityFavicon" src="" alt="Favicon" /></div>
</body>
</html>
138 changes: 138 additions & 0 deletions packages/Activity-Tracker-browser-extension/src/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
let title = null;
let url = null;
let favicon = null;
class ActiveBrowserWatcher {
/**
* @param {number} interval Polling interval
* @param {(activity) => void} changeCallback
*/
constructor(interval = 1000, changeCallback) {
this.startTime = null;
this.title = null; //Title
this.url = null;
this.favicon = null;
this.changeCallback = changeCallback;
this.interval = interval;
}

/**
* Storing the start time of the active window
* Collecting data of the window which will be active
*/
storeTime() {
const endTime = Date.now();
const startTime = this.startTime;

const title = this.title;
const url = this.url;
const favicon = this.favicon;

const data = {
title,
url,
favicon,
startTime,
endTime,
};

fetch('http://localhost:32768/api/browseractivities', {
method: 'POST',
body: data,
})
.then(console.log('done store'))
.catch(err => {
console.log(err);
});
this.changeCallback(data);
console.log(data);
}

/**
* Checks the active window is specific time interval
* and whenever the active window changes stores the time difference by calling {@link ActiveWindowWatcher.storeTime} function
*/
tracker() {
setInterval(() => {
let queryOptions = { active: true, currentWindow: true }; // to get current active tab from the current window
// eslint-disable-next-line no-undef
chrome.tabs.query(queryOptions, function currentTab(tabs) {
let currentTab = tabs[0]; // take the object from the returned promise
let currentTitle = currentTab.title; // take object title
let currentUrl = currentTab.url; // take object URL
let currentFavIcons = currentTab.favIconUrl;
title = currentTitle;
url = currentUrl;
favicon = currentFavIcons;

// Title
const activityTitle = document.getElementById('activityTitle');
const activityTitleUrl = document.getElementById('activityTitle');
activityTitle.innerHTML = 'Title: ' + currentTitle; //format it in html
activityTitleUrl.setAttribute('href', currentUrl);
console.log(activityTitle);

// URl
const activityUrl = document.getElementById('activityUrl');
const activityLink = document.getElementById('activityUrl');
activityUrl.innerHTML = 'URL: ' + currentUrl; //format it in html
activityLink.setAttribute('href', currentUrl);

// Favicon
const activityFavicon = document.getElementById('activityFavicon');
activityFavicon.setAttribute('src', currentFavIcons); //format Favicon in html
});

if (title === undefined) {
this.title = null;
this.url = null;
this.favicon = null;
return;
}

if (!this.title) {
this.startTime = Date.now();
this.title = title;
this.url = url;
this.favicon = favicon;
}

//If the active window is changed store the used time data.
if (title !== this.title) {
this.storeTime();
this.title = null;
this.url = null;
this.favicon = null;
}
console.log(title, url, favicon, this.startTime);
}, this.interval);
}

initialize() {
this.tracker();
}
}

// const activityTracker = new ActiveBrowserWatcher(1000);

// const activityTracker = new ActiveBrowserWatcher(1000, activity => {
// saveActivities(activity);
// });

// fetch('http://localhost:32768/api/browseractivities',{
// method: 'POST',
// body: activityTracker
// )
// });

const activityTracker = new ActiveBrowserWatcher(1000, activity => {
fetch('http://localhost:32768/api/browseractivities', {
method: 'POST',
body: activity,
})
.then(console.log('done'))
.catch(err => {
console.log(err);
});
});

activityTracker.initialize();
20 changes: 20 additions & 0 deletions packages/Activity-Tracker-browser-extension/style/popup.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
body {
width: 300px;
height: 300px;
text-align: center;
background-color: rgb(18, 31, 48);
}

.textBody {
color: aliceblue;
}

#activityFavicon {
margin-top: 50px;
margin-bottom: 50px;
}

img {
width: 20%;
height: auto;
}
2 changes: 2 additions & 0 deletions packages/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import root from './routes/root.routes.js';
import user from './routes/user.routes.js';
import activity from './routes/activity.routes.js';
import app_usage from './routes/app.routes.js';
import browser_activity from './routes/browseractivity.routes.js';

const app = express();

Expand Down Expand Up @@ -41,6 +42,7 @@ app.use('/api/', root);
app.use('/api/users', user);
app.use('/api/activities', activity);
app.use('/api/apps', app_usage);
app.use('/api/browseractivities', browser_activity);

app.listen(port, hostname, function () {
console.log(`Nodejs server running at http://${hostname}:${port}/`);
Expand Down
6 changes: 6 additions & 0 deletions packages/server/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { useLocal } from '../config.js';

import * as json_activity_controller from './json/activity.controller.js';
import * as json_app_controller from './json/app.controller.js';
import * as json_browser_activity_controller from './json/browseractivity.controller.js';

import * as mongo_activity_controller from './mongo/activity.controller.js';
import * as mongo_app_controller from './mongo/app.controller.js';
import * as mongo_user_controller from './mongo/user.controller.js';
import * as mongo_browser_activity_controller from './mongo/browserTracker.controller.js';

export const activity_controller = useLocal
? json_activity_controller
Expand All @@ -16,3 +18,7 @@ export const app_controller = useLocal
: mongo_app_controller;

export const user_controller = mongo_user_controller;

export const browser_activity_controller = useLocal
? json_browser_activity_controller
: mongo_browser_activity_controller;
87 changes: 87 additions & 0 deletions packages/server/controllers/json/browseractivity.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import fs from 'fs';
import path from 'path';
import envPaths from 'env-paths';
import { extract } from './utils.js';

const dirname = envPaths('ActivityTracker').data;
const browserActivityDir = path.join(dirname, '/browser-activity');
fs.mkdirSync(browserActivityDir, { recursive: true });

/**
* Get date string of format YYYY-MM-DD
* @param {Date} date
*/
const getISODateString = date => date.toISOString().slice(0, 10);

/**
* @param {Date} date
*/
function getFilePath(date) {
const filename = `${getISODateString(date)}.json`;
return path.join(browserActivityDir, filename);
}

/**
* Get Activities for a given date
* @param {Date} date
* @returns {Object[]}
*/
function getActivities(date) {
let data = [];
try {
data = JSON.parse(fs.readFileSync(getFilePath(date), 'utf-8'));
} catch (error) {
if (error.code === 'ENOENT') {
data = [];
} else {
throw error;
}
}
return data;
}

const getDataFromJson = (start, end) => {
start = new Date(getISODateString(new Date(start)));
end = new Date(getISODateString(new Date(end)));

let result = [];
let date = start;

while (date <= end) {
try {
result.push(...JSON.parse(fs.readFileSync(getFilePath(date), 'utf-8')));
} catch (error) {
console.log(`No data for ${getISODateString(date)}`);
}
date.setDate(date.getDate() + 1);
}
return result;
};

export const activity_create = (req, res) => {
const packet = extract(req.body, [
'title',
'url',
'favicon',
'startTime',
'endTime',
]);
const filepath = getFilePath(new Date(packet.endTime));
const data = getActivities(new Date(packet.endTime));
data.push(packet);
fs.writeFileSync(filepath, JSON.stringify(data, null, 4));

res.send('Activity created successfully');
};

export const all_activities = (req, res) => {
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);

const after = req.query.after ?? yesterday.toISOString();
const before = req.query.before ?? today.toISOString();

const activities = getDataFromJson(after, before);
res.json(activities);
};
3 changes: 3 additions & 0 deletions packages/server/controllers/json/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ export const groupBy = (array, key) => {
return result;
}, {}); // empty object is the initial value for result object
};

export const extract = (obj, keys) =>
Object.fromEntries(keys.map(k => [k, obj[k]]));
36 changes: 36 additions & 0 deletions packages/server/controllers/mongo/browserTracker.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import browserActivity from '../../models/browserTracker.model.js';

export const activity_create = (req, res, next) => {
const { title, url, favicon, startTime, endTime } = req.body;
const browseractivity = new browserActivity({
title,
url,
favicon,
startTime,
endTime,
});
browseractivity.save(err => {
if (err) return next(err);
});

res.send('Activity created succecssfully');
};

export const all_activities = (req, res, next) => {
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);

const after = req.query.after ?? yesterday.toISOString();
const before = req.query.before ?? today.toISOString();

browserActivity.find(
{
startTime: { $gte: after, $lt: before },
},
(err, activity) => {
if (err) return next(err);
res.json(activity);
},
);
};
Loading