1
0
mirror of https://github.com/beestat/app.git synced 2026-03-24 02:18:05 -04:00
beestat/js/beestat/api.js
2019-05-22 21:22:24 -04:00

333 lines
9.5 KiB
JavaScript

beestat.api2 = function() {
this.api_calls_ = [];
};
/**
* Stores cached responses statically across all API calls.
*
* @type {Object}
*/
beestat.api2.cache = {};
// if (window.localStorage.getItem('api_cache') !== null) {
// beestat.api2.cache = JSON.parse(window.localStorage.getItem('api_cache'));
// }
/**
* Beestat's local API key.
*
* @type {string}
*/
beestat.api2.api_key = 'ER9Dz8t05qUdui0cvfWi5GiVVyHP6OB8KPuSisP2';
/**
* Send an API call. If the api_call parameter is specified it will send that.
* If not, it will check the cache, then construct and send the appropriate
* API call if necessary.
*
* @param {Object=} opt_api_call The API call object.
*
* @return {beestat.api2} This.
*/
beestat.api2.prototype.send = function(opt_api_call) {
var self = this;
this.xhr_ = new XMLHttpRequest();
// If passing an actual API call, fire it off!
if (opt_api_call !== undefined) {
// Add in the API key
opt_api_call.api_key = beestat.api2.api_key;
// Build the query string
var query_string = Object.keys(opt_api_call)
.map(function(k) {
return encodeURIComponent(k) + '=' + encodeURIComponent(opt_api_call[k]);
})
.join('&');
this.xhr_.addEventListener('load', function() {
self.load_(this.responseText);
});
this.xhr_.open('POST', '../api/?' + query_string);
this.xhr_.send();
} else {
if (this.is_batch_() === true) {
// Only run uncached API calls.
var uncached_batch_api_calls = [];
this.cached_batch_api_calls_ = {};
this.api_calls_.forEach(function(api_call) {
var cached = self.get_cached_(api_call);
if (cached === undefined) {
uncached_batch_api_calls.push(api_call);
} else {
self.cached_batch_api_calls_[api_call.alias] = cached.data;
}
});
if (uncached_batch_api_calls.length === 0) {
// If no API calls left, just fire off the callback with the data.
if (this.callback_ !== undefined) {
this.callback_(this.cached_batch_api_calls_);
}
} else {
// If more than one API call left, fire off a batch API call.
this.send({'batch': JSON.stringify(uncached_batch_api_calls)});
}
} else {
var single_api_call = this.api_calls_[0];
var cached = this.get_cached_(single_api_call);
if (cached !== undefined) {
if (this.callback_ !== undefined) {
this.callback_(cached.data);
}
} else {
this.send(single_api_call);
}
}
}
return this;
};
/**
* Add an API call.
*
* @param {string} resource The resource.
* @param {string} method The method.
* @param {Object=} opt_args Optional arguments.
* @param {string=} opt_alias Optional alias (required for batch API calls).
*
* @return {beestat.api2} This.
*/
beestat.api2.prototype.add_call = function(resource, method, opt_args, opt_alias) {
var api_call = {
'resource': resource,
'method': method,
'arguments': JSON.stringify(beestat.default_value(opt_args, {}))
};
if (opt_alias !== undefined) {
api_call.alias = opt_alias;
}
this.api_calls_.push(api_call);
return this;
};
/**
* Set a callback function to be called once the API call completes.
*
* @param {Function} callback The callback function.
*
* @return {beestat.api2} This.
*/
beestat.api2.prototype.set_callback = function(callback) {
this.callback_ = callback;
return this;
};
/**
* Fires after an XHR request returns.
*
* @param {string} response_text Whatever the XHR request returned.
*/
beestat.api2.prototype.load_ = function(response_text) {
var response;
try {
response = window.JSON.parse(response_text);
} catch (e) {
beestat.error('API returned invalid response.', response_text);
return;
}
// Error handling
if (
response.data &&
(
response.data.error_code === 1004 || // Session is expired.
response.data.error_code === 10001 || // Could not get first token.
response.data.error_code === 10002 || // Could not refresh ecobee token; no token found.
response.data.error_code === 10003 // Could not refresh ecobee token; ecobee returned no token.
)
) {
window.location.href = '/';
return;
} else if (response.data && response.data.error_code === 1209) {
// Could not get lock; safe to ignore as that means sync is running.
} else if (response.success !== true) {
beestat.error(
'API call failed: ' + response.data.error_message,
JSON.stringify(response, null, 2)
);
}
// Cach responses
var cached_until_header = this.xhr_.getResponseHeader('beestat-cached-until');
if (this.is_batch_() === true) {
var cached_untils = window.JSON.parse(cached_until_header);
for (var alias in cached_untils) {
for (var i = 0; i < this.api_calls_.length; i++) {
if (this.api_calls_[i].alias === alias) {
this.cache_(
this.api_calls_[i],
response.data[alias],
cached_untils[alias]
);
}
}
}
} else {
if (cached_until_header !== null) {
this.cache_(this.api_calls_[0], response.data, cached_until_header);
}
}
/*
* For batch API calls, add in any responses that were pulled out earlier
* because they were cached.
*/
if (this.is_batch_() === true) {
for (var cached_alias in this.cached_batch_api_calls_) {
response.data[cached_alias] =
this.cached_batch_api_calls_[cached_alias];
}
}
// Callback
if (this.callback_ !== undefined) {
this.callback_(response.data);
}
};
/**
* Is this a batch API call? Determined by looking at the number of API calls
* added. If more than one, batch.
*
* @return {boolean} Whether or not this is a batch API call.
*/
beestat.api2.prototype.is_batch_ = function() {
return this.api_calls_.length > 1;
};
/**
* Cache an API call.
*
* @param {Object} api_call The API call object.
* @param {*} data The data to cache.
* @param {string} until Timestamp to cache until.
*/
beestat.api2.prototype.cache_ = function(api_call, data, until) {
var server_date = moment(this.xhr_.getResponseHeader('date'));
var duration = moment.duration(moment(until).diff(server_date));
beestat.api2.cache[this.get_key_(api_call)] = {
'data': data,
'until': moment().add(duration.asSeconds(), 'seconds')
};
/**
* Save the cache to localStorage to persist across reloads. It just happens
* to be annoying that localStorage only supports strings so I prefer to
* deal with myself.
*/
// window.localStorage.setItem(
// 'api_cache',
// window.JSON.stringify(beestat.api2.cache)
// );
};
/**
* Look for cached data for an API call and return it if not expired.
*
* @param {Object} api_call The API call object.
*
* @return {*} The cached data, or undefined if none.
*/
beestat.api2.prototype.get_cached_ = function(api_call) {
var cached = beestat.api2.cache[this.get_key_(api_call)];
if (
cached !== undefined &&
moment().isAfter(cached.until) === false
) {
return cached;
}
return undefined;
};
/**
* Get a cache key for an API call. There's a lack of hash options in
* JavaScript so this just concatenates a bunch of stuff together.
*
* @param {Object} api_call The API call object.
*
* @return {string} The cache key.
*/
beestat.api2.prototype.get_key_ = function(api_call) {
return api_call.resource + '.' + api_call.method + '.' + api_call.arguments;
};
// TODO OLD DELETE THIS
beestat.api = function(resource, method, args, callback) {
var xhr = new XMLHttpRequest();
var load = function() {
var response;
try {
response = window.JSON.parse(this.responseText);
} catch (e) {
beestat.error('API returned invalid response.', this.responseText);
return;
}
if (
response.data &&
(
response.data.error_code === 1004 || // Session is expired.
response.data.error_code === 10001 || // Could not get first token.
response.data.error_code === 10002 || // Could not refresh ecobee token; no token found.
response.data.error_code === 10003 // Could not refresh ecobee token; ecobee returned no token.
)
) {
window.location.href = '/';
} else if (response.data && response.data.error_code === 1209) {
// Could not get lock; safe to ignore as that means sync is running.
} else if (response.success !== true) {
beestat.error(
'API call failed: ' + response.data.error_message,
JSON.stringify(response, null, 2)
);
} else if (callback !== undefined) {
callback(response.data);
}
};
xhr.addEventListener('load', load);
var api_key = 'ER9Dz8t05qUdui0cvfWi5GiVVyHP6OB8KPuSisP2';
if (resource === 'api' && method === 'batch') {
args.forEach(function(api_call, i) {
if (args[i].arguments !== undefined) {
args[i].arguments = JSON.stringify(args[i].arguments);
}
});
xhr.open(
'POST',
'../api/?batch=' + JSON.stringify(args) +
'&api_key=' + api_key
);
} else {
xhr.open(
'POST',
'../api/?resource=' + resource +
'&method=' + method +
(args === undefined ? '' : '&arguments=' + JSON.stringify(args)) +
'&api_key=' + api_key
);
}
xhr.send();
};