Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

latest build with doc comments intact

  • Loading branch information...
commit 03f2fe0e0a36306759e09f1a74ed6f21b28adab0 1 parent 57cb2be
@r3b r3b authored
Showing with 1,010 additions and 7 deletions.
  1. +1,007 −4 apigee.js
  2. +3 −3 apigee.min.js
View
1,011 apigee.js
@@ -1,6 +1,30 @@
-/*! apigee-javascript-sdk@2.0.5 2013-11-26 */
+/*! apigee-javascript-sdk@2.0.5 2014-01-02 */
+/*
+* This module is a collection of classes designed to make working with
+* the Apigee App Services API as easy as possible.
+* Learn more at http://apigee.com/docs/usergrid
+*
+* Copyright 2012 Apigee Corporation
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+* @author rod simpson (rod@apigee.com)
+* @author matt dobson (matt@apigee.com)
+* @author ryan bridges (rbridges@apigee.com)
+*/
(function() {
var name = "Usergrid", global = global || this, overwrittenName = global[name];
+ //authentication type constants for Node.js
var AUTH_CLIENT_ID = "CLIENT_ID";
var AUTH_APP_USER = "APP_USER";
var AUTH_NONE = "NONE";
@@ -14,7 +38,9 @@
}
function Usergrid() {}
Usergrid.Client = function(options) {
+ //usergrid enpoint
this.URI = options.URI || "https://api.usergrid.com";
+ //Find your Orgname and Appname in the Admin portal (http://apigee.com/usergrid)
if (options.orgName) {
this.set("orgName", options.orgName);
}
@@ -24,16 +50,38 @@
if (options.appVersion) {
this.set("appVersion", options.appVersion);
}
+ //authentication data
this.authType = options.authType || AUTH_NONE;
this.clientId = options.clientId;
this.clientSecret = options.clientSecret;
- this.token = options.token || null;
+ this.setToken(options.token || null);
+ //other options
this.buildCurl = options.buildCurl || false;
this.logging = options.logging || false;
+ //timeout and callbacks
this._callTimeout = options.callTimeout || 3e4;
+ //default to 30 seconds
this._callTimeoutCallback = options.callTimeoutCallback || null;
this.logoutCallback = options.logoutCallback || null;
};
+ /*
+ * Main function for making requests to the API using node.
+ * Use Usergrid.Client.prototype.request for cross-platform compatibility.
+
+ *
+ * options object:
+ * `method` - http method (GET, POST, PUT, or DELETE), defaults to GET
+ * `qs` - object containing querystring values to be appended to the uri
+ * `body` - object containing entity body for POST and PUT requests
+ * `endpoint` - API endpoint, for example 'users/fred'
+ * `mQuery` - boolean, set to true if running management query, defaults to false
+ *
+ * @method _request_node
+ * @public
+ * @params {object} options
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Client.prototype._request_node = function(options, callback) {
global.request = global.request || require("request");
var request = global.request;
@@ -43,6 +91,7 @@
var body = options.body || {};
var qs = options.qs || {};
var mQuery = options.mQuery || false;
+ //is this a query to the management endpoint?
var orgName = this.get("orgName");
var appName = this.get("appName");
if (!mQuery && !orgName && !appName) {
@@ -85,11 +134,13 @@
} else {
err = true;
if (r.error === "auth_expired_session_token" || r.error === "auth_missing_credentials" || r.error == "auth_unverified_oath" || r.error === "expired_token" || r.error === "unauthorized" || r.error === "auth_invalid") {
+ //this error type means the user is not authorized. If a logout function is defined, call it
var error = r.body.error;
var errorDesc = r.body.error_description;
if (self.logging) {
console.log("Error (" + r.statusCode + ")(" + error + "): " + errorDesc);
}
+ //if the user has specified a logout callback:
if (typeof self.logoutCallback === "function") {
self.logoutCallback(err, data);
} else if (typeof callback === "function") {
@@ -108,6 +159,24 @@
}
});
};
+ /*
+ * Main function for making requests to the API using a browser.
+ * Use Usergrid.Client.prototype.request for cross-platform compatibility.
+
+ *
+ * options object:
+ * `method` - http method (GET, POST, PUT, or DELETE), defaults to GET
+ * `qs` - object containing querystring values to be appended to the uri
+ * `body` - object containing entity body for POST and PUT requests
+ * `endpoint` - API endpoint, for example 'users/fred'
+ * `mQuery` - boolean, set to true if running management query, defaults to false
+ *
+ * @method _request_node
+ * @public
+ * @params {object} options
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Client.prototype._request_xhr = function(options, callback) {
var self = this;
var method = options.method || "GET";
@@ -115,6 +184,7 @@
var body = options.body || {};
var qs = options.qs || {};
var mQuery = options.mQuery || false;
+ //is this a query to the management endpoint?
var orgName = this.get("orgName");
var appName = this.get("appName");
if (!mQuery && !orgName && !appName) {
@@ -131,17 +201,22 @@
if (self.getToken()) {
qs.access_token = self.getToken();
}
+ //append params to the path
var encoded_params = encodeParams(qs);
if (encoded_params) {
uri += "?" + encoded_params;
}
+ //stringify the body object
body = JSON.stringify(body);
+ //so far so good, so run the query
var xhr = new XMLHttpRequest();
xhr.open(method, uri, true);
+ //add content type = json if there is a json payload
if (body) {
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Accept", "application/json");
}
+ // Handle response.
xhr.onerror = function(response) {
self._end = new Date().getTime();
if (self.logging) {
@@ -150,6 +225,7 @@
if (self.logging) {
console.log("Error: API call failed at the network level.");
}
+ //network error
clearTimeout(timeout);
var err = true;
if (typeof callback === "function") {
@@ -157,19 +233,34 @@
}
};
xhr.onload = function(response) {
+ //call timing, get time, then log the call
self._end = new Date().getTime();
if (self.logging) {
console.log("success (time: " + self.calcTimeDiff() + "): " + method + " " + uri);
}
+ //call completed
clearTimeout(timeout);
- response = JSON.parse(xhr.responseText);
+ //decode the response
+ try {
+ response = JSON.parse(xhr.responseText);
+ } catch (e) {
+ response = {
+ error: "unhandled_error",
+ error_description: xhr.responseText
+ };
+ xhr.status = xhr.status === 200 ? 400 : xhr.status;
+ console.error(e);
+ }
if (xhr.status != 200) {
+ //there was an api error
var error = response.error;
var error_description = response.error_description;
if (self.logging) {
console.log("Error (" + xhr.status + ")(" + error + "): " + error_description);
}
if (error == "auth_expired_session_token" || error == "auth_missing_credentials" || error == "auth_unverified_oath" || error == "expired_token" || error == "unauthorized" || error == "auth_invalid") {
+ //these errors mean the user is not authorized for whatever reason. If a logout function is defined, call it
+ //if the user has specified a logout callback:
if (typeof self.logoutCallback === "function") {
return self.logoutCallback(true, response);
}
@@ -191,6 +282,7 @@
self.callback("API CALL TIMEOUT");
}
}, self._callTimeout);
+ //set for 30 seconds
if (this.logging) {
console.log("calling: " + method + " " + uri);
}
@@ -205,6 +297,22 @@
this._start = new Date().getTime();
xhr.send(body);
};
+ /*
+ * Main function for making requests to the API using node. You may call this method directly
+ *
+ * options object:
+ * `method` - http method (GET, POST, PUT, or DELETE), defaults to GET
+ * `qs` - object containing querystring values to be appended to the uri
+ * `body` - object containing entity body for POST and PUT requests
+ * `endpoint` - API endpoint, for example 'users/fred'
+ * `mQuery` - boolean, set to true if running management query, defaults to false
+ *
+ * @method _request_node
+ * @public
+ * @params {object} options
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Client.prototype.request = function(options, callback) {
if ("undefined" !== typeof window) {
Usergrid.Client.prototype._request_xhr.apply(this, arguments);
@@ -212,6 +320,14 @@
Usergrid.Client.prototype._request_node.apply(this, arguments);
}
};
+ /*
+ * function for building asset urls
+ *
+ * @method buildAssetURL
+ * @public
+ * @params {string} uuid
+ * @return {string} assetURL
+ */
Usergrid.Client.prototype.buildAssetURL = function(uuid) {
var self = this;
var qs = {};
@@ -219,12 +335,22 @@
if (self.getToken()) {
qs.access_token = self.getToken();
}
+ //append params to the path
var encoded_params = encodeParams(qs);
if (encoded_params) {
assetURL += "?" + encoded_params;
}
return assetURL;
};
+ /*
+ * Main function for creating new groups. Call this directly.
+ *
+ * @method createGroup
+ * @public
+ * @params {string} path
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Client.prototype.createGroup = function(options, callback) {
var getOnExist = options.getOnExist || false;
options = {
@@ -248,17 +374,45 @@
}
});
};
+ /*
+ * Main function for creating new entities - should be called directly.
+ *
+ * options object: options {data:{'type':'collection_type', 'key':'value'}, uuid:uuid}}
+ *
+ * @method createEntity
+ * @public
+ * @params {object} options
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Client.prototype.createEntity = function(options, callback) {
+ // todo: replace the check for new / save on not found code with simple save
+ // when users PUT on no user fix is in place.
+ /*
+ options = {
+ client:this,
+ data:options
+ }
+ var entity = new Usergrid.Entity(options);
+ entity.save(function(err, data) {
+ if (typeof(callback) === 'function') {
+ callback(err, entity);
+ }
+ });
+ */
var getOnExist = options.getOnExist || false;
+ //if true, will return entity if one already exists
options = {
client: this,
data: options
};
var entity = new Usergrid.Entity(options);
entity.fetch(function(err, data) {
+ //if the fetch doesn't find what we are looking for, or there is no error, do a save
var okToSave = err && "service_resource_not_found" === data.error || "no_name_specified" === data.error || "null_pointer" === data.error || !err && getOnExist;
if (okToSave) {
entity.set(options.data);
+ //add the data again just in case
entity.save(function(err, data) {
if (typeof callback === "function") {
callback(err, entity, data);
@@ -271,6 +425,20 @@
}
});
};
+ /*
+ * Main function for getting existing entities - should be called directly.
+ *
+ * You must supply a uuid or (username or name). Username only applies to users.
+ * Name applies to all custom entities
+ *
+ * options object: options {data:{'type':'collection_type', 'name':'value', 'username':'value'}, uuid:uuid}}
+ *
+ * @method createEntity
+ * @public
+ * @params {object} options
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Client.prototype.getEntity = function(options, callback) {
options = {
client: this,
@@ -283,6 +451,16 @@
}
});
};
+ /*
+ * Main function for restoring an entity from serialized data.
+ *
+ * serializedObject should have come from entityObject.serialize();
+ *
+ * @method restoreEntity
+ * @public
+ * @param {string} serializedObject
+ * @return {object} Entity Object
+ */
Usergrid.Client.prototype.restoreEntity = function(serializedObject) {
var data = JSON.parse(serializedObject);
options = {
@@ -292,6 +470,17 @@
var entity = new Usergrid.Entity(options);
return entity;
};
+ /*
+ * Main function for creating new collections - should be called directly.
+ *
+ * options object: options {client:client, type: type, qs:qs}
+ *
+ * @method createCollection
+ * @public
+ * @params {object} options
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Client.prototype.createCollection = function(options, callback) {
options.client = this;
var collection = new Usergrid.Collection(options, function(err, data) {
@@ -300,12 +489,31 @@
}
});
};
+ /*
+ * Main function for restoring a collection from serialized data.
+ *
+ * serializedObject should have come from collectionObject.serialize();
+ *
+ * @method restoreCollection
+ * @public
+ * @param {string} serializedObject
+ * @return {object} Collection Object
+ */
Usergrid.Client.prototype.restoreCollection = function(serializedObject) {
var data = JSON.parse(serializedObject);
data.client = this;
var collection = new Usergrid.Collection(data);
return collection;
};
+ /*
+ * Main function for retrieving a user's activity feed.
+ *
+ * @method getFeedForUser
+ * @public
+ * @params {string} username
+ * @param {function} callback
+ * @return {callback} callback(err, data, activities)
+ */
Usergrid.Client.prototype.getFeedForUser = function(username, callback) {
var options = {
method: "GET",
@@ -321,6 +529,41 @@
}
});
};
+ /*
+ * Function for creating new activities for the current user - should be called directly.
+ *
+ * //user can be any of the following: "me", a uuid, a username
+ * Note: the "me" alias will reference the currently logged in user (e.g. 'users/me/activties')
+ *
+ * //build a json object that looks like this:
+ * var options =
+ * {
+ * "actor" : {
+ * "displayName" :"myusername",
+ * "uuid" : "myuserid",
+ * "username" : "myusername",
+ * "email" : "myemail",
+ * "picture": "http://path/to/picture",
+ * "image" : {
+ * "duration" : 0,
+ * "height" : 80,
+ * "url" : "http://www.gravatar.com/avatar/",
+ * "width" : 80
+ * },
+ * },
+ * "verb" : "post",
+ * "content" : "My cool message",
+ * "lat" : 48.856614,
+ * "lon" : 2.352222
+ * }
+ *
+ * @method createEntity
+ * @public
+ * @params {string} user // "me", a uuid, or a username
+ * @params {object} options
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Client.prototype.createUserActivity = function(user, options, callback) {
options.type = "users/" + user + "/activities";
options = {
@@ -334,6 +577,19 @@
}
});
};
+ /*
+ * Function for creating user activities with an associated user entity.
+ *
+ * user object:
+ * The user object passed into this function is an instance of Usergrid.Entity.
+ *
+ * @method createUserActivityWithEntity
+ * @public
+ * @params {object} user
+ * @params {string} content
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Client.prototype.createUserActivityWithEntity = function(user, content, callback) {
var username = user.get("username");
var options = {
@@ -355,6 +611,9 @@
};
this.createUserActivity(username, options, callback);
};
+ /*
+ * A private method to get call timing of last call
+ */
Usergrid.Client.prototype.calcTimeDiff = function() {
var seconds = 0;
var time = this._end - this._start;
@@ -365,9 +624,24 @@
}
return seconds;
};
+ /*
+ * A public method to store the OAuth token for later use - uses localstorage if available
+ *
+ * @method setToken
+ * @public
+ * @params {string} token
+ * @return none
+ */
Usergrid.Client.prototype.setToken = function(token) {
this.set("token", token);
};
+ /*
+ * A public method to get the OAuth token
+ *
+ * @method getToken
+ * @public
+ * @return {string} token
+ */
Usergrid.Client.prototype.getToken = function() {
return this.get("token");
};
@@ -400,6 +674,18 @@
}
return null;
};
+ /*
+ * A public facing helper method for signing up users
+ *
+ * @method signup
+ * @public
+ * @params {string} username
+ * @params {string} password
+ * @params {string} email
+ * @params {string} name
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Client.prototype.signup = function(username, password, email, name, callback) {
var self = this;
var options = {
@@ -411,6 +697,17 @@
};
this.createEntity(options, callback);
};
+ /*
+ *
+ * A public method to log in an app user - stores the token for later use
+ *
+ * @method login
+ * @public
+ * @params {string} username
+ * @params {string} password
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Client.prototype.login = function(username, password, callback) {
var self = this;
var options = {
@@ -450,6 +747,7 @@
if (err && self.logging) {
console.log("error trying to re-authenticate user");
} else {
+ //save the re-authed token and current email/username
self.setToken(response.access_token);
}
if (typeof callback === "function") {
@@ -475,9 +773,11 @@
data = response.data;
self.setToken(data.token);
self.set("email", data.email);
+ //delete next block and corresponding function when iframes are refactored
localStorage.setItem("accessToken", data.token);
localStorage.setItem("userUUID", data.uuid);
localStorage.setItem("userEmail", data.email);
+ //end delete block
var userData = {
username: data.username,
email: data.email,
@@ -492,6 +792,7 @@
organizations = data.organizations;
var org = "";
try {
+ //if we have an org stored, then use that one. Otherwise, use the first one.
var existingOrg = self.get("orgName");
org = organizations[existingOrg] ? organizations[existingOrg] : organizations[Object.keys(organizations)[0]];
self.set("orgName", org.name);
@@ -501,6 +802,7 @@
console.log("error selecting org");
}
}
+ //should always be an org
applications = self.parseApplicationsArray(org);
self.selectFirstApp(applications);
self.setObject("organizations", organizations);
@@ -511,6 +813,16 @@
}
});
};
+ /*
+ * A public method to log in an app user with facebook - stores the token for later use
+ *
+ * @method loginFacebook
+ * @public
+ * @params {string} username
+ * @params {string} password
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Client.prototype.loginFacebook = function(facebookToken, callback) {
var self = this;
var options = {
@@ -537,6 +849,14 @@
}
});
};
+ /*
+ * A public method to get the currently logged in user entity
+ *
+ * @method getLoggedInUser
+ * @public
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Client.prototype.getLoggedInUser = function(callback) {
if (!this.getToken()) {
callback(true, null, null);
@@ -567,20 +887,44 @@
});
}
};
+ /*
+ * A public method to test if a user is logged in - does not guarantee that the token is still valid,
+ * but rather that one exists
+ *
+ * @method isLoggedIn
+ * @public
+ * @return {boolean} Returns true the user is logged in (has token and uuid), false if not
+ */
Usergrid.Client.prototype.isLoggedIn = function() {
if (this.getToken() && this.getToken() != "null") {
return true;
}
return false;
};
+ /*
+ * A public method to log out an app user - clears all user fields from client
+ *
+ * @method logout
+ * @public
+ * @return none
+ */
Usergrid.Client.prototype.logout = function() {
this.setToken(null);
};
+ /*
+ * A private method to build the curl call to display on the command line
+ *
+ * @method buildCurlCall
+ * @private
+ * @param {object} options
+ * @return {string} curl
+ */
Usergrid.Client.prototype.buildCurlCall = function(options) {
var curl = "curl";
var method = (options.method || "GET").toUpperCase();
var body = options.body || {};
var uri = options.uri;
+ //curl - add the method to the command (no need to add anything for GET)
if (method === "POST") {
curl += " -X POST";
} else if (method === "PUT") {
@@ -590,13 +934,18 @@
} else {
curl += " -X GET";
}
+ //curl - append the path
curl += " " + uri;
+ //curl - add the body
if ("undefined" !== typeof window) {
body = JSON.stringify(body);
}
+ //only in node module
if (body !== '"{}"' && method !== "GET" && method !== "DELETE") {
+ //curl - add in the json obj
curl += " -d '" + body + "'";
}
+ //log the curl command to the console
console.log(curl);
return curl;
};
@@ -615,15 +964,38 @@
return "https://apigee.com/usergrid/images/user_profile.png";
}
};
+ /*
+ * A class to Model a Usergrid Entity.
+ * Set the type and uuid of entity in the 'data' json object
+ *
+ * @constructor
+ * @param {object} options {client:client, data:{'type':'collection_type', uuid:'uuid', 'key':'value'}}
+ */
Usergrid.Entity = function(options) {
if (options) {
this._data = options.data || {};
this._client = options.client || {};
}
};
+ /*
+ * returns a serialized version of the entity object
+ *
+ * Note: use the client.restoreEntity() function to restore
+ *
+ * @method serialize
+ * @return {string} data
+ */
Usergrid.Entity.prototype.serialize = function() {
return JSON.stringify(this._data);
};
+ /*
+ * gets a specific field or the entire data object. If null or no argument
+ * passed, will return all data, else, will return a specific field
+ *
+ * @method get
+ * @param {string} field
+ * @return {string} || {object} data
+ */
Usergrid.Entity.prototype.get = function(field) {
if (field) {
return this._data[field];
@@ -631,6 +1003,16 @@
return this._data;
}
};
+ /*
+ * adds a specific key value pair or object to the Entity's data
+ * is additive - will not overwrite existing values unless they
+ * are explicitly specified
+ *
+ * @method set
+ * @param {string} key || {object}
+ * @param {string} value
+ * @return none
+ */
Usergrid.Entity.prototype.set = function(key, value) {
if (typeof key === "object") {
for (var field in key) {
@@ -646,6 +1028,14 @@
this._data = {};
}
};
+ /*
+ * Saves the entity back to the database
+ *
+ * @method save
+ * @public
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Entity.prototype.save = function(callback) {
var type = this.get("type");
var method = "POST";
@@ -653,9 +1043,11 @@
method = "PUT";
type += "/" + this.get("uuid");
}
+ //update the entity
var self = this;
var data = {};
var entityData = this.get();
+ //remove system specific properties
for (var item in entityData) {
if (item === "metadata" || item === "created" || item === "modified" || item === "type" || item === "activated" || item === "uuid") {
continue;
@@ -667,6 +1059,7 @@
endpoint: type,
body: data
};
+ //save the entity first
this._client.request(options, function(err, retdata) {
if (err && self._client.logging) {
console.log("could not save entity");
@@ -679,14 +1072,18 @@
var entity = retdata.entities[0];
self.set(entity);
var path = retdata.path;
+ //for connections, API returns type
while (path.substring(0, 1) === "/") {
path = path.substring(1);
}
self.set("type", path);
}
}
+ //if this is a user, update the password if it has been specified;
var needPasswordChange = (self.get("type") === "user" || self.get("type") === "users") && entityData.oldpassword && entityData.newpassword;
if (needPasswordChange) {
+ //Note: we have a ticket in to change PUT calls to /users to accept the password change
+ // once that is done, we will remove this call and merge it all into one
var pwdata = {};
pwdata.oldpassword = entityData.oldpassword;
pwdata.newpassword = entityData.newpassword;
@@ -699,6 +1096,7 @@
if (err && self._client.logging) {
console.log("could not update user");
}
+ //remove old and new password fields so they don't end up as part of the entity object
self.set("oldpassword", null);
self.set("newpassword", null);
if (typeof callback === "function") {
@@ -711,9 +1109,18 @@
}
});
};
+ /*
+ * refreshes the entity by making a GET call back to the database
+ *
+ * @method fetch
+ * @public
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Entity.prototype.fetch = function(callback) {
var type = this.get("type");
var self = this;
+ //Check for an entity type, then if a uuid is available, use that, otherwise, use the name
try {
if (type === undefined) {
throw "cannot fetch entity, no entity type specified";
@@ -757,6 +1164,16 @@
}
});
};
+ /*
+ * deletes the entity from the database - will only delete
+ * if the object has a valid uuid
+ *
+ * @method destroy
+ * @public
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ *
+ */
Usergrid.Entity.prototype.destroy = function(callback) {
var self = this;
var type = this.get("type");
@@ -786,9 +1203,21 @@
}
});
};
+ /*
+ * connects one entity to another
+ *
+ * @method connect
+ * @public
+ * @param {string} connection
+ * @param {object} entity
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ *
+ */
Usergrid.Entity.prototype.connect = function(connection, entity, callback) {
var self = this;
var error;
+ //connectee info
var connecteeType = entity.get("type");
var connectee = this.getEntityId(entity);
if (!connectee) {
@@ -801,6 +1230,7 @@
}
return;
}
+ //connector info
var connectorType = this.get("type");
var connector = this.getEntityId(this);
if (!connector) {
@@ -827,6 +1257,16 @@
}
});
};
+ /*
+ * returns a unique identifier for an entity
+ *
+ * @method connect
+ * @public
+ * @param {object} entity
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ *
+ */
Usergrid.Entity.prototype.getEntityId = function(entity) {
var id = false;
if (isUUID(entity.get("uuid"))) {
@@ -840,8 +1280,20 @@
}
return id;
};
+ /*
+ * gets an entities connections
+ *
+ * @method getConnections
+ * @public
+ * @param {string} connection
+ * @param {object} entity
+ * @param {function} callback
+ * @return {callback} callback(err, data, connections)
+ *
+ */
Usergrid.Entity.prototype.getConnections = function(connection, callback) {
var self = this;
+ //connector info
var connectorType = this.get("type");
var connector = this.getEntityId(this);
if (!connector) {
@@ -1023,9 +1475,21 @@
}
});
};
+ /*
+ * disconnects one entity from another
+ *
+ * @method disconnect
+ * @public
+ * @param {string} connection
+ * @param {object} entity
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ *
+ */
Usergrid.Entity.prototype.disconnect = function(connection, entity, callback) {
var self = this;
var error;
+ //connectee info
var connecteeType = entity.get("type");
var connectee = this.getEntityId(entity);
if (!connectee) {
@@ -1038,6 +1502,7 @@
}
return;
}
+ //connector info
var connectorType = this.get("type");
var connector = this.getEntityId(this);
if (!connector) {
@@ -1064,29 +1529,52 @@
}
});
};
+ /*
+ * The Collection class models Usergrid Collections. It essentially
+ * acts as a container for holding Entity objects, while providing
+ * additional funcitonality such as paging, and saving
+ *
+ * @constructor
+ * @param {string} options - configuration object
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Collection = function(options, callback) {
if (options) {
this._client = options.client;
this._type = options.type;
this.qs = options.qs || {};
+ //iteration
this._list = options.list || [];
this._iterator = options.iterator || -1;
+ //first thing we do is increment, so set to -1
+ //paging
this._previous = options.previous || [];
this._next = options.next || null;
this._cursor = options.cursor || null;
+ //restore entities if available
if (options.list) {
var count = options.list.length;
for (var i = 0; i < count; i++) {
+ //make new entity with
var entity = this._client.restoreEntity(options.list[i]);
this._list[i] = entity;
}
}
}
if (callback) {
+ //populate the collection
this.fetch(callback);
}
};
+ /*
+ * gets the data from the collection object for serialization
+ *
+ * @method serialize
+ * @return {object} data
+ */
Usergrid.Collection.prototype.serialize = function() {
+ //pull out the state from this object and return it
var data = {};
data.type = this._type;
data.qs = this.qs;
@@ -1122,9 +1610,17 @@
}
});
};
+ /*
+ * Populates the collection from the server
+ *
+ * @method fetch
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Collection.prototype.fetch = function(callback) {
var self = this;
var qs = this.qs;
+ //add in the cursor if one is available
if (this._cursor) {
qs.cursor = this._cursor;
} else {
@@ -1139,18 +1635,23 @@
if (err && self._client.logging) {
console.log("error getting collection");
} else {
+ //save the cursor if there is one
var cursor = data.cursor || null;
self.saveCursor(cursor);
if (data.entities) {
self.resetEntityPointer();
var count = data.entities.length;
+ //save entities locally
self._list = [];
+ //clear the local list first
for (var i = 0; i < count; i++) {
var uuid = data.entities[i].uuid;
if (uuid) {
var entityData = data.entities[i] || {};
self._baseType = data.entities[i].type;
+ //store the base type in the collection
entityData.type = self._type;
+ //make sure entities are same type (have same path) as parent collection.
var entityOptions = {
type: self._type,
client: self._client,
@@ -1170,11 +1671,21 @@
}
});
};
+ /*
+ * Adds a new Entity to the collection (saves, then adds to the local object)
+ *
+ * @method addNewEntity
+ * @param {object} entity
+ * @param {function} callback
+ * @return {callback} callback(err, data, entity)
+ */
Usergrid.Collection.prototype.addEntity = function(options, callback) {
var self = this;
options.type = this._type;
+ //create the new entity
this._client.createEntity(options, function(err, entity) {
if (!err) {
+ //then add the entity to the list
var count = self._list.length;
self._list[count] = entity;
}
@@ -1184,9 +1695,18 @@
});
};
Usergrid.Collection.prototype.addExistingEntity = function(entity) {
+ //entity should already exist in the db, so just add it to the list
var count = this._list.length;
this._list[count] = entity;
};
+ /*
+ * Removes the Entity from the collection, then destroys the object on the server
+ *
+ * @method destroyEntity
+ * @param {object} entity
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Collection.prototype.destroyEntity = function(entity, callback) {
var self = this;
entity.destroy(function(err, data) {
@@ -1198,9 +1718,11 @@
callback(err, data);
}
} else {
+ //destroy was good, so repopulate the collection
self.fetch(callback);
}
});
+ //remove entity from the local store
this.removeEntity(entity);
};
Usergrid.Collection.prototype.removeEntity = function(entity) {
@@ -1213,6 +1735,14 @@
}
return false;
};
+ /*
+ * Looks up an Entity by UUID
+ *
+ * @method getEntityByUUID
+ * @param {string} UUID
+ * @param {function} callback
+ * @return {callback} callback(err, data, entity)
+ */
Usergrid.Collection.prototype.getEntityByUUID = function(uuid, callback) {
for (var key in this._list) {
var listItem = this._list[key];
@@ -1220,6 +1750,7 @@
return listItem;
}
}
+ //get the entity from the database
var options = {
data: {
type: this._type,
@@ -1230,6 +1761,12 @@
var entity = new Usergrid.Entity(options);
entity.fetch(callback);
};
+ /*
+ * Returns the first Entity of the Entity list - does not affect the iterator
+ *
+ * @method getFirstEntity
+ * @return {object} returns an entity object
+ */
Usergrid.Collection.prototype.getFirstEntity = function() {
var count = this._list.length;
if (count > 0) {
@@ -1237,6 +1774,12 @@
}
return null;
};
+ /*
+ * Returns the last Entity of the Entity list - does not affect the iterator
+ *
+ * @method getLastEntity
+ * @return {object} returns an entity object
+ */
Usergrid.Collection.prototype.getLastEntity = function() {
var count = this._list.length;
if (count > 0) {
@@ -1244,6 +1787,15 @@
}
return null;
};
+ /*
+ * Entity iteration -Checks to see if there is a "next" entity
+ * in the list. The first time this method is called on an entity
+ * list, or after the resetEntityPointer method is called, it will
+ * return true referencing the first entity in the list
+ *
+ * @method hasNextEntity
+ * @return {boolean} true if there is a next entity, false if not
+ */
Usergrid.Collection.prototype.hasNextEntity = function() {
var next = this._iterator + 1;
var hasNextElement = next >= 0 && next < this._list.length;
@@ -1252,14 +1804,31 @@
}
return false;
};
+ /*
+ * Entity iteration - Gets the "next" entity in the list. The first
+ * time this method is called on an entity list, or after the method
+ * resetEntityPointer is called, it will return the,
+ * first entity in the list
+ *
+ * @method hasNextEntity
+ * @return {object} entity
+ */
Usergrid.Collection.prototype.getNextEntity = function() {
this._iterator++;
+ //Usergrid had < while others had <=
var hasNextElement = this._iterator >= 0 && this._iterator < this._list.length;
if (hasNextElement) {
return this._list[this._iterator];
}
return false;
};
+ /*
+ * Entity iteration - Checks to see if there is a "previous"
+ * entity in the list.
+ *
+ * @method hasPrevEntity
+ * @return {boolean} true if there is a previous entity, false if not
+ */
Usergrid.Collection.prototype.hasPrevEntity = function() {
var previous = this._iterator - 1;
var hasPreviousElement = previous >= 0 && previous < this._list.length;
@@ -1268,6 +1837,12 @@
}
return false;
};
+ /*
+ * Entity iteration - Gets the "previous" entity in the list.
+ *
+ * @method getPrevEntity
+ * @return {object} entity
+ */
Usergrid.Collection.prototype.getPrevEntity = function() {
this._iterator--;
var hasPreviousElement = this._iterator >= 0 && this._iterator <= this._list.length;
@@ -1276,45 +1851,108 @@
}
return false;
};
+ /*
+ * Entity iteration - Resets the iterator back to the beginning
+ * of the list
+ *
+ * @method resetEntityPointer
+ * @return none
+ */
Usergrid.Collection.prototype.resetEntityPointer = function() {
this._iterator = -1;
};
+ /*
+ * Method to save off the cursor just returned by the last API call
+ *
+ * @public
+ * @method saveCursor
+ * @return none
+ */
Usergrid.Collection.prototype.saveCursor = function(cursor) {
+ //if current cursor is different, grab it for next cursor
if (this._next !== cursor) {
this._next = cursor;
}
};
+ /*
+ * Resets the paging pointer (back to original page)
+ *
+ * @public
+ * @method resetPaging
+ * @return none
+ */
Usergrid.Collection.prototype.resetPaging = function() {
this._previous = [];
this._next = null;
this._cursor = null;
};
+ /*
+ * Paging - checks to see if there is a next page od data
+ *
+ * @method hasNextPage
+ * @return {boolean} returns true if there is a next page of data, false otherwise
+ */
Usergrid.Collection.prototype.hasNextPage = function() {
return this._next;
};
+ /*
+ * Paging - advances the cursor and gets the next
+ * page of data from the API. Stores returned entities
+ * in the Entity list.
+ *
+ * @method getNextPage
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Collection.prototype.getNextPage = function(callback) {
if (this.hasNextPage()) {
+ //set the cursor to the next page of data
this._previous.push(this._cursor);
this._cursor = this._next;
+ //empty the list
this._list = [];
this.fetch(callback);
} else {
callback(true);
}
};
+ /*
+ * Paging - checks to see if there is a previous page od data
+ *
+ * @method hasPreviousPage
+ * @return {boolean} returns true if there is a previous page of data, false otherwise
+ */
Usergrid.Collection.prototype.hasPreviousPage = function() {
return this._previous.length > 0;
};
+ /*
+ * Paging - reverts the cursor and gets the previous
+ * page of data from the API. Stores returned entities
+ * in the Entity list.
+ *
+ * @method getPreviousPage
+ * @param {function} callback
+ * @return {callback} callback(err, data)
+ */
Usergrid.Collection.prototype.getPreviousPage = function(callback) {
if (this.hasPreviousPage()) {
this._next = null;
+ //clear out next so the comparison will find the next item
this._cursor = this._previous.pop();
+ //empty the list
this._list = [];
this.fetch(callback);
} else {
callback(true);
}
};
+ /*
+ * A class to model a Usergrid group.
+ * Set the path in the options object.
+ *
+ * @constructor
+ * @param {object} options {client:client, data: {'key': 'value'}, path:'path'}
+ */
Usergrid.Group = function(options, callback) {
this._path = options.path;
this._list = [];
@@ -1322,7 +1960,20 @@
this._data = options.data || {};
this._data.type = "groups";
};
+ /*
+ * Inherit from Usergrid.Entity.
+ * Note: This only accounts for data on the group object itself.
+ * You need to use add and remove to manipulate group membership.
+ */
Usergrid.Group.prototype = new Usergrid.Entity();
+ /*
+ * Fetches current group data, and members.
+ *
+ * @method fetch
+ * @public
+ * @param {function} callback
+ * @returns {function} callback(err, data)
+ */
Usergrid.Group.prototype.fetch = function(callback) {
var self = this;
var groupEndpoint = "groups/" + this._path;
@@ -1378,11 +2029,30 @@
}
});
};
+ /*
+ * Retrieves the members of a group.
+ *
+ * @method members
+ * @public
+ * @param {function} callback
+ * @return {function} callback(err, data);
+ */
Usergrid.Group.prototype.members = function(callback) {
if (typeof callback === "function") {
callback(null, this._list);
}
};
+ /*
+ * Adds a user to the group, and refreshes the group object.
+ *
+ * Options object: {user: user_entity}
+ *
+ * @method add
+ * @public
+ * @params {object} options
+ * @param {function} callback
+ * @return {function} callback(err, data)
+ */
Usergrid.Group.prototype.add = function(options, callback) {
var self = this;
options = {
@@ -1399,6 +2069,17 @@
}
});
};
+ /*
+ * Removes a user from a group, and refreshes the group object.
+ *
+ * Options object: {user: user_entity}
+ *
+ * @method remove
+ * @public
+ * @params {object} options
+ * @param {function} callback
+ * @return {function} callback(err, data)
+ */
Usergrid.Group.prototype.remove = function(options, callback) {
var self = this;
options = {
@@ -1415,6 +2096,14 @@
}
});
};
+ /*
+ * Gets feed for a group.
+ *
+ * @public
+ * @method feed
+ * @param {function} callback
+ * @returns {callback} callback(err, data, activities)
+ */
Usergrid.Group.prototype.feed = function(callback) {
var self = this;
var endpoint = "groups/" + this._path + "/feed";
@@ -1431,6 +2120,17 @@
}
});
};
+ /*
+ * Creates activity and posts to group feed.
+ *
+ * options object: {user: user_entity, content: "activity content"}
+ *
+ * @public
+ * @method createGroupActivity
+ * @params {object} options
+ * @param {function} callback
+ * @returns {callback} callback(err, entity)
+ */
Usergrid.Group.prototype.createGroupActivity = function(options, callback) {
var user = options.user;
options = {
@@ -1461,6 +2161,14 @@
}
});
};
+ /*
+ * Tests if the string is a uuid
+ *
+ * @public
+ * @method isUUID
+ * @param {string} uuid The string to test
+ * @returns {Boolean} true if string is uuid
+ */
function isUUID(uuid) {
var uuidValueRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
if (!uuid) {
@@ -1468,6 +2176,14 @@
}
return uuidValueRegex.test(uuid);
}
+ /*
+ * method to encode the query string parameters
+ *
+ * @method encodeParams
+ * @public
+ * @params {object} params - an object of name value pairs that will be urlencoded
+ * @return {string} Returns the encoded string
+ */
function encodeParams(params) {
var tail = [];
var item = [];
@@ -1521,21 +2237,69 @@
if (!Usergrid) {
throw "Usergrid module is required for the monitoring module.";
}
+ /*
+ * Logs a user defined verbose message.
+ *
+ * @method logDebug
+ * @public
+ * @param {object} options
+ *
+ */
Usergrid.client.prototype.logVerbose = function(options) {
this.monitor.logVerbose(options);
};
+ /*
+ * Logs a user defined debug message.
+ *
+ * @method logDebug
+ * @public
+ * @param {object} options
+ *
+ */
Usergrid.client.prototype.logDebug = function(options) {
this.monitor.logDebug(options);
};
+ /*
+ * Logs a user defined informational message.
+ *
+ * @method logInfo
+ * @public
+ * @param {object} options
+ *
+ */
Usergrid.client.prototype.logInfo = function(options) {
this.monitor.logInfo(options);
};
+ /*
+ * Logs a user defined warning message.
+ *
+ * @method logWarn
+ * @public
+ * @param {object} options
+ *
+ */
Usergrid.client.prototype.logWarn = function(options) {
this.monitor.logWarn(options);
};
+ /*
+ * Logs a user defined error message.
+ *
+ * @method logError
+ * @public
+ * @param {object} options
+ *
+ */
Usergrid.client.prototype.logError = function(options) {
this.monitor.logError(options);
};
+ /*
+ * Logs a user defined assert message.
+ *
+ * @method logAssert
+ * @public
+ * @param {object} options
+ *
+ */
Usergrid.client.prototype.logAssert = function(options) {
this.monitor.logAssert(options);
};
@@ -1589,11 +2353,16 @@
};
var UNKNOWN = "UNKNOWN";
var SDKTYPE = "JavaScript";
+ //Work around hack because onerror is always called in the window context so we can't store crashes internally
+ //This isn't too bad because we are encapsulated.
var logs = [];
var metrics = [];
var Apigee = Usergrid;
Apigee.prototype = Usergrid.prototype;
+ //Apigee.constructor=Apigee;
+ //function Apigee() {};
Apigee.Client = function(options) {
+ //Init app monitoring.
this.monitoringEnabled = options.monitoringEnabled || true;
if (this.monitoringEnabled) {
try {
@@ -1605,14 +2374,22 @@
Usergrid.client.call(this, options);
};
Apigee.Client.prototype = Usergrid.client.prototype;
+ //Apigee.Client.constructor=Apigee.Client;
+ //BEGIN APIGEE MONITORING SDK
+ //Constructor for Apigee Monitoring SDK
Apigee.MonitoringClient = function(options) {
+ //Needed for the setInterval call for syncing. Have to pass in a ref to ourselves. It blows scope away.
var self = this;
this.orgName = options.orgName;
this.appName = options.appName;
this.syncOnClose = options.syncOnClose || false;
+ //Put this in here because I don't want sync issues with testing.
this.testMode = options.testMode || false;
+ //You best know what you're doing if you're setting this for Apigee monitoring!
this.URI = typeof options.URI === "undefined" ? "https://api.usergrid.com" : options.URI;
this.syncDate = timeStamp();
+ //Can do a manual config override specifiying raw json as your config. I use this for testing.
+ //May be useful down the road. Needs to conform to current config.
if (typeof options.config !== "undefined") {
this.configuration = options.config;
if (this.configuration.deviceLevelOverrideEnabled === true) {
@@ -1626,14 +2403,18 @@
this.configuration = null;
this.downloadConfig();
}
+ //Don't do anything if configuration wasn't loaded.
if (this.configuration !== null && this.configuration !== "undefined") {
+ //Ensure that we want to sample data from this device.
var sampleSeed = 0;
if (this.deviceConfig.samplingRate < 100) {
sampleSeed = Math.floor(Math.random() * 101);
}
+ //If we're not in the sampling window don't setup data collection at all
if (sampleSeed < this.deviceConfig.samplingRate) {
this.appId = this.configuration.instaOpsApplicationId;
this.appConfigType = this.deviceConfig.appConfigType;
+ //Let's monkeypatch logging calls to intercept and send to server.
if (this.deviceConfig.enableLogMonitoring) {
this.patchLoggingCalls();
}
@@ -1641,7 +2422,9 @@
if (typeof this.deviceConfig.agentUploadIntervalInSeconds !== "undefined") {
syncIntervalMillis = this.deviceConfig.agentUploadIntervalInSeconds * 1e3;
}
+ //Needed for the setInterval call for syncing. Have to pass in a ref to ourselves. It blows scope away.
if (!this.syncOnClose) {
+ //Old server syncing logic
setInterval(function() {
self.prepareSync();
}, syncIntervalMillis);
@@ -1661,6 +2444,7 @@
});
}
}
+ //Setting up the catching of errors and network calls
if (this.deviceConfig.networkMonitoringEnabled) {
this.patchNetworkCalls(XMLHttpRequest);
}
@@ -1674,13 +2458,24 @@
};
Apigee.MonitoringClient.prototype.applyMonkeyPatches = function() {
var self = this;
+ //Let's monkeypatch logging calls to intercept and send to server.
if (self.deviceConfig.enableLogMonitoring) {
self.patchLoggingCalls();
}
+ //Setting up the catching of errors and network calls
if (self.deviceConfig.networkMonitoringEnabled) {
self.patchNetworkCalls(XMLHttpRequest);
}
};
+ /**
+ * Function for retrieving the current Apigee Monitoring configuration.
+ *
+ * @method downloadConfig
+ * @public
+ * @params {function} callback
+ * NOTE: Passing in a callback makes this call async. Wires it all up for you.
+ *
+ */
Apigee.MonitoringClient.prototype.getConfig = function(options, callback) {
if (typeof options.config !== "undefined") {
this.configuration = options.config;
@@ -1697,9 +2492,19 @@
this.downloadConfig(callback);
}
};
+ /**
+ * Function for downloading the current Apigee Monitoring configuration.
+ *
+ * @method downloadConfig
+ * @public
+ * @params {function} callback
+ * NOTE: Passing in a callback makes this call async. Wires it all up for you.
+ *
+ */
Apigee.MonitoringClient.prototype.downloadConfig = function(callback) {
var configRequest = new XMLHttpRequest();
var path = this.URI + "/" + this.orgName + "/" + this.appName + "/apm/apigeeMobileConfig";
+ //If we have a function lets load the config async else do it sync.
if (typeof callback === "function") {
configRequest.open(VERBS.get, path, true);
} else {
@@ -1710,6 +2515,8 @@
configRequest.setRequestHeader("Content-Type", "application/json");
configRequest.onreadystatechange = onReadyStateChange;
configRequest.send();
+ //A little async magic. Let's return the AJAX issue from downloading the configs.
+ //Or we can return the parsed out config.
function onReadyStateChange() {
if (configRequest.readyState === 4) {
if (typeof callback === "function") {
@@ -1734,7 +2541,16 @@
}
}
};
+ /**
+ * Function for syncing data back to the server. Currently called in the Apigee.MonitoringClient constructor using setInterval.
+ *
+ * @method sync
+ * @public
+ * @params {object} syncObject
+ *
+ */
Apigee.MonitoringClient.prototype.sync = function(syncObject) {
+ //Sterilize the sync data
var syncData = {};
syncData.logs = syncObject.logs;
syncData.metrics = syncObject.metrics;
@@ -1744,21 +2560,34 @@
syncData.fullAppName = this.orgName + "_" + this.appName;
syncData.instaOpsApplicationId = this.configuration.instaOpsApplicationId;
syncData.timeStamp = timeStamp();
+ //Send it to the apmMetrics endpoint.
var syncRequest = new XMLHttpRequest();
var path = this.URI + "/" + this.orgName + "/" + this.appName + "/apm/apmMetrics";
syncRequest.open(VERBS.post, path, false);
syncRequest.setRequestHeader("Accept", "application/json");
syncRequest.setRequestHeader("Content-Type", "application/json");
syncRequest.send(JSON.stringify(syncData));
+ //Only wipe data if the sync was good. Hold onto it if it was bad.
if (syncRequest.status === 200) {
logs = [];
metrics = [];
var response = syncRequest.responseText;
} else {
+ //Not much we can do if there was an error syncing data.
+ //Log it to console accordingly.
console.log("Error syncing");
console.log(syncRequest.responseText);
}
};
+ /**
+ * Function that is called during the window.onerror handler. Grabs all parameters sent by that function.
+ *
+ * @public
+ * @param {string} crashEvent
+ * @param {string} url
+ * @param {string} line
+ *
+ */
Apigee.MonitoringClient.catchCrashReport = function(crashEvent, url, line) {
logCrash({
tag: "CRASH",
@@ -1781,8 +2610,11 @@
Apigee.MonitoringClient.prototype.detectAppPlatform = function(sessionSummary) {
var self = this;
var callbackHandler_Titanium = function(e) {
+ //Framework is appcelerator
sessionSummary.devicePlatform = e.name;
sessionSummary.deviceOSVersion = e.osname;
+ //Get the device id if we want it. If we dont, but we want it obfuscated generate
+ //a one off id and attach it to localStorage.
if (self.deviceConfig.deviceIdCaptureEnabled) {
if (self.deviceConfig.obfuscateDeviceId) {
sessionSummary.deviceId = generateDeviceId();
@@ -1812,6 +2644,8 @@
if ("connection" in navigator) {
sessionSummary.networkType = navigator.connection.type || UNKNOWN;
}
+ //Get the device id if we want it. If we dont, but we want it obfuscated generate
+ //a one off id and attach it to localStorage.
if (self.deviceConfig.deviceIdCaptureEnabled) {
if (self.deviceConfig.obfuscateDeviceId) {
sessionSummary.deviceId = generateDeviceId();
@@ -1836,6 +2670,7 @@
}
sessionSummary.devicePlatform = UNKNOWN;
sessionSummary.deviceOSVersion = os;
+ //Get the device id if we want it. Trigger.io doesn't expose device id APIs
if (self.deviceConfig.deviceIdCaptureEnabled) {
sessionSummary.deviceId = generateDeviceId();
} else {
@@ -1845,19 +2680,28 @@
sessionSummary.networkType = forge.is.connection.wifi() ? "WIFI" : UNKNOWN;
return sessionSummary;
};
+ //We're checking if it's a phonegap app.
+ //If so let's use APIs exposed by phonegap to collect device info.
+ //If not let's fallback onto stuff we should collect ourselves.
if (isPhoneGap()) {
+ //framework is phonegap.
sessionSummary = callbackHandler_PhoneGap(sessionSummary);
} else if (isTrigger()) {
+ //Framework is trigger
sessionSummary = callbackHandler_Trigger(sessionSummary);
} else if (isTitanium()) {
Ti.App.addEventListener("analytics:platformMetrics", callbackHandler_Titanium);
} else {
+ //Can't detect framework assume browser.
+ //Here we want to check for localstorage and make sure the browser has it
if (typeof window.localStorage !== "undefined") {
+ //If no uuid is set in localstorage create a new one, and set it as the session's deviceId
if (self.deviceConfig.deviceIdCaptureEnabled) {
sessionSummary.deviceId = generateDeviceId();
}
}
if (typeof navigator.userAgent !== "undefined") {
+ //Small hack to make all device names consistent.
var browserData = determineBrowserType(navigator.userAgent, navigator.appName);
sessionSummary.devicePlatform = browserData.devicePlatform;
sessionSummary.deviceOSVersion = browserData.deviceOSVersion;
@@ -1871,13 +2715,23 @@
}
return sessionSummary;
};
+ /**
+ * Registers a device with Apigee Monitoring. Generates a new UUID for a device and collects relevant info on it.
+ *
+ * @method registerDevice
+ * @public
+ *
+ */
Apigee.MonitoringClient.prototype.startSession = function() {
if (this.configuration === null || this.configuration === "undefined") {
return;
}
+ //If the user agent string exists on the device
var self = this;
var sessionSummary = {};
+ //timeStamp goes first because it is used in other properties
sessionSummary.timeStamp = timeStamp();
+ //defaults for other properties
sessionSummary.appConfigType = this.appConfigType;
sessionSummary.appId = this.appId.toString();
sessionSummary.applicationVersion = "undefined" !== typeof this.appVersion ? this.appVersion.toString() : UNKNOWN;
@@ -1899,6 +2753,13 @@
self.startLocationCapture();
self.sessionMetrics = self.detectAppPlatform(sessionSummary);
};
+ /**
+ * Method to encapsulate the monkey patching of AJAX methods. We pass in the XMLHttpRequest object for monkey patching.
+ *
+ * @public
+ * @param {XMLHttpRequest} XHR
+ *
+ */
Apigee.MonitoringClient.prototype.patchNetworkCalls = function(XHR) {
"use strict";
var apigee = this;
@@ -1916,7 +2777,10 @@
var method = this._method;
var url = this._url;
function onReadyStateChange() {
- if (self.readyState == 4) {
+ if (self.readyState == 4) // complete
+ {
+ //gap_exec and any other platform specific filtering here
+ //gap_exec is used internally by phonegap, and shouldn't be logged.
var monitoringURL = apigee.getMonitoringURL();
if (url.indexOf("/!gap_exec") === -1 && url.indexOf(monitoringURL) === -1) {
var endTime = timeStamp();
@@ -1932,9 +2796,11 @@
responseDataSize: self.responseText.length.toString()
};
if (self.status == 200) {
+ //Record the http call here
summary.numErrors = "0";
apigee.logNetworkCall(summary);
} else {
+ //Record a connection failure here
summary.numErrors = "1";
apigee.logNetworkCall(summary);
}
@@ -1957,6 +2823,8 @@
};
};
Apigee.MonitoringClient.prototype.patchLoggingCalls = function() {
+ //Hacky way of tapping into this and switching it around but it'll do.
+ //We assume that the first argument is the intended log message. Except assert which is the second message.
var self = this;
var original = window.console;
window.console = {
@@ -1997,6 +2865,7 @@
}
};
if (isTitanium()) {
+ //Patch console.log to work in Titanium as well.
var originalTitanium = Ti.API;
window.console.log = function() {
originalTitanium.info.apply(originalTitanium, arguments);
@@ -2047,14 +2916,24 @@
};
}
};
+ /**
+ * Prepares data for syncing on window close.
+ *
+ * @method prepareSync
+ * @public
+ *
+ */
Apigee.MonitoringClient.prototype.prepareSync = function() {
var syncObject = {};
var self = this;
+ //Just in case something bad happened.
if (typeof self.sessionMetrics !== "undefined") {
syncObject.sessionMetrics = self.sessionMetrics;
}
var syncFlag = false;
this.syncDate = timeStamp();
+ //Go through each of the aggregated metrics
+ //If there are unreported metrics present add them to the object to be sent across the network
if (metrics.length > 0) {
syncFlag = true;
}
@@ -2063,10 +2942,19 @@
}
syncObject.logs = logs;
syncObject.metrics = metrics;
+ //If there is data to sync go ahead and do it.
if (syncFlag && !self.testMode) {
this.sync(syncObject);
}
};
+ /**
+ * Logs a user defined message.
+ *
+ * @method logMessage
+ * @public
+ * @param {object} options
+ *
+ */
Apigee.MonitoringClient.prototype.logMessage = function(options) {
var log = options || {};
var cleansedLog = {
@@ -2077,6 +2965,14 @@
};
logs.push(cleansedLog);
};
+ /**
+ * Logs a user defined verbose message.
+ *
+ * @method logDebug
+ * @public
+ * @param {object} options
+ *
+ */
Apigee.MonitoringClient.prototype.logVerbose = function(options) {
var logOptions = options || {};
logOptions.logLevel = LOGLEVELS.verbose;
@@ -2084,6 +2980,14 @@
this.logMessage(options);
}
};
+ /**
+ * Logs a user defined debug message.
+ *
+ * @method logDebug
+ * @public
+ * @param {object} options
+ *
+ */
Apigee.MonitoringClient.prototype.logDebug = function(options) {
var logOptions = options || {};
logOptions.logLevel = LOGLEVELS.debug;
@@ -2091,6 +2995,14 @@
this.logMessage(options);
}
};
+ /**
+ * Logs a user defined informational message.
+ *
+ * @method logInfo
+ * @public
+ * @param {object} options
+ *
+ */
Apigee.MonitoringClient.prototype.logInfo = function(options) {
var logOptions = options || {};
logOptions.logLevel = LOGLEVELS.info;
@@ -2098,6 +3010,14 @@
this.logMessage(options);
}
};
+ /**
+ * Logs a user defined warning message.
+ *
+ * @method logWarn
+ * @public
+ * @param {object} options
+ *
+ */
Apigee.MonitoringClient.prototype.logWarn = function(options) {
var logOptions = options || {};
logOptions.logLevel = LOGLEVELS.warn;
@@ -2105,6 +3025,14 @@
this.logMessage(options);
}
};
+ /**
+ * Logs a user defined error message.
+ *
+ * @method logError
+ * @public
+ * @param {object} options
+ *
+ */
Apigee.MonitoringClient.prototype.logError = function(options) {
var logOptions = options || {};
logOptions.logLevel = LOGLEVELS.error;
@@ -2112,6 +3040,14 @@
this.logMessage(options);
}
};
+ /**
+ * Logs a user defined assert message.
+ *
+ * @method logAssert
+ * @public
+ * @param {object} options
+ *
+ */
Apigee.MonitoringClient.prototype.logAssert = function(options) {
var logOptions = options || {};
logOptions.logLevel = LOGLEVELS.assert;
@@ -2119,6 +3055,11 @@
this.logMessage(options);
}
};
+ /**
+ * Internal function for encapsulating crash log catches. Not directly callable.
+ * Needed because of funkiness with the errors being thrown solely on the window
+ *
+ */
function logCrash(options) {
var log = options || {};
var cleansedLog = {
@@ -2129,13 +3070,42 @@
};
logs.push(cleansedLog);
}
+ /**
+ * Logs a network call.
+ *
+ * @method logNetworkCall
+ * @public
+ * @param {object} options
+ *
+ */
Apigee.MonitoringClient.prototype.logNetworkCall = function(options) {
metrics.push(options);
};
+ /**
+ * Retrieves monitoring URL.
+ *
+ * @method getMonitoringURL
+ * @public
+ * @returns {string} value
+ *
+ */
Apigee.MonitoringClient.prototype.getMonitoringURL = function() {
return this.URI + "/" + this.orgName + "/" + this.appName + "/apm/";
};
+ /**
+ * Gets custom config parameters. These are set by user in dashboard.
+ *
+ * @method getConfig
+ * @public
+ * @param {string} key
+ * @returns {stirng} value
+ *
+ * TODO: Once there is a dashboard plugged into the API implement this so users can set
+ * custom configuration parameters for their applications.
+ */
Apigee.MonitoringClient.prototype.getConfig = function(key) {};
+ //TEST HELPERS NOT REALLY MEANT TO BE USED OUTSIDE THAT CONTEXT.
+ //Simply exposes some internal data that is collected.
Apigee.MonitoringClient.prototype.logs = function() {
return logs;
};
@@ -2155,22 +3125,48 @@
destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
}
};
+ //UUID Generation function unedited
+ /** randomUUID.js - Version 1.0
+ *
+ * Copyright 2008, Robert Kieffer
+ *
+ * This software is made available under the terms of the Open Software License
+ * v3.0 (available here: http://www.opensource.org/licenses/osl-3.0.php )
+ *
+ * The latest version of this file can be found at:
+ * http://www.broofa.com/Tools/randomUUID.js
+ *
+ * For more information, or to comment on this, please go to:
+ * http://www.broofa.com/blog/?p=151
+ */
+ /**
+ * Create and return a "version 4" RFC-4122 UUID string.
+ */
function randomUUID() {
var s = [], itoh = "0123456789ABCDEF", i;
+ // Make array of random hex digits. The UUID only has 32 digits in it, but we
+ // allocate an extra items to make room for the '-'s we'll be inserting.
for (i = 0; i < 36; i++) {
s[i] = Math.floor(Math.random() * 16);
}
+ // Conform to RFC-4122, section 4.4
s[14] = 4;
+ // Set 4 high bits of time_high field to version
s[19] = s[19] & 3 | 8;
+ // Specify 2 high bits of clock sequence
+ // Convert to hex chars
for (i = 0; i < 36; i++) {
s[i] = itoh[s[i]];
}
+ // Insert '-'s
s[8] = s[13] = s[18] = s[23] = "-";
return s.join("");
}
+ //Generate an epoch timestamp string
function timeStamp() {
return new Date().getTime().toString();
}
+ //Generate a device id, and attach it to localStorage.
function generateDeviceId() {
var deviceId = "UNKNOWN";
try {
@@ -2188,15 +3184,21 @@
return deviceId;
}
}
+ //Helper. Determines if the platform device is phonegap
function isPhoneGap() {
return typeof cordova !== "undefined" || typeof PhoneGap !== "undefined" || typeof window.device !== "undefined";
}
+ //Helper. Determines if the platform device is trigger.io
function isTrigger() {
return typeof window.forge !== "undefined";
}
+ //Helper. Determines if the platform device is titanium.
function isTitanium() {
return typeof Titanium !== "undefined";
}
+ /**
+ * @method determineBrowserType
+ */
var BROWSERS = [ "Opera", "MSIE", "Safari", "Chrome", "Firefox" ];
function createBrowserRegex(browser) {
return new RegExp("\\b(" + browser + ")\\/([^\\s]+)");
@@ -2219,6 +3221,7 @@
return createBrowserTest(navigator.userAgent, arr[0], arr[1]);
});
function determineBrowserType(ua, appName) {
+ //var ua = navigator.userAgent;
var browserName = appName;
var nameOffset, verOffset, verLength, ix, fullVersion = UNKNOWN;
var browserData = {
View
6 apigee.min.js
@@ -1,3 +1,3 @@
-/*! apigee-javascript-sdk@2.0.5 2013-11-26 */
-!function(){function Usergrid(){}function isUUID(uuid){var uuidValueRegex=/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;return uuid?uuidValueRegex.test(uuid):!1}function encodeParams(params){var i,tail=[],item=[];if(params instanceof Array)for(i in params)item=params[i],item instanceof Array&&item.length>1&&tail.push(item[0]+"="+encodeURIComponent(item[1]));else for(var key in params)if(params.hasOwnProperty(key)){var value=params[key];if(value instanceof Array)for(i in value)item=value[i],tail.push(key+"="+encodeURIComponent(item));else tail.push(key+"="+encodeURIComponent(value))}return tail.join("&")}var name="Usergrid",global=global||this,overwrittenName=global[name],AUTH_CLIENT_ID="CLIENT_ID",AUTH_APP_USER="APP_USER",AUTH_NONE="NONE";"undefined"==typeof console&&(global.console={log:function(){},warn:function(){},error:function(){},dir:function(){}}),Usergrid.Client=function(options){this.URI=options.URI||"https://api.usergrid.com",options.orgName&&this.set("orgName",options.orgName),options.appName&&this.set("appName",options.appName),options.appVersion&&this.set("appVersion",options.appVersion),this.authType=options.authType||AUTH_NONE,this.clientId=options.clientId,this.clientSecret=options.clientSecret,this.token=options.token||null,this.buildCurl=options.buildCurl||!1,this.logging=options.logging||!1,this._callTimeout=options.callTimeout||3e4,this._callTimeoutCallback=options.callTimeoutCallback||null,this.logoutCallback=options.logoutCallback||null},Usergrid.Client.prototype._request_node=function(options,callback){global.request=global.request||require("request");var request=global.request,self=this,method=options.method||"GET",endpoint=options.endpoint,body=options.body||{},qs=options.qs||{},mQuery=options.mQuery||!1,orgName=this.get("orgName"),appName=this.get("appName");if(!mQuery&&!orgName&&!appName&&"function"==typeof this.logoutCallback)return this.logoutCallback(!0,"no_org_or_app_name_specified");uri=mQuery?this.URI+"/"+endpoint:this.URI+"/"+orgName+"/"+appName+"/"+endpoint,this.authType===AUTH_CLIENT_ID?(qs.client_id=this.clientId,qs.client_secret=this.clientSecret):this.authType===AUTH_APP_USER&&(qs.access_token=self.getToken()),this.logging&&console.log("calling: "+method+" "+uri),this._start=(new Date).getTime();var callOptions={method:method,uri:uri,json:body,qs:qs};request(callOptions,function(err,r,data){if(self.buildCurl&&(options.uri=r.request.uri.href,self.buildCurlCall(options)),self._end=(new Date).getTime(),200===r.statusCode)self.logging&&console.log("success (time: "+self.calcTimeDiff()+"): "+method+" "+uri),callback(err,data);else if(err=!0,"auth_expired_session_token"===r.error||"auth_missing_credentials"===r.error||"auth_unverified_oath"==r.error||"expired_token"===r.error||"unauthorized"===r.error||"auth_invalid"===r.error){var error=r.body.error,errorDesc=r.body.error_description;self.logging&&console.log("Error ("+r.statusCode+")("+error+"): "+errorDesc),"function"==typeof self.logoutCallback?self.logoutCallback(err,data):"function"==typeof callback&&callback(err,data)}else{var error=r.body.error,errorDesc=r.body.error_description;self.logging&&console.log("Error ("+r.statusCode+")("+error+"): "+errorDesc),"function"==typeof callback&&callback(err,data)}})},Usergrid.Client.prototype._request_xhr=function(options,callback){var self=this,method=options.method||"GET",endpoint=options.endpoint,body=options.body||{},qs=options.qs||{},mQuery=options.mQuery||!1,orgName=this.get("orgName"),appName=this.get("appName");if(!mQuery&&!orgName&&!appName&&"function"==typeof this.logoutCallback)return this.logoutCallback(!0,"no_org_or_app_name_specified");var uri;uri=mQuery?this.URI+"/"+endpoint:this.URI+"/"+orgName+"/"+appName+"/"+endpoint,self.getToken()&&(qs.access_token=self.getToken());var encoded_params=encodeParams(qs);encoded_params&&(uri+="?"+encoded_params),body=JSON.stringify(body);var xhr=new XMLHttpRequest;xhr.open(method,uri,!0),body&&(xhr.setRequestHeader("Content-Type","application/json"),xhr.setRequestHeader("Accept","application/json")),xhr.onerror=function(response){self._end=(new Date).getTime(),self.logging&&console.log("success (time: "+self.calcTimeDiff()+"): "+method+" "+uri),self.logging&&console.log("Error: API call failed at the network level."),clearTimeout(timeout);var err=!0;"function"==typeof callback&&callback(err,response)},xhr.onload=function(response){if(self._end=(new Date).getTime(),self.logging&&console.log("success (time: "+self.calcTimeDiff()+"): "+method+" "+uri),clearTimeout(timeout),response=JSON.parse(xhr.responseText),200!=xhr.status){var error=response.error,error_description=response.error_description;if(self.logging&&console.log("Error ("+xhr.status+")("+error+"): "+error_description),("auth_expired_session_token"==error||"auth_missing_credentials"==error||"auth_unverified_oath"==error||"expired_token"==error||"unauthorized"==error||"auth_invalid"==error)&&"function"==typeof self.logoutCallback)return self.logoutCallback(!0,response);"function"==typeof callback&&callback(!0,response)}else"function"==typeof callback&&callback(!1,response)};var timeout=setTimeout(function(){xhr.abort(),"function"===self._callTimeoutCallback?self._callTimeoutCallback("API CALL TIMEOUT"):self.callback("API CALL TIMEOUT")},self._callTimeout);if(this.logging&&console.log("calling: "+method+" "+uri),this.buildCurl){var curlOptions={uri:uri,body:body,method:method};this.buildCurlCall(curlOptions)}this._start=(new Date).getTime(),xhr.send(body)},Usergrid.Client.prototype.request=function(){"undefined"!=typeof window?Usergrid.Client.prototype._request_xhr.apply(this,arguments):Usergrid.Client.prototype._request_node.apply(this,arguments)},Usergrid.Client.prototype.buildAssetURL=function(uuid){var self=this,qs={},assetURL=this.URI+"/"+this.orgName+"/"+this.appName+"/assets/"+uuid+"/data";self.getToken()&&(qs.access_token=self.getToken());var encoded_params=encodeParams(qs);return encoded_params&&(assetURL+="?"+encoded_params),assetURL},Usergrid.Client.prototype.createGroup=function(options,callback){var getOnExist=options.getOnExist||!1;options={path:options.path,client:this,data:options};var group=new Usergrid.Group(options);group.fetch(function(err,data){var okToSave=err&&"service_resource_not_found"===data.error||"no_name_specified"===data.error||"null_pointer"===data.error||!err&&getOnExist;okToSave?group.save(function(err){"function"==typeof callback&&callback(err,group)}):"function"==typeof callback&&callback(err,group)})},Usergrid.Client.prototype.createEntity=function(options,callback){var getOnExist=options.getOnExist||!1;options={client:this,data:options};var entity=new Usergrid.Entity(options);entity.fetch(function(err,data){var okToSave=err&&"service_resource_not_found"===data.error||"no_name_specified"===data.error||"null_pointer"===data.error||!err&&getOnExist;okToSave?(entity.set(options.data),entity.save(function(err,data){"function"==typeof callback&&callback(err,entity,data)})):"function"==typeof callback&&callback(err,entity,data)})},Usergrid.Client.prototype.getEntity=function(options,callback){options={client:this,data:options};var entity=new Usergrid.Entity(options);entity.fetch(function(err,data){"function"==typeof callback&&callback(err,entity,data)})},Usergrid.Client.prototype.restoreEntity=function(serializedObject){var data=JSON.parse(serializedObject);options={client:this,data:data};var entity=new Usergrid.Entity(options);return entity},Usergrid.Client.prototype.createCollection=function(options,callback){options.client=this;var collection=new Usergrid.Collection(options,function(err,data){"function"==typeof callback&&callback(err,collection,data)})},Usergrid.Client.prototype.restoreCollection=function(serializedObject){var data=JSON.parse(serializedObject);data.client=this;var collection=new Usergrid.Collection(data);return collection},Usergrid.Client.prototype.getFeedForUser=function(username,callback){var options={method:"GET",endpoint:"users/"+username+"/feed"};this.request(options,function(err,data){"function"==typeof callback&&(err?callback(err):callback(err,data,data.entities))})},Usergrid.Client.prototype.createUserActivity=function(user,options,callback){options.type="users/"+user+"/activities",options={client:this,data:options};var entity=new Usergrid.Entity(options);entity.save(function(err){"function"==typeof callback&&callback(err,entity)})},Usergrid.Client.prototype.createUserActivityWithEntity=function(user,content,callback){var username=user.get("username"),options={actor:{displayName:username,uuid:user.get("uuid"),username:username,email:user.get("email"),picture:user.get("picture"),image:{duration:0,height:80,url:user.get("picture"),width:80}},verb:"post",content:content};this.createUserActivity(username,options,callback)},Usergrid.Client.prototype.calcTimeDiff=function(){var seconds=0,time=this._end-this._start;try{seconds=(time/10/60).toFixed(2)}catch(e){return 0}return seconds},Usergrid.Client.prototype.setToken=function(token){this.set("token",token)},Usergrid.Client.prototype.getToken=function(){return this.get("token")},Usergrid.Client.prototype.setObject=function(key,value){value&&(value=JSON.stringify(value)),this.set(key,value)},Usergrid.Client.prototype.set=function(key,value){var keyStore="apigee_"+key;this[key]=value,"undefined"!=typeof Storage&&(value?localStorage.setItem(keyStore,value):localStorage.removeItem(keyStore))},Usergrid.Client.prototype.getObject=function(key){return JSON.parse(this.get(key))},Usergrid.Client.prototype.get=function(key){var keyStore="apigee_"+key;return this[key]?this[key]:"undefined"!=typeof Storage?localStorage.getItem(keyStore):null},Usergrid.Client.prototype.signup=function(username,password,email,name,callback){var options={type:"users",username:username,password:password,email:email,name:name};this.createEntity(options,callback)},Usergrid.Client.prototype.login=function(username,password,callback){var self=this,options={method:"POST",endpoint:"token",body:{username:username,password:password,grant_type:"password"}};this.request(options,function(err,data){var user={};err&&self.logging?console.log("error trying to log user in"):(options={client:self,data:data.user},user=new Usergrid.Entity(options),self.setToken(data.access_token)),"function"==typeof callback&&callback(err,data,user)})},Usergrid.Client.prototype.reAuthenticateLite=function(callback){var self=this,options={method:"GET",endpoint:"management/me",mQuery:!0};this.request(options,function(err,response){err&&self.logging?console.log("error trying to re-authenticate user"):self.setToken(response.access_token),"function"==typeof callback&&callback(err)})},Usergrid.Client.prototype.reAuthenticate=function(email,callback){var self=this,options={method:"GET",endpoint:"management/users/"+email,mQuery:!0};this.request(options,function(err,response){var data,organizations={},applications={},user={};if(err&&self.logging)console.log("error trying to full authenticate user");else{data=response.data,self.setToken(data.token),self.set("email",data.email),localStorage.setItem("accessToken",data.token),localStorage.setItem("userUUID",data.uuid),localStorage.setItem("userEmail",data.email);var userData={username:data.username,email:data.email,name:data.name,uuid:data.uuid};options={client:self,data:userData},user=new Usergrid.Entity(options),organizations=data.organizations;var org="";try{var existingOrg=self.get("orgName");org=organizations[existingOrg]?organizations[existingOrg]:organizations[Object.keys(organizations)[0]],self.set("orgName",org.name)}catch(e){err=!0,self.logging&&console.log("error selecting org")}applications=self.parseApplicationsArray(org),self.selectFirstApp(applications),self.setObject("organizations",organizations),self.setObject("applications",applications)}"function"==typeof callback&&callback(err,data,user,organizations,applications)})},Usergrid.Client.prototype.loginFacebook=function(facebookToken,callback){var self=this,options={method:"GET",endpoint:"auth/facebook",qs:{fb_access_token:facebookToken}};this.request(options,function(err,data){var user={};if(err&&self.logging)console.log("error trying to log user in");else{var options={client:self,data:data.user};user=new Usergrid.Entity(options),self.setToken(data.access_token)}"function"==typeof callback&&callback(err,data,user)})},Usergrid.Client.prototype.getLoggedInUser=function(callback){if(this.getToken()){var self=this,options={method:"GET",endpoint:"users/me"};this.request(options,function(err,data){if(err)self.logging&&console.log("error trying to log user in"),"function"==typeof callback&&callback(err,data,null);else{var options={client:self,data:data.entities[0]},user=new Usergrid.Entity(options);"function"==typeof callback&&callback(err,data,user)}})}else callback(!0,null,null)},Usergrid.Client.prototype.isLoggedIn=function(){return this.getToken()&&"null"!=this.getToken()?!0:!1},Usergrid.Client.prototype.logout=function(){this.setToken(null)},Usergrid.Client.prototype.buildCurlCall=function(options){var curl="curl",method=(options.method||"GET").toUpperCase(),body=options.body||{},uri=options.uri;return curl+="POST"===method?" -X POST":"PUT"===method?" -X PUT":"DELETE"===method?" -X DELETE":" -X GET",curl+=" "+uri,"undefined"!=typeof window&&(body=JSON.stringify(body)),'"{}"'!==body&&"GET"!==method&&"DELETE"!==method&&(curl+=" -d '"+body+"'"),console.log(curl),curl},Usergrid.Client.prototype.getDisplayImage=function(email,picture,size){try{if(picture)return picture;var size=size||50;return email.length?"https://secure.gravatar.com/avatar/"+MD5(email)+"?s="+size+encodeURI("&d=https://apigee.com/usergrid/images/user_profile.png"):"https://apigee.com/usergrid/images/user_profile.png"}catch(e){return"https://apigee.com/usergrid/images/user_profile.png"}},Usergrid.Entity=function(options){options&&(this._data=options.data||{},this._client=options.client||{})},Usergrid.Entity.prototype.serialize=function(){return JSON.stringify(this._data)},Usergrid.Entity.prototype.get=function(field){return field?this._data[field]:this._data},Usergrid.Entity.prototype.set=function(key,value){if("object"==typeof key)for(var field in key)this._data[field]=key[field];else"string"==typeof key?null===value?delete this._data[key]:this._data[key]=value:this._data={}},Usergrid.Entity.prototype.save=function(callback){var type=this.get("type"),method="POST";isUUID(this.get("uuid"))&&(method="PUT",type+="/"+this.get("uuid"));var self=this,data={},entityData=this.get();for(var item in entityData)"metadata"!==item&&"created"!==item&&"modified"!==item&&"type"!==item&&"activated"!==item&&"uuid"!==item&&(data[item]=entityData[item]);var options={method:method,endpoint:type,body:data};this._client.request(options,function(err,retdata){if(err&&self._client.logging){if(console.log("could not save entity"),"function"==typeof callback)return callback(err,retdata,self)}else{if(retdata.entities&&retdata.entities.length){var entity=retdata.entities[0];self.set(entity);for(var path=retdata.path;"/"===path.substring(0,1);)path=path.substring(1);self.set("ty