mirror of
https://github.com/beestat/app.git
synced 2025-05-24 02:14:03 -04:00
beestat.io, app.beestat.io, api.beestat.io, demo.beestat.io They all now work and the cookies should behave better. Fixes #134 among other things.
288 lines
8.2 KiB
JavaScript
288 lines
8.2 KiB
JavaScript
beestat.api = function() {
|
|
this.api_calls_ = [];
|
|
};
|
|
|
|
/**
|
|
* Stores cached responses statically across all API calls.
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
beestat.api.cache = {};
|
|
|
|
/**
|
|
* Beestat's local API key.
|
|
*
|
|
* @type {string}
|
|
*/
|
|
beestat.api.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.api} This.
|
|
*/
|
|
beestat.api.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.api.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);
|
|
});
|
|
|
|
// var endpoint = (window.environment === 'live')
|
|
// ? 'https://api.beestat.io/'
|
|
// : 'http://' + window.environment + '.api.beestat.io/';
|
|
// this.xhr_.open('POST', endpoint + '?' + query_string);
|
|
this.xhr_.open('POST', 'api/?' + query_string);
|
|
this.xhr_.send();
|
|
} else {
|
|
if (this.api_calls_.length === 0) {
|
|
throw new Error('Must add at least one API call.');
|
|
}
|
|
|
|
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.
|
|
* Timeout makes this behave like an actual API call in terms of
|
|
* program flow. Without this, if there is a rerender() inside a
|
|
* callback, the rerender can happen during a render which causes
|
|
* problems.
|
|
*/
|
|
if (this.callback_ !== undefined) {
|
|
setTimeout(function() {
|
|
self.callback_(self.cached_batch_api_calls_);
|
|
}, 0);
|
|
}
|
|
} 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) {
|
|
/**
|
|
* Timeout makes this behave like an actual API call in terms of
|
|
* program flow. Without this, if there is a rerender() inside a
|
|
* callback, the rerender can happen during a render which causes
|
|
* problems.
|
|
*/
|
|
setTimeout(function() {
|
|
self.callback_(cached.data);
|
|
}, 0);
|
|
}
|
|
} 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.api} This.
|
|
*/
|
|
beestat.api.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.api} This.
|
|
*/
|
|
beestat.api.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.api.prototype.load_ = function(response_text) {
|
|
var response;
|
|
try {
|
|
response = window.JSON.parse(response_text);
|
|
} catch (e) {
|
|
var detail = response_text;
|
|
if (detail === '') {
|
|
detail = this.xhr_.status + ' ' + this.xhr_.statusText;
|
|
}
|
|
|
|
beestat.error('API returned invalid response.', detail);
|
|
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)
|
|
);
|
|
return;
|
|
}
|
|
|
|
// 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.api.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.api.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.api.cache[this.get_key_(api_call)] = {
|
|
'data': data,
|
|
'until': moment().add(duration.asSeconds(), 'seconds')
|
|
};
|
|
};
|
|
|
|
/**
|
|
* 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.api.prototype.get_cached_ = function(api_call) {
|
|
var cached = beestat.api.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.api.prototype.get_key_ = function(api_call) {
|
|
return api_call.resource + '.' + api_call.method + '.' + api_call.arguments;
|
|
};
|