/*******************************************************************************
* Copyright 2013 ClearBlade, Inc
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Any redistribution of this program in any form must include this copyright
*******************************************************************************/
if (!window.console) {
window.console = window.console || {};
window.console.log = window.console.log || function () {};
}
(function (window, undefined) {
'use strict';
var ClearBlade, $cb, currentUser;
/**
* This is the base module for the ClearBlade Platform API
* @namespace ClearBlade
* @example
Initialize ClearBladeAPI
* initOptions = {systemKey: 'asdfknafikjasd3853n34kj2vc', systemSecret: 'SHHG245F6GH7SDFG823HGSDFG9'};
* ClearBlade.init(initOptions);
*
*/
if (typeof exports != 'undefined') {
window = exports;
} else {
ClearBlade = $cb = window.$cb = window.ClearBlade = window.ClearBlade || {};
}
/**
* This method returns the current version of the API
* @method ClearBlade.getApiVersion
*/
ClearBlade.getApiVersion = function () {
return '0.0.2';
};
ClearBlade.MESSAGING_QOS_AT_MOST_ONCE = 0;
ClearBlade.MESSAGING_QOS_AT_LEAST_ONCE = 1;
ClearBlade.MESSAGING_QOS_EXACTLY_ONCE = 2;
/**
* This method initializes the ClearBlade module with the values needed to connect to the platform
* @method ClearBlade.init
* @param options {Object} the `options` Object
*/
ClearBlade.init = function (options) {
//check for undefined/null then check if they are the correct types for required params
if (!options || typeof options !== 'object')
throw new Error('Options must be an object or it is undefined');
if (!options.systemKey || typeof options.systemKey !== 'string')
throw new Error('systemKey must be defined/a string');
if (!options.systemSecret || typeof options.systemSecret !== 'string')
throw new Error('systemSecret must be defined/a string');
//check for optional params.
if (options.logging && typeof options.logging !== 'boolean')
throw new Error('logging must be a true boolean if present');
if (options.callback && typeof options.callback !== 'function') {
throw new Error('callback must be a function');
}
if (options.email && typeof options.email !== 'string') {
throw new Error('email must be a string');
}
if (options.password && typeof options.password !== 'string') {
throw new Error('password must be a string');
}
if (options.registerUser && typeof options.registerUser !== 'boolean') {
throw new Error('registerUser must be a true boolean if present');
}
if (options.useUser && (!options.useUser.email || !options.useUser.authToken)) {
throw new Error('useUser must contain both an email and an authToken ' +
'{"email":email,"authToken":authToken}');
}
if (options.email && !options.password) {
throw new Error('Must provide a password for email');
}
if (options.password && !options.email) {
throw new Error('Must provide a email for password');
}
if (options.registerUser && !options.email) {
throw new Error('Cannot register anonymous user. Must provide an email');
}
if (options.useUser && (options.email || options.password || options.registerUser)) {
throw new Error('Cannot authenticate or register a new user when useUser is set');
}
// store keys
/**
* This is the app key that will identify your app in order to connect to the Platform
* @property systemKey
* @type String
*/
ClearBlade.systemKey = options.systemKey;
/**
* This is the app secret that will be used in combination with the systemKey to authenticate your app
* @property systemSecret
* @type String
*/
ClearBlade.systemSecret = options.systemSecret;
/**
* This is the master secret that is used during development to test many apps at a time
* This is not currently not in use
* @property masterSecret
* @type String
*/
ClearBlade.masterSecret = options.masterSecret || null;
/**
* This is the URI used to identify where the Platform is located
* @property URI
* @type String
*/
ClearBlade.URI = options.URI || "https://platform.clearblade.com";
/**
* This is the URI used to identify where the Messaging server is located
* @property messagingURI
* @type String
*/
ClearBlade.messagingURI = options.messagingURI || "platform.clearblade.com";
/**
* This is the default port used when connecting to the messaging server
* @prpopert messagingPort
* @type Number
*/
ClearBlade.messagingPort = options.messagingPort || 8904;
/**
* This is the property that tells the API whether or not the API will log to the console
* This should be left `false` in production
* @property logging
* @type Boolean
*/
ClearBlade.logging = options.logging || false;
ClearBlade.defaultQoS = options.defaultQoS || 0;
/**
* This is the amount of time that the API will use to determine a timeout
* @property _callTimeout=30000
* @type Number
* @private
*/
ClearBlade._callTimeout = options.callTimeout || 30000; //default to 30 seconds
ClearBlade.user = null;
if (options.useUser) {
ClearBlade.user = options.useUser;
} else if (options.registerUser) {
ClearBlade.registerUser(options.email, options.password, function(err, response) {
if (err) {
execute(err, response, options.callback);
} else {
ClearBlade.loginUser(options.email, options.password, function(err, user) {
execute(err, user, options.callback);
});
}
});
} else if (options.email) {
ClearBlade.loginUser(options.email, options.password, function(err, user) {
execute(err, user, options.callback);
});
} else {
ClearBlade.loginAnon(function(err, user) {
execute(err, user, options.callback);
});
}
};
var _validateEmailPassword = function(email, password) {
if (email == null || email == undefined || typeof email != 'string') {
throw new Error("Email must be given and must be a string");
}
if (password == null || password == undefined || typeof password != 'string') {
throw new Error("Password must be given and must be a string");
}
};
ClearBlade.setUser = function(email, authToken) {
ClearBlade.user = {
"email": email,
"authToken": authToken
};
};
ClearBlade.registerUser = function(email, password, callback) {
_validateEmailPassword(email, password);
ClearBlade.request({
method: 'POST',
endpoint: 'api/v/1/user/reg',
useUser: false,
body: { "email": email, "password": password }
}, function (err, response) {
if (err) {
execute(true, response, callback);
} else {
execute(false, "User successfully registered", callback);
}
});
};
ClearBlade.isCurrentUserAuthenticated = function(callback) {
ClearBlade.request({
method: 'POST',
endpoint: 'api/v/1/user/checkauth'
}, function (err, response) {
if (err) {
execute(true, response, callback);
} else {
execute(false, response.is_authenticated, callback);
}
});
};
ClearBlade.logoutUser = function(callback) {
ClearBlade.request({
method: 'POST',
endpoint: 'api/v/1/user/logout'
}, function(err, response) {
if (err) {
execute(true, response, callback);
} else {
execute(false, "User Logged out", callback);
}
});
};
ClearBlade.loginAnon = function(callback) {
ClearBlade.request({
method: 'POST',
useUser: false,
endpoint: 'api/v/1/user/anon'
}, function(err, response) {
if (err) {
execute(true, response, callback);
} else {
ClearBlade.setUser(null, response.user_token);
execute(false, ClearBlade.user, callback);
}
});
};
ClearBlade.loginUser = function(email, password, callback) {
_validateEmailPassword(email, password);
ClearBlade.request({
method: 'POST',
useUser: false,
endpoint: 'api/v/1/user/auth',
body: { "email": email, "password": password }
}, function (err, response) {
if (err) {
execute(true, response, callback);
} else {
ClearBlade.setUser(email, response.user_token);
execute(false, ClearBlade.user, callback);
}
});
};
/*
* Helper functions
*/
var execute = function (error, response, callback) {
if (typeof callback === 'function') {
callback(error, response);
} else {
logger("Did you forget to supply a valid Callback!");
}
};
var logger = function (message) {
if (ClearBlade.logging) {
console.log(message);
}
return;
};
var isObjectEmpty = function (object) {
/*jshint forin:false */
if (typeof object !== 'object') {
return true;
}
for (var keys in object) {
return false;
}
return true;
};
var makeKVPair = function (key, value) {
var KVPair = {};
KVPair[key] = value;
return KVPair;
};
var addToQuery = function(queryObj, key, value) {
queryObj.query[key] = value;
};
var addFilterToQuery = function (queryObj, condition, key, value) {
var newObj = {};
newObj[key] = value;
var newFilter = {};
newFilter[condition] = [newObj];
if (typeof queryObj.query.FILTERS === 'undefined') {
queryObj.query.FILTERS = [];
queryObj.query.FILTERS.push([newFilter]);
return;
} else {
for (var i = 0; i < queryObj.query.FILTERS[0].length; i++) {
for (var k in queryObj.query.FILTERS[0][i]) {
if (queryObj.query.FILTERS[0][i].hasOwnProperty(k)) {
if (k === condition) {
queryObj.query.FILTERS[0][i][k].push(newObj);
return;
}
}
}
}
queryObj.query.FILTERS[0].push(newFilter);
}
};
var addSortToQuery = function(queryObj, direction, column) {
if (typeof queryObj.query.SORT === 'undefined') {
queryObj.query.SORT = [];
}
queryObj.query.SORT.push(makeKVPair(direction, column));
};
/*
* request method
*
* TODO: Go over with a fine toothed comb! Get all superfluous methods out!
*
*/
var _request = function (options, callback) {
var self = ClearBlade;
var method = options.method || 'GET';
var endpoint = options.endpoint || '';
var body = options.body || {};
var qs = options.qs || '';
var url = options.URI || self.URI;
var useUser = options.useUser || true;
var authToken = useUser && options.authToken;
if (useUser && !authToken && ClearBlade.user && ClearBlade.user.authToken) {
authToken = ClearBlade.user.authToken;
}
var params = qs;
if (endpoint) {
url += ('/' + endpoint);
}
if (params) {
url += "?" + params;
}
//begin XMLHttpRequest
var httpRequest;
if (typeof window.XMLHttpRequest !== 'undefined') { // Mozilla, Safari, IE 10 ..
httpRequest = new XMLHttpRequest();
// if "withCredentials is not in the XMLHttpRequest object CORS is not supported
if (!("withCredentials" in httpRequest)) {
logger("Sorry it seems that CORS is not supported on your Browser. The RESTful api calls will not work!");
httpRequest = null;
throw new Error("CORS is not supported!");
}
httpRequest.open(method, url, true);
} else if (typeof window.XDomainRequest !== 'undefined') { // IE 8/9
httpRequest = new XDomainRequest();
httpRequest.open(method, url);
} else {
alert("Sorry it seems that CORS is not supported on your Browser. The RESTful api calls will not work!");
httpRequest = null;
throw new Error("CORS is not supported!");
}
// Set Credentials; Maybe some encryption later
if (authToken) {
httpRequest.setRequestHeader("CLEARBLADE-USERTOKEN", authToken);
} else {
httpRequest.setRequestHeader("ClearBlade-SystemKey", ClearBlade.systemKey);
httpRequest.setRequestHeader("ClearBlade-SystemSecret", ClearBlade.systemSecret);
}
if (!isObjectEmpty(body) || params) {
if (method === "POST" || method === "PUT") {
// Content-Type is expected for POST and PUT; bad things can happen if you don't specify this.
httpRequest.setRequestHeader("Content-Type", "application/json");
}
httpRequest.setRequestHeader("Accept", "application/json");
/* Authorization headers expected to change once app structure is set up */
//set Authorization header
if (typeof ClearBlade.masterSecret === 'String') {
// if masterSecret exists as a string use it for Authorization
httpRequest.setRequestHeader("Authorization", "Basic " + window.btoa(ClearBlade.systemKey + ':' + ClearBlade.masterSecret));
} else if (currentUser && currentUser.getToken()) {
// if there is a current user then use the current Access token
httpRequest.setRequestHeader("Authorization", "Bearer " + self.getToken());
}
/* uncomment the following line once server is set to echo back origins or take custom headers */
//httpRequest.withCredentials = true;
}
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState === 4) {
// Looks like we didn't time out!
clearTimeout(xhrTimeout);
//define error for the entire scope of the if statement
var error = false;
if (httpRequest.status >= 200 && httpRequest.status < 300) {
var parsedResponse;
var response;
var flag = true;
// try to parse response, it should be JSON
if (httpRequest.responseText == '[{}]' || httpRequest.responseText == '[]') {
error = true;
execute(error, "query returned nothing", callback);
} else {
try {
response = JSON.parse(httpRequest.responseText);
parsedResponse = [];
for (var item in response) {
if (response[item] instanceof Object) {
for (var key in response[item]) {
if (response[item][key] instanceof Object ){
if (response[item][key]['_key']) {
delete response[item][key]['_key'];
}
if (response[item][key]['collectionID']) {
delete response[item][key]['collectionID'];
}
parsedResponse.push(response[item][key]);
continue;
} else {
if (response[item]['_key']) {
delete response[item]['_key'];
}
if (response[item]['collectionID']) {
delete response[item]['collectionID'];
}
parsedResponse.push(response[item]);
break;
}
}
} else {
flag = true;
}
}
} catch (e) {
// the response probably was not JSON; Probably had html in it, just output it until all requirements of our backend are defined.
if (e instanceof SyntaxError) {
response = httpRequest.responseText;
// some other error occured; log message , execute callback
} else {
logger("Error during JSON response parsing: " + e);
error = true;
execute(error, e, callback);
}
} // end of catch
// execute callback with whatever was in the response
if (flag) {
execute(error, response, callback);
} else {
execute(error, parsedResponse, callback);
}
}
} else {
var msg = "Request Failed: Status " + httpRequest.status + " " + (httpRequest.statusText);
/*jshint expr: true */
httpRequest.responseText && (msg += "\nmessage:" + httpRequest.responseText);
logger(msg);
error = true;
execute(error, msg, callback);
}
}
};
logger('calling: ' + method + ' ' + url);
body = JSON.stringify(body);
// set up our own TimeOut function, because XMLHttpRequest.onTimeOut is not implemented by all browsers yet.
function callAbort() {
httpRequest.abort();
logger("It seems the request has timed Out, please try again.");
execute(true, "API Request TimeOut", callback);
}
// set timeout and timeout function
var xhrTimeout = setTimeout(callAbort, ClearBlade._callTimeout);
httpRequest.send(body);
};
ClearBlade.request = function (options, callback) {
if (!options || typeof options !== 'object') {
throw new Error("Request: options is not an object or is empty");
}
_request(options, callback);
};
var _parseOperationQuery = function(_query) {
return encodeURIComponent(JSON.stringify(_query.FILTERS));
};
var _parseQuery = function(_query) {
var parsed = encodeURIComponent(JSON.stringify(_query));
return parsed;
};
/**
* Creates a new Collection that represents the server-side collection with the specified collection ID
* @class ClearBlade.Collection
* @classdesc This class represents a server-side collection. It does not actully make a connection upon instantiation, but has all the methods necessary to do so. It also has all the methods necessary to do operations on the server-side collections.
* @param {String} collectionID The string ID for the collection you want to represent.
*/
ClearBlade.Collection = function(collectionID) {
this.ID = collectionID;
};
/**
* Reqests an item or a set of items from the collection.
* @method ClearBlade.Collection.prototype.fetch
* @param {Query} _query Used to request a specific item or subset of items from the collection on the server. Optional.
* @param {function} callback Supplies processing for what to do with the data that is returned from the collection
* @example Fetching data from a collection
* var returnedData = [];
* var callback = function (err, data) {
* if (err) {
* throw new Error (data);
* } else {
* returnedData = data;
* }
* };
*
* col.fetch(query, callback);
* //this will give returnedData the value of what ever was returned from the server.
*/
ClearBlade.Collection.prototype.fetch = function (_query, callback) {
var query;
/*
* The following logic may look funny, but it is intentional.
* I do this because it is typeical for the callback to be the last parameter.
* However, '_query' is an optional parameter, so I have to check if 'callback' is undefined
* in order to see weather or not _query is defined.
*/
if (callback === undefined) {
callback = _query;
query = {
FILTERS: []
};
query = 'query='+ _parseQuery(query);
} else {
query = 'query='+ _parseQuery(_query.query);
}
var reqOptions = {
method: 'GET',
endpoint: 'api/v/1/data/' + this.ID,
qs: query
};
var colID = this.ID;
var callCallback = function (err, data) {
callback(err, data);
};
if (typeof callback === 'function') {
_request(reqOptions, callCallback);
} else {
logger("No callback was defined!");
}
};
/**
* Creates a new item in the collection and returns the created item to the callback
* @method ClearBlade.Collection.prototype.create
* @param {Object} newItem An object that represents an item that you want to add to the collection
* @param {function} callback Supplies processing for what to do with the data that is returned from the collection
* @example Creating a new item in the collection
* //This example assumes a collection of items that have the columns: name, height, and age.
* var newPerson = {
* name: 'Jim',
* height: 70,
* age: 32
* };
* var callback = function (err, data) {
* if (err) {
* throw new Error (data);
* } else {
* console.log(data);
* }
* };
* col.create(newPerson, callback);
* //this inserts the the newPerson item into the collection that col represents
*
*/
ClearBlade.Collection.prototype.create = function (newItem, callback) {
var reqOptions = {
method: 'POST',
endpoint: 'api/v/1/data/' + this.ID,
body: newItem
};
if (typeof callback === 'function') {
_request(reqOptions, callback);
} else {
logger("No callback was defined!");
}
};
/**
* Updates an existing item or set of items
* @method ClearBlade.Collection.prototype.update
* @param {Query} _query Query object to denote which items or set of Items will be changed
* @param {Object} changes Object representing the attributes that you want changed
* @param {function} callback Function that handles the response of the server
* @example Updating a set of items
* //This example assumes a collection of items that have the columns name and age.
* var query = new ClearBlade.Query();
* query.equalTo('name', 'John');
* var changes = {
* age: 23
* };
* var callback = function (err, data) {
* if (err) {
* throw new Error (data);
* } else {
* console.log(data);
* }
* };
*
* col.update(query, changes, callback);
* //sets John's age to 23
*/
ClearBlade.Collection.prototype.update = function (_query, changes, callback) {
var reqOptions = {
method: 'PUT',
endpoint: 'api/v/1/data/' + this.ID,
body: {query: _query.query.FILTERS, $set: changes}
};
if (typeof callback === 'function') {
_request(reqOptions, callback);
} else {
logger("No callback was defined!");
}
};
/**
* Removes an item or set of items from the specified collection
* @method ClearBlade.Collection.prototype.remove
* @param {Query} _query Query object that used to define what item or set of items to remove
* @param {function} callback Function that handles the response from the server
* @example Removing an item in a collection
* //This example assumes that you have a collection with the item whose 'name' attribute is 'John'
* var query = new ClearBlade.Query();
* query.equalTo('name', 'John');
* var callback = function (err, data) {
* if (err) {
* throw new Error (data);
* } else {
* console.log(data);
* }
* };
*
* col.remove(query, callback);
* //removes every item whose 'name' attribute is equal to 'John'
*/
ClearBlade.Collection.prototype.remove = function (_query, callback) {
var query;
if (_query === undefined) {
throw new Error("no query defined!");
} else {
query = 'query=' + _parseOperationQuery(_query.query);
}
var reqOptions = {
method: 'DELETE',
endpoint: 'api/v/1/data/' + this.ID,
qs: query
};
if (typeof callback === 'function') {
_request(reqOptions, callback);
} else {
logger("No callback was defined!");
}
};
/**
* creates a Query object that can be used in Collection methods or on its own to operate on items on the server
* @class ClearBlade.Query
* @param {Object} options Object that has configuration values used when instantiating a Query object
*/
ClearBlade.Query = function (options) {
if (!options) {
options = {};
}
if (options.collection !== undefined || options.collection !== "") {
this.collection = options.collection;
}
this.query = {};
this.OR = [];
this.OR.push([this.query]);
this.offset = options.offset || 0;
this.limit = options.limit || 10;
};
ClearBlade.Query.prototype.ascending = function (field) {
addSortToQuery(this, "ASC", field);
return this;
};
ClearBlade.Query.prototype.descending = function (field) {
addSortToQuery(this, "DESC", field);
return this;
};
/**
* Creates an equality clause in the query object
* @method ClearBlade.Query.prototype.equalTo
* @param {String} field String defining what attribute to compare
* @param {String} value String or Number that is used to compare against
* @example Adding an equality clause to a query
* var query = new ClearBlade.Query();
* query.equalTo('name', 'John');
* //will only match if an item has an attribute 'name' that is equal to 'John'
*/
ClearBlade.Query.prototype.equalTo = function (field, value) {
addFilterToQuery(this, "EQ", field, value);
return this;
};
/**
* Creates a greater than clause in the query object
* @method ClearBlade.Query.prototype.greaterThan
* @param {String} field String defining what attribute to compare
* @param {String} value String or Number that is used to compare against
* @example Adding a greater than clause to a query
* var query = new ClearBlade.Query();
* query.greaterThan('age', 21);
* //will only match if an item has an attribute 'age' that is greater than 21
*/
ClearBlade.Query.prototype.greaterThan = function (field, value) {
addFilterToQuery(this, "GT", field, value);
return this;
};
/**
* Creates a greater than or equality clause in the query object
* @method ClearBlade.Query.prototype.greaterThanEqualTo
* @param {String} field String defining what attribute to compare
* @param {String} value String or Number that is used to compare against
* @example Adding a greater than or equality clause to a query
* var query = new ClearBlade.Query();
* query.greaterThanEqualTo('age', 21);
* //will only match if an item has an attribute 'age' that is greater than or equal to 21
*/
ClearBlade.Query.prototype.greaterThanEqualTo = function (field, value) {
addFilterToQuery(this, "GTE", field, value);
return this;
};
/**
* Creates a less than clause in the query object
* @method ClearBlade.Query.prototype.lessThan
* @param {String} field String defining what attribute to compare
* @param {String} value String or Number that is used to compare against
* @example Adding a less than clause to a query
* var query = new ClearBlade.Query();
* query.lessThan('age', 50);
* //will only match if an item has an attribute 'age' that is less than 50
*/
ClearBlade.Query.prototype.lessThan = function (field, value) {
addFilterToQuery(this, "LT", field, value);
return this;
};
/**
* Creates a less than or equality clause in the query object
* @method ClearBlade.Query.prototype.lessThanEqualTo
* @param {String} field String defining what attribute to compare
* @param {String} value String or Number that is used to compare against
* @example Adding a less than or equality clause to a query
* var query = new ClearBlade.Query();
* query.lessThanEqualTo('age', 50);
* //will only match if an item has an attribute 'age' that is less than or equal to 50
*/
ClearBlade.Query.prototype.lessThanEqualTo = function (field, value) {
addFilterToQuery(this, "LTE", field, value);
return this;
};
/**
* Creates a not equal clause in the query object
* @method ClearBlade.Query.prototype.notEqualTo
* @param {String} field String defining what attribute to compare
* @param {String} value String or Number that is used to compare against
* @example Adding a not equal clause to a query
* var query = new ClearBlade.Query();
* query.notEqualTo('name', 'Jim');
* //will only match if an item has an attribute 'name' that is not equal to 'Jim'
*/
ClearBlade.Query.prototype.notEqualTo = function (field, value) {
addFilterToQuery(this, "NEQ", field, value);
return this;
};
/**
* chains an existing query object to the Query object in an or
* @method ClearBlade.Query.prototype.or
* @param {Query} that Query object that will be added in disjunction to this query object
* @example Chaining two queries together in an or
* var query1 = new ClearBlade.Query();
* var query2 = new ClearBlade.Query();
* query1.equalTo('name', 'John');
* query2.equalTo('name', 'Jim');
* query1.or(query2);
* //will match if an item has an attribute 'name' that is equal to 'John' or 'Jim'
*/
ClearBlade.Query.prototype.or = function (that) {
if (this.query.hasOwnProperty('FILTERS') && that.query.hasOwnProperty('FILTERS')) {
for (var i = 0; i < that.query.FILTERS.length; i++) {
this.query.FILTERS.push(that.query.FILTERS[i]);
}
return this;
} else if (!this.query.hasOwnProperty('FILTERS') && that.query.hasOwnProperty('FILTERS')) {
for (var j = 0; j < that.query.FILTERS.length; j++) {
this.query.FILTERS = [];
this.query.FILTERS.push(that.query.FILTERS[j]);
}
return this;
}
};
/**
* Set the pagination options for a Query.
* @method ClearBlade.Query.prototype.setPage
* @param {int} pageSize Number of items per response page. The default is
* 100.
* @param {int} pageNum Page number, taking into account the page size. The
* default is 1.
*/
ClearBlade.Query.prototype.setPage = function (pageSize, pageNum) {
addToQuery(this, "PAGESIZE", pageSize);
addToQuery(this, "PAGENUM", pageNum);
return this;
};
ClearBlade.Query.prototype.execute = function (method, callback) {
var reqOptions = {
method: method
};
switch(method) {
case "GET":
reqOptions.qs = 'query=' + _parseQuery(this.query);
break;
case "PUT":
reqOptions.body = this.query; // TOOD: check this shit
break;
case "DELETE":
reqOptions.qs = 'query=' + _parseOperationQuery(this.query);
break;
default:
throw new Error("The method " + method + " does not exist");
}
if (this.collection === undefined || this.collection === "") {
throw new Error("No collection was defined");
} else {
reqOptions.endpoint = "api/v/1/data/" + this.collection;
}
if (typeof callback === 'function') {
_request(reqOptions, callback);
} else {
logger("No callback was defined!");
}
};
/**
* Reqests an item or a set of items from the query. Requires that
* the Query object was initialized with a collection.
* @method ClearBlade.Query.prototype.fetch
* @param {function} callback Supplies processing for what to do with the data that is returned from the collection
* @example The typical callback
* var callback = function (err, data) {
* if (err) {
* //error handling
* } else {
* console.log(data);
* }
* };
* query.fetch(callback);
* //this will give returnedData the value of what ever was returned from the server.
*/
ClearBlade.Query.prototype.fetch = function (callback) {
var reqOptions = {
method: 'GET',
qs: 'query=' + _parseQuery(this.query)
};
if (this.collection === undefined || this.collection === "") {
throw new Error("No collection was defined");
} else {
reqOptions.endpoint = "api/v/1/data/" + this.collection;
}
var colID = this.collection;
var callCallback = function (err, data) {
callback(err, data);
};
if (typeof callback === 'function') {
_request(reqOptions, callCallback);
} else {
logger("No callback was defined!");
}
};
/**
* Updates an existing item or set of items. Requires that a collection was
* set when the Query was initialized.
* @method ClearBlade.Query.prototype.update
* @param {Object} changes Object representing the attributes that you want changed
* @param {function} callback Function that handles the response of the server
* @example Updating a set of items
* //This example assumes a collection of items that have the columns name and age.
* var query = new ClearBlade.Query({'collection': 'COLLECTIONID'});
* query.equalTo('name', 'John');
* var changes = {
* age: 23
* };
* var callback = function (err, data) {
* if (err) {
* throw new Error (data);
* } else {
* console.log(data);
* }
* };
*
* query.update(changes, callback);
* //sets John's age to 23
*/
ClearBlade.Query.prototype.update = function (changes, callback) {
var reqOptions = {
method: 'PUT',
body: {query: this.query.FILTERS, $set: changes}
};
var colID = this.collection;
var callCallback = function (err, data) {
if (err) {
callback(err, data);
} else {
var itemArray = [];
for (var i = 0; i < data.length; i++) {
var newItem = new ClearBlade.Item(data[i], colID);
itemArray.push(newItem);
}
callback(err, itemArray);
}
};
if (this.collection === undefined || this.collection === "") {
throw new Error("No collection was defined");
} else {
reqOptions.endpoint = "api/v/1/data/" + this.collection;
}
if (typeof callback === 'function') {
_request(reqOptions, callCallback);
} else {
logger("No callback was defined!");
}
};
/**
* Removes an item or set of items from the Query
* @method ClearBlade.Query.prototype.remove
* @param {function} callback Function that handles the response from the server
* @example Removing an item in a collection
* //This example assumes that you have a collection with the item whose 'name' attribute is 'John'
* var query = new ClearBlade.Query({'collection': 'COLLECTIONID'});
* query.equalTo('name', 'John');
* var callback = function (err, data) {
* if (err) {
* throw new Error (data);
* } else {
* console.log(data);
* }
* };
*
* query.remove(callback);
* //removes every item whose 'name' attribute is equal to 'John'
*/
ClearBlade.Query.prototype.remove = function (callback) {
var reqOptions = {
method: 'DELETE',
qs: 'query=' + _parseOperationQuery(this.query)
};
var colID = this.collection;
var callCallback = function (err, data) {
if (err) {
callback(err, data);
} else {
var itemArray = [];
for (var i in data) {
var newItem = new cb.Item(data[i], colID);
itemArray.push(newItem);
}
callback(err, itemArray);
}
};
if (this.collection == undefined || this.collection == "") {
throw new Error("No collection was defined");
} else {
reqOptions.endpoint = "api/v/1/data/" + this.collection;
}
if (typeof callback === 'function') {
_request(reqOptions, callCallback);
} else {
logger("No callback was defined!");
}
};
ClearBlade.Item = function (data, collection) {
if (!(data instanceof Object)) {
throw new Error("data must be of type Object");
}
if ((typeof collection !== 'string') || (collection == "")) {
throw new Error("You have to give a collection ID");
}
this.collection = collection;
this.data = data;
};
ClearBlade.Item.prototype.save = function () {
//do a put or a post to the database to save the item in the db
var self = this;
var query = new ClearBlade.Query({collection: this.collection});
query.equalTo('itemId', this.data.itemId);
var callback = function (err, data) {
if (err) {
throw new Error (data);
} else {
self.data = data[0].data;
}
};
query.update(this.data, callback);
};
ClearBlade.Item.prototype.refresh = function () {
//do a get to make the local item reflect the database
var self = this;
var query = new ClearBlade.Query({collection: this.collection});
query.equalTo('itemId', this.data.itemId);
var callback = function (err, data) {
if (err) {
throw new Error (data);
} else {
self.data = data[0].data;
}
};
query.fetch(callback);
};
ClearBlade.Item.prototype.destroy = function () {
//deletes the relative record in the DB then deletes the item locally
var self = this;
var query = new ClearBlade.Query({collection: this.collection});
query.equalTo('itemId', this.data.itemId);
var callback = function (err, data) {
if (err) {
throw new Error (data);
} else {
self.data = null;
self.collection = null;
delete self.data;
delete self.collection;
}
};
query.remove(callback);
delete this;
};
//ClearBlade.Code.exe = function(){};
ClearBlade.Code = function(){};
ClearBlade.Code.execute = function(name, params, callback){
var reqOptions = {
method: 'POST',
endpoint: 'api/v/1/code/' + ClearBlade.systemKey + '/' + name,
body: params
};
if (typeof callback === 'function') {
_request(reqOptions, callback);
} else {
logger("No callback was defined!");
}
};
ClearBlade.User = function(){
};
ClearBlade.User.getUser = function(callback){
var reqOptions = {
method: 'GET',
endpoint: 'api/v/1/user/info'
};
if (typeof callback === 'function') {
_request(reqOptions, callback);
} else {
logger("No callback was defined!");
}
};
ClearBlade.User.setUser = function(data, callback){
var reqOptions = {
method: 'PUT',
endpoint: 'api/v/1/user/info',
body: data
};
if (typeof callback === 'function') {
_request(reqOptions, callback);
} else {
logger("No callback was defined!");
}
};
ClearBlade.User.allUsers = function(_query, callback) {
var query;
if (callback === undefined) {
callback = _query;
query = '';
} else {
query = 'query=' + _parseQuery(_query.query);
}
var reqOptions = {
method: 'GET',
endpoint: 'api/v/1/user',
qs: query
};
var callCallback = function(err, data) {
callback(err, data);
};
if (typeof callback === 'function') {
_request(reqOptions, callCallback);
} else {
logger('No callback was defined!');
}
};
/**
* Initializes the ClearBlade messaging object and connects to a server.
* @class ClearBlade.Messaging
* @param {Object} options This value contains the config object for connecting. A number of reasonable defaults are set for the option if none are set.
*
*The connect options and their defaults are:
*
{number} [timeout] sets the timeout for the websocket connection in case of failure. The default is 60
* {Messaging Message} [willMessage] A message sent on a specified topic when the client disconnects without sending a disconnect packet. The default is none.
* {Number} [keepAliveInterval] The server disconnects if there is no activity for this pierod of time. The default is 60.
* {boolean} [cleanSession] The server will persist state of the session if true. Not avaliable in beta.
* {boolean} [useSSL] The option to use SSL websockets. Default is false for now.
* {object} [invocationContext] An object to wrap all the important variables needed for the onFalure and onSuccess functions. The default is empty.
* {function} [onSuccess] A callback to operate on the result of a sucessful connect. In beta the default is just the invoking of the `callback` parameter with the data from the connection.
* {function} [onFailure] A callback to operate on the result of an unsuccessful connect. In beta the default is just the invoking of the `callback` parameter with the data from the connection.
* {Object} [hosts] An array of hosts to attempt to connect too. Sticks to the first one that works. The default is [ClearBlade.messagingURI].
* {Object} [ports] An array of ports to try, it also sticks to thef first one that works. The default is [1337].
*
* @param {function} callback Callback to be run upon either succeessful or
* failed connection
* @example A standard connect
* var callback = function (data) {
* console.log(data);
* };
* //A connect with a nonstandard timeout
* var cb = new ClearBlade.Messaging({"timeout":15}, callback);
*/
ClearBlade.Messaging = function(options, callback){
var that = this;
//roll through the config
var conf = {};
conf.userName = ClearBlade.user.authToken;
conf.password = ClearBlade.systemKey;
conf.cleanSession = options.cleanSession || true;
conf.useSSL = options.useSSL || false; //up for debate. ole' perf vs sec argument
conf.hosts = options.hosts || [ClearBlade.messagingURI];
conf.ports = options.ports || [ClearBlade.messagingPort];
if (options.qos !== undefined && options.qos !== null) {
this._qos = options.qos;
} else {
this._qos = ClearBlade.defaultQoS;
}
var onConnectionLost = function(){
console.log("ClearBlade Messaging connection lost- attempting to reestablish");
that.client.connect(conf);
};
var onMessageArrived = function(message){
// messageCallback from Subscribe()
that.messageCallback(message.payloadString);
};
var clientID = Math.floor(Math.random() * 10e12).toString();
this.client = new Messaging.Client(conf.hosts[0],conf.ports[0],clientID);
this.client.onConnectionLost = onConnectionLost;
this.client.onMessageArrived = onMessageArrived;
// the mqtt websocket library uses "onConnect," but our terminology uses
// "onSuccess" and "onFailure"
var onSuccess = function(data) {
callback(data);
};
this.client.onConnect = onSuccess;
var onFailure = function(err) {
console.log("ClearBlade Messaging failed to connect");
callback(err);
};
conf.onSuccess = options.onSuccess || onSuccess;
conf.onFailure = options.onFailure || onFailure;
this.client.connect(conf);
};
/**
* Publishes to a topic.
* @method ClearBlade.Messaging.prototype.Publish
* @param {string} topic Is the topic path of the message to be published. This will be sent to all listeners on the topic. No default.
* @param {string | ArrayBuffer} payload The payload to be sent. Also no default.
* @example How to publish
* var callback = function (data) {
* console.log(data);
* };
* var cb = ClearBlade.Messaging({}, callback);
* cb.Publish("ClearBlade/is awesome!","Totally rules");
* //Topics can include spaces and punctuation except "/"
*/
ClearBlade.Messaging.prototype.Publish = function(topic, payload){
var msg = new Messaging.Message(payload);
msg.destinationName = topic;
msg.qos = this._qos;
this.client.send(msg);
};
/**
* Subscribes to a topic
* @method ClearBlade.Messaging.prototype.Subscribe
* @param {string} topic The topic to subscribe to. No default.
* @param {Object} [options] The configuration object. Options:
{Number} [qos] The quality of service specified within MQTT. The default is 0, or fire and forget.
{Object} [invocationContext] An object that contains variables and other data for the onSuccess and failure callbacks. The default is blank.
{function} [onSuccess] The callback invoked on a successful subscription. The default is nothing.
{function} [onFailure] The callback invoked on a failed subsciption. The default is nothing.
{Number} [timeout] The time to wait for a response from the server acknowleging the subscription.
* @param {function} messageCallback Callback to invoke upon message arrival
* @example How to publish
* var callback = function (data) {
* console.log(data);
* };
* var cb = ClearBlade.Messaging({}, callback);
* cb.Subscribe("ClearBlade/is awesome!",{});
*/
ClearBlade.Messaging.prototype.Subscribe = function (topic,options,messageCallback){
var onSuccess = function() {
var conf = {};
conf["qos"] = this._qos || 0;
conf["invocationContext"] = options["invocationContext"] || {};
conf["onSuccess"] = options["onSuccess"] || null;
conf["onFailure"] = options["onFailure"] || null;
conf["timeout"] = options["timeout"] || 60;
this.client.subscribe(topic,conf);
};
var onFailure = function() {
alert("failed to connect");
};
this.client.subscribe(topic);
this.messageCallback = messageCallback;
};
/**
* Unsubscribes from a topic
* @method ClearBlade.Messaging.prototype.Unsubscribe
* @param {string} topic The topic to subscribe to. No default.
* @param {Object} [options] The configuration object
@options {Object} [invocationContext] An object that contains variables and other data for the onSuccess and failure callbacks. The default is blank.
@options {function} [onSuccess] The callback invoked on a successful unsubscription. The default is nothing.
@options {function} [onFailure] The callback invoked on a failed unsubcription. The default is nothing.
@options {Number} [timeout] The time to wait for a response from the server acknowleging the subscription.
* @example How to publish
* var callback = function (data) {
* console.log(data);
* };
* var cb = ClearBlade.Messaging({}, callback);
* cb.Unsubscribe("ClearBlade/is awesome!",{"onSuccess":function(){console.log("we unsubscribe");});
*/
ClearBlade.Messaging.prototype.Unsubscribe = function(topic,options){
var conf = {};
conf["invocationContext"] = options["invocationContext"] || {};
conf["onSuccess"] = options["onSuccess"] || function(){};//null;
conf["onFailure"] = options["onFailure"] || function(){};//null;
conf["timeout"] = options["timeout"] || 60;
this.client.unsubscribe(topic,conf);
};
/**
* Disconnects from the server.
* @method ClearBlade.Messaging.prototype.Disconnect
* @example How to publish
* var callback = function (data) {
* console.log(data);
* };
* var cb = ClearBlade.Messaging({}, callback);
* cb.Disconnect()//why leave so soon :(
*/
ClearBlade.Messaging.prototype.Disconnect = function(){
this.client.disconnect()
};
})(window);