diff --git a/package.json b/package.json index f72cb5f..036806e 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/packages/Activity-Tracker-browser-extension/images/icon128.png b/packages/Activity-Tracker-browser-extension/images/icon128.png new file mode 100644 index 0000000..8ba8a61 Binary files /dev/null and b/packages/Activity-Tracker-browser-extension/images/icon128.png differ diff --git a/packages/Activity-Tracker-browser-extension/images/icon16.png b/packages/Activity-Tracker-browser-extension/images/icon16.png new file mode 100644 index 0000000..4994be1 Binary files /dev/null and b/packages/Activity-Tracker-browser-extension/images/icon16.png differ diff --git a/packages/Activity-Tracker-browser-extension/images/icon32.png b/packages/Activity-Tracker-browser-extension/images/icon32.png new file mode 100644 index 0000000..b919666 Binary files /dev/null and b/packages/Activity-Tracker-browser-extension/images/icon32.png differ diff --git a/packages/Activity-Tracker-browser-extension/images/icon48.png b/packages/Activity-Tracker-browser-extension/images/icon48.png new file mode 100644 index 0000000..05d7b49 Binary files /dev/null and b/packages/Activity-Tracker-browser-extension/images/icon48.png differ diff --git a/packages/Activity-Tracker-browser-extension/manifest.json b/packages/Activity-Tracker-browser-extension/manifest.json new file mode 100644 index 0000000..c102c01 --- /dev/null +++ b/packages/Activity-Tracker-browser-extension/manifest.json @@ -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"] +} diff --git a/packages/Activity-Tracker-browser-extension/popup.html b/packages/Activity-Tracker-browser-extension/popup.html new file mode 100644 index 0000000..2086671 --- /dev/null +++ b/packages/Activity-Tracker-browser-extension/popup.html @@ -0,0 +1,19 @@ + + + + + + + + Document + + + +

Activity

+
+ Title: Loading ... +
+ URL: Loading ... +
Favicon
+ + diff --git a/packages/Activity-Tracker-browser-extension/src/background.js b/packages/Activity-Tracker-browser-extension/src/background.js new file mode 100644 index 0000000..3ddd104 --- /dev/null +++ b/packages/Activity-Tracker-browser-extension/src/background.js @@ -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(); diff --git a/packages/Activity-Tracker-browser-extension/style/popup.css b/packages/Activity-Tracker-browser-extension/style/popup.css new file mode 100644 index 0000000..228faca --- /dev/null +++ b/packages/Activity-Tracker-browser-extension/style/popup.css @@ -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; +} diff --git a/packages/server/app.js b/packages/server/app.js index 70a0782..feeda99 100644 --- a/packages/server/app.js +++ b/packages/server/app.js @@ -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(); @@ -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}/`); diff --git a/packages/server/controllers/index.js b/packages/server/controllers/index.js index c7f8531..20f7b71 100644 --- a/packages/server/controllers/index.js +++ b/packages/server/controllers/index.js @@ -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 @@ -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; diff --git a/packages/server/controllers/json/browseractivity.controller.js b/packages/server/controllers/json/browseractivity.controller.js new file mode 100644 index 0000000..bd5e974 --- /dev/null +++ b/packages/server/controllers/json/browseractivity.controller.js @@ -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); +}; diff --git a/packages/server/controllers/json/utils.js b/packages/server/controllers/json/utils.js index 2c9dab5..d716156 100644 --- a/packages/server/controllers/json/utils.js +++ b/packages/server/controllers/json/utils.js @@ -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]])); diff --git a/packages/server/controllers/mongo/browserTracker.controller.js b/packages/server/controllers/mongo/browserTracker.controller.js new file mode 100644 index 0000000..0d5f38c --- /dev/null +++ b/packages/server/controllers/mongo/browserTracker.controller.js @@ -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); + }, + ); +}; diff --git a/packages/server/models/browserTracker.model.js b/packages/server/models/browserTracker.model.js new file mode 100644 index 0000000..00017af --- /dev/null +++ b/packages/server/models/browserTracker.model.js @@ -0,0 +1,13 @@ +import mongoose from 'mongoose'; +const Schema = mongoose.Schema; + +// Schema +const browserActivitySchema = new Schema({ + title: { type: String, required: true }, + url: { type: String, required: true }, + favicon: { type: String, required: true }, + startTime: { type: Date, required: true }, + endTime: { type: Date, required: true }, +}); + +export default mongoose.model('browserActivity', browserActivitySchema); diff --git a/packages/server/routes/browseractivity.routes.js b/packages/server/routes/browseractivity.routes.js new file mode 100644 index 0000000..d7e9867 --- /dev/null +++ b/packages/server/routes/browseractivity.routes.js @@ -0,0 +1,10 @@ +import express from 'express'; +import auth from '../middlewares/auth.js'; +import { browser_activity_controller } from '../controllers/index.js'; + +const router = express.Router(); + +router.get('/', auth, browser_activity_controller.all_activities); +router.post('/', auth, browser_activity_controller.activity_create); + +export default router; diff --git a/packages/server/routes/root.routes.js b/packages/server/routes/root.routes.js index 3e1b73f..2bb2082 100644 --- a/packages/server/routes/root.routes.js +++ b/packages/server/routes/root.routes.js @@ -8,6 +8,7 @@ router.get('/', (req, res) => { const routes = { apps: `http://localhost:${port}/api/apps`, activities: `http://localhost:${port}/api/activities`, + browseractivities: `http://localhost:${port}/api/browseractivities`, }; if (!useLocal) routes.users = `http://localhost:${port}/api/users`; res.json(routes);