Merge pull request #16 from mabasic/heartbeat

Heartbeats
This commit is contained in:
Mario Bašić
2015-06-04 13:08:37 +02:00
14 changed files with 10347 additions and 285 deletions

12
assets/js/UrlHelper.js Normal file
View File

@@ -0,0 +1,12 @@
class UrlHelper {
static getDomainFromUrl(url)
{
var parts = url.split('/');
return parts[0] + "//" + parts[2];
}
}
export default UrlHelper;

View File

@@ -1,6 +1,191 @@
/** var UrlHelper = require('./UrlHelper');
* I think that I am going to need this.
*/ var $ = require('jquery');
var currentTimestamp = require('./helpers/currentTimestamp');
var changeExtensionIcon = require('./helpers/changeExtensionIcon');
class WakaTime { class WakaTime {
detectionIntervalInSeconds = 60; //default
loggingType = 'domain'; //default
heartbeatApiUrl = 'https://wakatime.com/api/v1/users/current/heartbeats';
currentUserApiUrl = 'https://wakatime.com/api/v1/users/current';
/**
* Checks if the user is logged in.
*
* @return $.promise()
*/
checkAuth()
{
var deferredObject = $.Deferred();
$.ajax({
url: this.currentUserApiUrl,
dataType: 'json',
success: (data) => {
deferredObject.resolve(data.data);
},
error: (xhr, status, err) => {
console.error(this.currentUserApiUrl, status, err.toString());
deferredObject.resolve(false);
}
});
return deferredObject.promise();
}
/**
* Depending on various factors detects the current active tab URL or domain,
* and sends it to WakaTime for logging.
*
* @return null
*/
recordHeartbeat()
{
this.checkAuth().done(data => {
if(data !== false){
// User is logged in.
// Change extension icon to default color.
changeExtensionIcon();
chrome.idle.queryState(this.detectionIntervalInSeconds, (newState) => {
if(newState === 'active')
{
// Get current tab URL.
chrome.tabs.query({active: true}, (tabs) => {
this.sendHeartbeat(tabs[0].url);
});
}
});
}
else {
// User is not logged in.
// Change extension icon to red color.
changeExtensionIcon('red');
}
});
}
/**
* Creates payload for the heartbeat and returns it as JSON.
*
* @param string entity
* @param string type 'domain' or 'url'
* @param boolean debug = false
* @return JSON
*/
_preparePayload(entity, type, debug = false)
{
return JSON.stringify({
entity: entity,
type: type,
time: currentTimestamp(),
is_debugging: debug
});
}
/**
* Returns a promise with logging type variable.
*
* @return $.promise
*/
_getLoggingType()
{
var deferredObject = $.Deferred();
chrome.storage.sync.get({
loggingType: this.loggingType
}, function(items) {
deferredObject.resolve(items.loggingType);
});
return deferredObject.promise();
}
/**
* Given the entity and logging type it creates a payload and
* sends an ajax post request to the API.
*
* @param string entity
* @return null
*/
sendHeartbeat(entity)
{
this._getLoggingType().done((loggingType) => {
// Get only the domain from the entity.
// And send that in heartbeat
if(loggingType == 'domain') {
var domain = UrlHelper.getDomainFromUrl(entity);
var payload = this._preparePayload(domain, 'domain');
console.log(payload);
this.sendAjaxRequestToApi(payload);
}
// Send entity in heartbeat
else if (loggingType == 'url') {
var payload = this._preparePayload(entity, 'url');
console.log(payload);
this.sendAjaxRequestToApi(payload);
}
});
}
/**
* Sends AJAX request with payload to the heartbeat API as JSON.
*
* @param JSON payload
* @param string method = 'POST'
* @return $.promise
*/
sendAjaxRequestToApi(payload, method = 'POST') {
var deferredObject = $.Deferred();
$.ajax({
url: this.heartbeatApiUrl,
dataType: 'json',
contentType: 'application/json',
method: method,
data: payload,
success: (response) => {
deferredObject.resolve(this);
},
error: (xhr, status, err) => {
console.error(this.heartbeatApiUrl, status, err.toString());
deferredObject.resolve(this);
}
});
return deferredObject.promise();
}
} }
export default WakaTime;

View File

@@ -6,10 +6,10 @@ var MainList = require('./MainList.react');
var changeExtensionIcon = require('../helpers/changeExtensionIcon'); var changeExtensionIcon = require('../helpers/changeExtensionIcon');
var WakaTimeOriginal = require('../WakaTime');
class WakaTime extends React.Component class WakaTime extends React.Component
{ {
currentUserApiUrl = 'https://wakatime.com/api/v1/users/current';
logoutUserUrl = 'https://wakatime.com/logout'; logoutUserUrl = 'https://wakatime.com/logout';
state = { state = {
@@ -34,7 +34,9 @@ class WakaTime extends React.Component
} }
}); });
this.checkAuth().done(data => { var wakatime = new WakaTimeOriginal;
wakatime.checkAuth().done(data => {
if(data !== false){ if(data !== false){
@@ -61,29 +63,6 @@ class WakaTime extends React.Component
} }
checkAuth()
{
var deferredObject = $.Deferred();
$.ajax({
url: this.currentUserApiUrl,
dataType: 'json',
success: (data) => {
deferredObject.resolve(data.data);
},
error: (xhr, status, err) => {
console.error(this.currentUserApiUrl, status, err.toString());
deferredObject.resolve(false);
}
});
return deferredObject.promise();
}
logoutUser() logoutUser()
{ {
var deferredObject = $.Deferred(); var deferredObject = $.Deferred();

View File

@@ -0,0 +1,67 @@
var WakaTime = require('./WakaTime');
/**
* Whenever an alarms sets off, this function
* gets called to detect the alarm name and
* do appropriate action.
*
* @param alarm
*/
function resolveAlarm(alarm) {
// |alarm| can be undefined because onAlarm also gets called from
// window.setTimeout on old chrome versions.
if (alarm && alarm.name == 'heartbeatAlarm') {
console.log('recording a heartbeat - alarm triggered');
var wakatime = new WakaTime;
wakatime.recordHeartbeat();
}
}
// Add a listener to resolve alarms
chrome.alarms.onAlarm.addListener(resolveAlarm);
// Create a new alarm for heartbeats.
chrome.alarms.create('heartbeatAlarm', {periodInMinutes: 2});
/**
* Whenever a active tab is changed it records a heartbeat with that tab url.
*/
chrome.tabs.onActivated.addListener(function(activeInfo) {
chrome.tabs.get(activeInfo.tabId, function(tab) {
console.log('recording a heartbeat - active tab changed');
var wakatime = new WakaTime;
wakatime.recordHeartbeat();
});
});
/**
* Whenever any tab is updated it checks if the updated tab is the tab that is
* currently active and if it is, then it records a heartbeat.
*/
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if(changeInfo.status === 'complete')
{
// Get current tab URL.
chrome.tabs.query({active: true}, (tabs) => {
// If tab updated is the same as active tab
if(tabId == tabs[0].id)
{
console.log('recording a heartbeat - tab updated');
var wakatime = new WakaTime;
wakatime.recordHeartbeat();
}
});
}
});

View File

@@ -7,28 +7,13 @@
*/ */
export default function changeExtensionIcon(color = '') { export default function changeExtensionIcon(color = '') {
var canvas = document.getElementById('icon');
var context = canvas.getContext('2d');
var x = 0;
var y = 0;
var width = 19;
var height = 19;
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(imageObj, x, y, width, height);
var imageData = context.getImageData(x, y, width, height);
chrome.browserAction.setIcon({
imageData: imageData
});
};
if(color !== ''){ if(color !== ''){
color = '-' + color; color = '-' + color;
} }
imageObj.src = 'graphics/wakatime-logo-48' + color + '.png'; var path = './graphics/wakatime-logo-48' + color + '.png';
chrome.browserAction.setIcon({
path: path
});
} }

View File

@@ -0,0 +1,8 @@
/**
* Returns UNIX timestamp
*
* @return integer
*/
export default function(){
return Math.round((new Date()).getTime() / 1000);
}

View File

@@ -4,16 +4,31 @@ require('bootstrap');
var $ = require('jquery'); var $ = require('jquery');
function detectCheckedRadio(name)
{
for(var i = 0; i < document.getElementsByName(name).length; i++)
{
var button = document.getElementsByName(name)[i];
if(button.checked === true)
{
return button.value;
}
}
}
// Saves options to chrome.storage.sync. // Saves options to chrome.storage.sync.
function save_options(e) { function save_options(e) {
e.preventDefault(); e.preventDefault();
var theme = document.getElementById('theme').value; var theme = document.getElementById('theme').value;
var blacklist = document.getElementById('blacklist').value; var blacklist = document.getElementById('blacklist').value;
var loggingType = detectCheckedRadio('loggingType');
chrome.storage.sync.set({ chrome.storage.sync.set({
theme: theme, theme: theme,
blacklist: blacklist blacklist: blacklist,
loggingType: loggingType
}, function() { }, function() {
// Update status to let user know options were saved. // Update status to let user know options were saved.
var status = $('#status'); var status = $('#status');
@@ -36,10 +51,12 @@ function restore_options() {
// Use default value color = 'red' and likesColor = true. // Use default value color = 'red' and likesColor = true.
chrome.storage.sync.get({ chrome.storage.sync.get({
theme: 'light', theme: 'light',
blacklist: '' blacklist: '',
loggingType: 'domain'
}, function(items) { }, function(items) {
document.getElementById('theme').value = items.theme; document.getElementById('theme').value = items.theme;
document.getElementById('blacklist').value = items.blacklist; document.getElementById('blacklist').value = items.blacklist;
document.getElementById(items.loggingType + 'Type').checked = true;
}); });
} }

View File

@@ -19,7 +19,7 @@ elixir(function (mix) {
mix.copy('vendor/bower_components/font-awesome/less', 'assets/less/font-awesome'); mix.copy('vendor/bower_components/font-awesome/less', 'assets/less/font-awesome');
mix.copy('vendor/bower_components/font-awesome/fonts', 'public/fonts'); mix.copy('vendor/bower_components/font-awesome/fonts', 'public/fonts');
mix.less('app.less'); mix.less('app.less');
mix.browserify('app.js', null, 'assets/js'); //mix.browserify('app.js', null, 'assets/js');
//mix.browserify('events.js', 'public/js/events.js', 'assets/js'); mix.browserify('events.js', 'public/js/events.js', 'assets/js');
//mix.browserify('options.js', 'public/js/options.js', 'assets/js'); //mix.browserify('options.js', 'public/js/options.js', 'assets/js');
}); });

View File

@@ -11,9 +11,11 @@
"permissions": [ "permissions": [
"https://wakatime.com/api/v1/users/current", "https://wakatime.com/api/v1/users/current",
"https://wakatime.com/logout", "https://wakatime.com/logout",
"https://wakatime.com/api/v1/users/current/heartbeats",
"alarms", "alarms",
"tabs", "tabs",
"storage" "storage",
"idle"
], ],
"background": { "background": {
"scripts": [ "scripts": [

View File

@@ -36,6 +36,25 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group">
<label class="col-lg-2 control-label">Logging type</label>
<div class="col-lg-10">
<div class="radio">
<label>
<input type="radio" name="loggingType" id="domainType" value="domain" checked="">
Only the domain
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="loggingType" id="urlType" value="url">
Entire URL
</label>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div class="col-lg-10 col-lg-offset-2"> <div class="col-lg-10 col-lg-offset-2">
<button id="save" type="submit" class="btn btn-primary">Save</button> <button id="save" type="submit" class="btn btn-primary">Save</button>

View File

@@ -10,8 +10,6 @@
</head> </head>
<body> <body>
<canvas id="icon"></canvas>
<div id="wakatime"></div> <div id="wakatime"></div>
<script src="public/js/bundle.js"></script> <script src="public/js/bundle.js"></script>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,16 +8,28 @@ require('bootstrap');
var $ = require('jquery'); var $ = require('jquery');
function detectCheckedRadio(name) {
for (var i = 0; i < document.getElementsByName(name).length; i++) {
var button = document.getElementsByName(name)[i];
if (button.checked === true) {
return button.value;
}
}
}
// Saves options to chrome.storage.sync. // Saves options to chrome.storage.sync.
function save_options(e) { function save_options(e) {
e.preventDefault(); e.preventDefault();
var theme = document.getElementById('theme').value; var theme = document.getElementById('theme').value;
var blacklist = document.getElementById('blacklist').value; var blacklist = document.getElementById('blacklist').value;
var loggingType = detectCheckedRadio('loggingType');
chrome.storage.sync.set({ chrome.storage.sync.set({
theme: theme, theme: theme,
blacklist: blacklist blacklist: blacklist,
loggingType: loggingType
}, function () { }, function () {
// Update status to let user know options were saved. // Update status to let user know options were saved.
var status = $('#status'); var status = $('#status');
@@ -39,10 +51,12 @@ function restore_options() {
// Use default value color = 'red' and likesColor = true. // Use default value color = 'red' and likesColor = true.
chrome.storage.sync.get({ chrome.storage.sync.get({
theme: 'light', theme: 'light',
blacklist: '' blacklist: '',
loggingType: 'domain'
}, function (items) { }, function (items) {
document.getElementById('theme').value = items.theme; document.getElementById('theme').value = items.theme;
document.getElementById('blacklist').value = items.blacklist; document.getElementById('blacklist').value = items.blacklist;
document.getElementById(items.loggingType + 'Type').checked = true;
}); });
} }