Skip to content

Instantly share code, notes, and snippets.

@aaronksaunders
Created September 2, 2012 16:10
Show Gist options
  • Save aaronksaunders/3600972 to your computer and use it in GitHub Desktop.
Save aaronksaunders/3600972 to your computer and use it in GitHub Desktop.
Modified Kinvey Appcelerator Module to Support Resources
// this sets the background color of the master UIView (when there are no windows/tab groups on it)
var Kinvey = require('/services/kinvey-titanium-0.9.10');
//
// create base UI tab and root window
//
var win = Titanium.UI.createWindow({
title : 'Tab 1',
backgroundColor : '#fff'
});
if (Ti.Network.getOnline()) {
Kinvey.init({
appKey : 'kid1834', // USE YOUR CREDENTIALS
appSecret : 'xxxxxxxxxxxxxx' // USE YOUR CREDENTIALS
});
var params = {
"name" : 'KS_nav_ui.png',
"data" : Ti.Filesystem.getFile('KS_nav_ui.png').read()
};
// first check of there is a user with those credentials
Kinvey.Resource.upload(params, {
success : function(resource) {
Ti.API.info(JSON.stringify(resource));
displayResource(resource);
},
error : function(resource) {
Ti.API.info(JSON.stringify(resource));
}
});
var displayResource = function(_resource) {
Kinvey.Resource.download(_resource.name, {
success : function(resource) {
Ti.API.info(JSON.stringify(resource));
var iv = Ti.UI.createImageView({
image : resource.data
});
win.add(iv);
},
error : function(resource) {
Ti.API.info(JSON.stringify(resource));
}
});
}
} else {
alert('No Internet Connection')
}
win.open();
/*!
* Copyright (c) 2012 Kinvey, Inc. All rights reserved.
*
* Licensed to Kinvey, Inc. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Kinvey, Inc. licenses this file to you 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.
*/
(function(undefined) {
// Save reference to global object (window in browser, global on server).
var root = this;
/**
* Top-level namespace. Exported for browser and CommonJS.
*
* @name Kinvey
* @namespace
*/
var Kinvey;
if('undefined' !== typeof exports) {
Kinvey = exports;
}
else {
Kinvey = root.Kinvey = {};
}
// Define a base class for all Kinvey classes. Provides a property method for
// class inheritance. This method is available to all child definitions.
var Base = Object.defineProperty(function() { }, 'extend', {
value: function(prototype, properties) {
// Create class definition
var constructor = prototype && prototype.hasOwnProperty('constructor') ? prototype.constructor : this;
var def = function() {
constructor.apply(this, arguments);
};
// Set prototype by merging child prototype into parents.
def.prototype = (function(parent, child) {
Object.getOwnPropertyNames(child).forEach(function(property) {
Object.defineProperty(parent, property, Object.getOwnPropertyDescriptor(child, property));
});
return parent;
}(Object.create(this.prototype), prototype || {}));
// Set static properties.
if(properties) {
for(var prop in properties) {
if(properties.hasOwnProperty(prop)) {
def[prop] = properties[prop];
}
}
}
// Add extend to definition.
Object.defineProperty(def, 'extend', Object.getOwnPropertyDescriptor(this, 'extend'));
// Return definition.
return def;
}
});
// Convenient method for binding context to anonymous functions.
var bind = function(thisArg, fn) {
fn || (fn = function() { });
return fn.bind ? fn.bind(thisArg) : function() {
return fn.apply(thisArg, arguments);
};
};
// Merges multiple source objects into one newly created object.
var merge = function(/*sources*/) {
var target = {};
Array.prototype.slice.call(arguments, 0).forEach(function(source) {
for(var prop in source) {
target[prop] = source[prop];
}
});
return target;
};
// Define the Storage class.
var Storage = {
get: function(key) {
var value = Titanium.App.Properties.getString(key);
return value ? JSON.parse(value) : null;
},
set: function(key, value) {
Titanium.App.Properties.setString(key, JSON.stringify(value));
},
remove: function(key) {
Titanium.App.Properties.removeProperty(key);
}
};
// Define the Xhr mixin.
var Xhr = (function() {
/**
* Base 64 encodes string.
*
* @private
* @param {string} value
* @return {string} Encoded string.
*/
var base64 = function(value) {
return Titanium.Utils.base64encode(value);
};
/**
* Returns authorization string.
*
* @private
* @return {Object} Authorization.
*/
var getAuth = function() {
// Use master secret if specified.
if(null !== Kinvey.masterSecret) {
return 'Basic ' + this._base64(Kinvey.appKey + ':' + Kinvey.masterSecret);
}
// Use Session Auth if there is a current user.
var user = Kinvey.getCurrentUser();
if(null !== user) {
return 'Kinvey ' + user.getToken();
}
// Use application credentials as last resort.
return 'Basic ' + this._base64(Kinvey.appKey + ':' + Kinvey.appSecret);
};
/**
* Returns device information.
*
* @private
* @return {string} Device information.
*/
var getDeviceInfo = function() {
// Example: "Titanium mobileweb 2.0.1.GA2 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX".
return [
'Titanium',
Titanium.Platform.osname,
Titanium.Platform.version,
Titanium.App.getGUID()// device ID.
].map(function(value) {
return value.toString().toLowerCase().replace(' ', '_');
}).join(' ');
};
/**
* Sends a request against Kinvey.
*
* @private
* @param {string} method Request method.
* @param {string} url Request URL.
* @param {string} body Request body.
* @param {Object} options
* @param {integer} [options.timeout] Request timeout (ms).
* @param {function(response, info)} [options.success] Success callback.
* @param {function(error, info)} [options.error] Failure callback.
*/
var send = function(method, url, body, options) {
options || (options = {});
'undefined' !== typeof options.timeout || (options.timeout = this.options.timeout);
options.success || (options.success = this.options.success);
options.error || (options.error = this.options.error);
// For now, include authorization in this adapter. Ideally, it should
// have some external interface.
if(null === Kinvey.getCurrentUser() && Kinvey.Store.AppData.USER_API !== this.api && null === Kinvey.masterSecret) {
return Kinvey.User.create({}, merge(options, {
success: bind(this, function() {
this._send(method, url, body, options);
})
}));
}
// Add host to URL.
url = Kinvey.HOST + url;
// Headers.
var headers = {
Accept: 'application/json, text/javascript',
Authorization: this._getAuth(),
'X-Kinvey-API-Version': Kinvey.API_VERSION,
'X-Kinvey-Device-Information': this._getDeviceInfo()
};
body && (headers['Content-Type'] = 'application/json; charset=utf-8');
// Add header for compatibility with Android 2.2, 2.3.3 and 3.2.
// @link http://www.kinvey.com/blog/item/179-how-to-build-a-service-that-supports-every-android-browser
if('GET' === method && 'undefined' !== typeof window && window.location) {
headers['X-Kinvey-Origin'] = window.location.protocol + '//' + window.location.host;
}
// Execute request.
this._xhr(method, url, body, merge(options, {
headers: headers,
success: function(response, info) {
// Response is expected to be either empty, or valid JSON.
response = response ? JSON.parse(response) : null;
options.success(response, info);
},
error: function(response, info) {
// Response could be valid JSON if the error occurred at Kinvey.
try {
response = JSON.parse(response);
}
catch(_) {// Or just the error type if something else went wrong.
var error = {
abort: 'The request was aborted',
error: 'The request failed',
timeout: 'The request timed out'
};
// Execute application-level handler.
response = {
error: Kinvey.Error.REQUEST_FAILED,
description: error[response] || error.error,
debug: ''
};
}
// Return.
options.error(response, info);
}
}));
};
/**
* Sends a request.
*
* @private
* @param {string} method Request method.
* @param {string} url Request URL.
* @param {string} body Request body.
* @param {Object} options
* @param {Object} [options.headers] Request headers.
* @param {integer} [options.timeout] Request timeout (ms).
* @param {function(status, response)} [options.success] Success callback.
* @param {function(type)} [options.error] Failure callback.
*/
var xhr = function(method, url, body, options) {
options || (options = {});
options.headers || (options.headers = {});
'undefined' !== typeof options.timeout || (options.timeout = this.options.timeout);
options.success || (options.success = this.options.success);
options.error || (options.error = this.options.error);
// Create the request. Titanium.Network.createHTTPClient is buggy for
// Mobile Web, so use the native implementation instead.
var request;
if('undefined' === typeof XMLHttpRequest) {
request = Titanium.Network.createHTTPClient();
}
else {
request = new XMLHttpRequest();
}
request.open(method, url);
request.timeout = options.timeout;
// Pass headers to request.
for(var name in options.headers) {
if(options.headers.hasOwnProperty(name)) {
request.setRequestHeader(name, options.headers[name]);
}
}
// Attach handlers.
request.onload = function() {
// Success implicates status 2xx (Successful), or 304 (Not Modified).
if(2 === parseInt(this.status / 100, 10) || 304 === this.status) {
options.success(this.responseText, { network: true, data : this.responseData });
}
else {
options.error(this.responseText, { network: true });
}
};
request.onabort = request.onerror = request.ontimeout = function(event) {
options.error(event.type, { network: true });
};
// Fire request.
request.send(body);
};
// Attach to context.
return function() {
this._base64 = base64;
this._getAuth = getAuth;
this._getDeviceInfo = getDeviceInfo;
this._send = send;
this._xhr = xhr;
return this;
};
}());
// Current user.
var currentUser = null;
/**
* API version.
*
* @constant
*/
Kinvey.API_VERSION = 2;
/**
* Host.
*
* @constant
*/
Kinvey.HOST = 'https://baas.kinvey.com';
/**
* SDK version.
*
* @constant
*/
Kinvey.SDK_VERSION = '0.9.10';
/**
* Returns current user, or null if not set.
*
* @return {Kinvey.User} Current user.
*/
Kinvey.getCurrentUser = function() {
return currentUser;
};
/**
* Initializes library for use with Kinvey services. Never use the master
* secret in client-side code.
*
* @example <code>
* Kinvey.init({
* appKey: 'your-app-key',
* appSecret: 'your-app-secret'
* });
* </code>
*
* @param {Object} options Kinvey credentials. Object expects properties:
* "appKey", and "appSecret" or "masterSecret". Optional: "sync".
* @throws {Error}
* <ul>
* <li>On empty appKey,</li>
* <li>On empty appSecret and masterSecret.</li>
* </ul>
*/
Kinvey.init = function(options) {
options || (options = {});
if(null == options.appKey) {
throw new Error('appKey must be defined');
}
if(null == options.appSecret && null == options.masterSecret) {
throw new Error('appSecret or masterSecret must be defined');
}
// Store credentials.
Kinvey.appKey = options.appKey;
Kinvey.appSecret = options.appSecret || null;
Kinvey.masterSecret = options.masterSecret || null;
// Restore current user.
Kinvey.User._restore();
// Synchronize app in the background.
options.sync && Kinvey.Sync && Kinvey.Sync.application();
};
/**
* Round trips a request to the server and back, helps ensure connectivity.
*
* @example <code>
* Kinvey.ping({
* success: function(response) {
* console.log('Ping successful', response.kinvey, response.version);
* },
* error: function(error) {
* console.log('Ping failed', error.message);
* }
* });
* </code>
*
* @param {Object} [options]
* @param {function(response, info)} [options.success] Success callback.
* @param {function(error, info)} [options.error] Failure callback.
*/
Kinvey.ping = function(options) {
// Ping always targets the Kinvey backend.
new Kinvey.Store.AppData(null).query(null, options);
};
/**
* Sets the current user. This method is only used by the Kinvey.User
* namespace.
*
* @private
* @param {Kinvey.User} user Current user.
*/
Kinvey.setCurrentUser = function(user) {
currentUser = user;
};
/**
* Kinvey Error namespace definition. Holds all possible errors.
*
* @namespace
*/
Kinvey.Error = {
// Client-side.
/** @constant */
DATABASE_ERROR: 'DatabaseError',
/** @constant */
NO_NETWORK: 'NoNetwork',
/** @constant */
OPERATION_DENIED: 'OperationDenied',
/** @constant */
REQUEST_FAILED: 'RequestFailed',
/** @constant */
RESPONSE_PROBLEM: 'ResponseProblem',
// Server-side.
/** @constant */
ENTITY_NOT_FOUND: 'EntityNotFound',
/** @constant */
COLLECTION_NOT_FOUND: 'CollectionNotFound',
/** @constant */
APP_NOT_FOUND: 'AppNotFound',
/** @constant */
USER_NOT_FOUND: 'UserNotFound',
/** @constant */
BLOB_NOT_FOUND: 'BlobNotFound',
/** @constant */
INVALID_CREDENTIALS: 'InvalidCredentials',
/** @constant */
KINVEY_INTERNAL_ERROR_RETRY: 'KinveyInternalErrorRetry',
/** @constant */
KINVEY_INTERNAL_ERROR_STOP: 'KinveyInternalErrorStop',
/** @constant */
USER_ALREADY_EXISTS: 'UserAlreadyExists',
/** @constant */
USER_UNAVAILABLE: 'UserUnavailable',
/** @constant */
DUPLICATE_END_USERS: 'DuplicateEndUsers',
/** @constant */
INSUFFICIENT_CREDENTIALS: 'InsufficientCredentials',
/** @constant */
WRITES_TO_COLLECTION_DISALLOWED: 'WritesToCollectionDisallowed',
/** @constant */
INDIRECT_COLLECTION_ACCESS_DISALLOWED : 'IndirectCollectionAccessDisallowed',
/** @constant */
APP_PROBLEM: 'AppProblem',
/** @constant */
PARAMETER_VALUE_OUT_OF_RANGE: 'ParameterValueOutOfRange',
/** @constant */
CORS_DISABLED: 'CORSDisabled',
/** @constant */
INVALID_QUERY_SYNTAX: 'InvalidQuerySyntax',
/** @constant */
MISSING_QUERY: 'MissingQuery',
/** @constant */
JSON_PARSE_ERROR: 'JSONParseError',
/** @constant */
MISSING_REQUEST_HEADER: 'MissingRequestHeader',
/** @constant */
INCOMPLETE_REQUEST_BODY: 'IncompleteRequestBody',
/** @constant */
MISSING_REQUEST_PARAMETER: 'MissingRequestParameter',
/** @constant */
INVALID_IDENTIFIER: 'InvalidIdentifier',
/** @constant */
BAD_REQUEST: 'BadRequest',
/** @constant */
FEATURE_UNAVAILABLE: 'FeatureUnavailable',
/** @constant */
API_VERSION_NOT_IMPLEMENTED: 'APIVersionNotImplemented',
/** @constant */
API_VERSION_NOT_AVAILABLE: 'APIVersionNotAvailable',
/** @constant */
INPUT_VALIDATION_FAILED: 'InputValidationFailed',
/** @constant */
BLruntimeError: 'BLruntimeError'
};
// Define the Kinvey Entity class.
Kinvey.Entity = Base.extend({
// Identifier attribute.
ATTR_ID: '_id',
// Map.
map: {},
/**
* Creates a new entity.
*
* @example <code>
* var entity = new Kinvey.Entity({}, 'my-collection');
* var entity = new Kinvey.Entity({ key: 'value' }, 'my-collection');
* </code>
*
* @name Kinvey.Entity
* @constructor
* @param {Object} [attr] Attribute object.
* @param {string} collection Owner collection.
* @param {Object} options Options.
* @throws {Error} On empty collection.
*/
constructor: function(attr, collection, options) {
if(null == collection) {
throw new Error('Collection must not be null');
}
this.collection = collection;
this.metadata = null;
// Options.
options || (options = {});
this.store = Kinvey.Store.factory(options.store, this.collection, options.options);
options.map && (this.map = options.map);
// Parse attributes.
this.attr = attr ? this._parseAttr(attr) : {};
// State (used by save()).
this._pending = false;
},
/** @lends Kinvey.Entity# */
/**
* Destroys entity.
*
* @param {Object} [options]
* @param {function(entity, info)} [options.success] Success callback.
* @param {function(error, info)} [options.error] Failure callback.
*/
destroy: function(options) {
options || (options = {});
this.store.remove(this.toJSON(), merge(options, {
success: bind(this, function(_, info) {
options.success && options.success(this, info);
})
}));
},
/**
* Returns attribute, or null if not set.
*
* @param {string} key Attribute key.
* @throws {Error} On empty key.
* @return {*} Attribute.
*/
get: function(key) {
if(null == key) {
throw new Error('Key must not be null');
}
// Return attribute, or null if attribute is null or undefined.
var value = this.attr[key];
return null != value ? value : null;
},
/**
* Returns id or null if not set.
*
* @return {string} id
*/
getId: function() {
return this.get(this.ATTR_ID);
},
/**
* Returns metadata.
*
* @return {Kinvey.Metadata} Metadata.
*/
getMetadata: function() {
// Lazy load metadata object, and return it.
this.metadata || (this.metadata = new Kinvey.Metadata(this.attr));
return this.metadata;
},
/**
* Returns whether entity is persisted.
*
* @return {boolean}
*/
isNew: function() {
return null === this.getId();
},
/**
* Loads entity by id.
*
* @param {string} id Entity id.
* @param {Object} [options]
* @param {function(entity, info)} [options.success] Success callback.
* @param {function(error, info)} [options.error] Failure callback.
* @throws {Error} On empty id.
*/
load: function(id, options) {
if(null == id) {
throw new Error('Id must not be null');
}
options || (options = {});
this.store.query(id, merge(options, {
success: bind(this, function(response, info) {
this.attr = this._parseAttr(response);
this.metadata = null;// Reset.
options.success && options.success(this, info);
})
}));
},
/**
* Saves entity.
*
* @param {Object} [options]
* @param {function(entity, info)} [options.success] Success callback.
* @param {function(error, info)} [options.error] Failure callback.
*/
save: function(options) {
options || (options = {});
// Refuse to save circular references.
if(this._pending && options._ref) {
this._pending = false;// Reset.
options.error({
error: Kinvey.Error.OPERATION_DENIED,
message: 'Circular reference detected, aborting save',
debug: ''
}, {});
return;
}
this._pending = true;
// Save children first.
this._saveAttr(this.attr, {
success: bind(this, function(references) {
// All children are saved, save parent.
this.store.save(this.toJSON(), merge(options, {
success: bind(this, function(response, info) {
this._pending = false;// Reset.
// Merge response with response of children.
this.attr = merge(this._parseAttr(response), references);
this.metadata = null;// Reset.
options.success && options.success(this, info);
}),
error: bind(this, function(error, info) {
this._pending = false;// Reset.
options.error && options.error(error, info);
})
}));
}),
// One of the children failed, break on error.
error: bind(this, function(error, info) {
this._pending = false;// Reset.
options.error && options.error(error, info);
}),
// Flag to detect any circular references.
_ref: true
});
},
/**
* Sets attribute.
*
* @param {string} key Attribute key.
* @param {*} value Attribute value.
* @throws {Error} On empty key.
*/
set: function(key, value) {
if(null == key) {
throw new Error('Key must not be null');
}
this.attr[key] = this._parse(key, value);
},
/**
* Sets id.
*
* @param {string} id Id.
* @throws {Error} On empty id.
*/
setId: function(id) {
if(null == id) {
throw new Error('Id must not be null');
}
this.set(this.ATTR_ID, id);
},
/**
* Sets metadata.
*
* @param {Kinvey.Metadata} metadata Metadata object.
* @throws {Error} On invalid instance.
*/
setMetadata: function(metadata) {
if(metadata && !(metadata instanceof Kinvey.Metadata)) {
throw new Error('Metadata must be an instanceof Kinvey.Metadata');
}
this.metadata = metadata || null;
},
/**
* Returns JSON representation. Used by JSON#stringify.
*
* @returns {Object} JSON representation.
*/
toJSON: function(name) {
// Flatten references.
var result = this._flattenAttr(this.attr);// Copy by value.
this.metadata && (result._acl = this.metadata.toJSON()._acl);
return result;
},
/**
* Removes attribute.
*
* @param {string} key Attribute key.
*/
unset: function(key) {
delete this.attr[key];
},
/**
* Flattens attribute value.
*
* @private
* @param {*} value Attribute value.
* @returns {*}
*/
_flatten: function(value) {
if(value instanceof Object) {
if(value instanceof Kinvey.Entity) {// Case 1: value is a reference.
value = {
_type: 'KinveyRef',
_collection: value.collection,
_id: value.getId()
};
}
else if(value instanceof Array) {// Case 2: value is an array.
// Flatten all members.
value = value.map(bind(this, function(x) {
return this._flatten(x);
}));
}
else {
value = this._flattenAttr(value);// Case 3: value is an object.
}
}
return value;
},
/**
* Flattens attributes.
*
* @private
* @param {Object} attr Attributes.
* @returns {Object} Flattened attributes.
*/
_flattenAttr: function(attr) {
var result = {};
Object.keys(attr).forEach(bind(this, function(name) {
result[name] = this._flatten(attr[name]);
}));
return result;
},
/**
* Parses references in name/value pair.
*
* @private
* @param {string} name Attribute name.
* @param {*} value Attribute value.
* @returns {*} Parsed value.
*/
_parse: function(name, value) {
if(value instanceof Object) {
if(value instanceof Kinvey.Entity) { }// Skip.
else if('KinveyRef' === value._type) {// Case 1: value is a reference.
// Create object from reference if embedded, otherwise skip.
if(value._obj) {
var Entity = this.map[name] || Kinvey.Entity;// Use mapping if defined.
// Maintain store type and configuration.
var opts = { store: this.store.type, options: this.store.options };
value = new Entity(value._obj, value._collection, opts);
}
}
else if(value instanceof Array) {// Case 2: value is an array.
// Loop through and parse all members.
value = value.map(bind(this, function(x) {
return this._parse(name, x);
}));
}
else {// Case 3: value is an object.
value = this._parseAttr(value, name);
}
}
return value;
},
/**
* Parses references in attributes.
*
* @private
* @param {Object} attr Attributes.
* @param {string} [prefix] Name prefix.
* @return {Object} Parsed attributes.
*/
_parseAttr: function(attr, prefix) {
var result = merge(attr);// Copy by value.
Object.keys(attr).forEach(bind(this, function(name) {
result[name] = this._parse((prefix ? prefix + '.' : '') + name, attr[name]);
}));
return result;
},
/**
* Saves an attribute value.
*
* @private
* @param {*} value Attribute value.
* @param {Object} options Options.
*/
_save: function(value, options) {
if(value instanceof Object) {
if(value instanceof Kinvey.Entity) {// Case 1: value is a reference.
// Only save when authorized, otherwise just return. Note any writable
// children are not saved if the parent is not writable.
return value.getMetadata().hasWritePermissions() ? value.save(options) : options.success(value);
}
if(value instanceof Array) {// Case 2: value is an array.
// Save every element in the array (serially), and update in place.
var i = 0;
// Define save handler.
var save = bind(this, function() {
var item = value[i];
item ? this._save(item, merge(options, {
success: function(response) {
value[i++] = response;// Update.
save();// Advance.
}
})) : options.success(value);
});
// Trigger.
return save();
}
// Case 3: value is an object.
return this._saveAttr(value, options);
}
// Case 4: value is a scalar.
options.success(value);
},
/**
* Saves attributes.
*
* @private
* @param {Object} attr Attributes.
* @param {Object} options Options.
*/
_saveAttr: function(attr, options) {
// Save attributes serially.
var attrs = Object.keys(attr);
var i = 0;
// Define save handler.
var save = bind(this, function() {
var name = attrs[i++];
name ? this._save(attr[name], merge(options, {
success: function(response) {
attr[name] = response;// Update.
save();// Advance.
}
})) : options.success(attr);
});
// Trigger.
save();
}
});
// Define the Kinvey Collection class.
Kinvey.Collection = Base.extend({
// List of entities.
list: [],
// Mapped entity class.
entity: Kinvey.Entity,
/**
* Creates new collection.
*
* @example <code>
* var collection = new Kinvey.Collection('my-collection');
* </code>
*
* @constructor
* @name Kinvey.Collection
* @param {string} name Collection name.
* @param {Object} [options] Options.
* @throws {Error}
* <ul>
* <li>On empty name,</li>
* <li>On invalid query instance.</li>
* </ul>
*/
constructor: function(name, options) {
if(null == name) {
throw new Error('Name must not be null');
}
this.name = name;
// Options.
options || (options = {});
this.setQuery(options.query || new Kinvey.Query());
this.store = Kinvey.Store.factory(options.store, this.name, options.options);
},
/** @lends Kinvey.Collection# */
/**
* Aggregates entities in collection.
*
* @param {Kinvey.Aggregation} aggregation Aggregation object.
* @param {Object} [options]
* @param {function(aggregation, info)} [options.success] Success callback.
* @param {function(error, info)} [options.error] Failure callback.
*/
aggregate: function(aggregation, options) {
if(!(aggregation instanceof Kinvey.Aggregation)) {
throw new Error('Aggregation must be an instanceof Kinvey.Aggregation');
}
aggregation.setQuery(this.query);// respect collection query.
this.store.aggregate(aggregation.toJSON(), options);
},
/**
* Clears collection.
*
* @param {Object} [options]
* @param {function(info)} [success] Success callback.
* @param {function(error, info)} [error] Failure callback.
*/
clear: function(options) {
options || (options = {});
this.store.removeWithQuery(this.query.toJSON(), merge(options, {
success: bind(this, function(_, info) {
this.list = [];
options.success && options.success(info);
})
}));
},
/**
* Counts number of entities.
*
* @example <code>
* var collection = new Kinvey.Collection('my-collection');
* collection.count({
* success: function(i) {
* console.log('Number of entities: ' + i);
* },
* error: function(error) {
* console.log('Count failed', error.description);
* }
* });
* </code>
*
* @param {Object} [options]
* @param {function(count, info)} [success] Success callback.
* @param {function(error, info)} [error] Failure callback.
*/
count: function(options) {
options || (options = {});
var aggregation = new Kinvey.Aggregation();
aggregation.setInitial({ count: 0 });
aggregation.setReduce(function(doc, out) {
out.count += 1;
});
aggregation.setQuery(this.query);// Apply query.
this.store.aggregate(aggregation.toJSON(), merge(options, {
success: function(response, info) {
options.success && options.success(response[0].count, info);
}
}));
},
/**
* Fetches entities in collection.
*
* @param {Object} [options]
* @param {function(list, info)} [options.success] Success callback.
* @param {function(error, info)} [options.error] Failure callback.
*/
fetch: function(options) {
options || (options = {});
// Send request.
this.store.queryWithQuery(this.query.toJSON(), merge(options, {
success: bind(this, function(response, info) {
this.list = [];
response.forEach(bind(this, function(attr) {
// Maintain collection store type and configuration.
var opts = { store: this.store.name, options: this.store.options };
this.list.push(new this.entity(attr, this.name, opts));
}));
options.success && options.success(this.list, info);
})
}));
},
/**
* Sets query.
*
* @param {Kinvey.Query} [query] Query.
* @throws {Error} On invalid instance.
*/
setQuery: function(query) {
if(query && !(query instanceof Kinvey.Query)) {
throw new Error('Query must be an instanceof Kinvey.Query');
}
this.query = query || new Kinvey.Query();
}
});
// Function to get the cache key for this app.
var CACHE_TAG = function() {
return 'Kinvey.' + Kinvey.appKey;
};
// Define the Kinvey User class.
Kinvey.User = Kinvey.Entity.extend({
// Credential attributes.
ATTR_USERNAME: 'username',
ATTR_PASSWORD: 'password',
// Authorization token.
token: null,
/**
* Creates a new user.
*
* @example <code>
* var user = new Kinvey.User();
* var user = new Kinvey.User({ key: 'value' });
* </code>
*
* @name Kinvey.User
* @constructor
* @extends Kinvey.Entity
* @param {Object} [attr] Attributes.
*/
constructor: function(attr) {
Kinvey.Entity.prototype.constructor.call(this, attr, 'user');
},
/** @lends Kinvey.User# */
/**
* Destroys user. Use with caution.
*
* @override
* @see Kinvey.Entity#destroy
*/
destroy: function(options) {
options || (options = {});
// Destroying the user will automatically invalidate its token, so no
// need to logout explicitly.
Kinvey.Entity.prototype.destroy.call(this, merge(options, {
success: bind(this, function(_, info) {
this._logout();
options.success && options.success(this, info);
})
}));
},
/**
* Returns social identity, or null if not set.
*
* @return {Object} Identity.
*/
getIdentity: function() {
return this.get('_socialIdentity');
},
/**
* Returns token, or null if not set.
*
* @return {string} Token.
*/
getToken: function() {
return this.token;
},
/**
* Returns username, or null if not set.
*
* @return {string} Username.
*/
getUsername: function() {
return this.get(this.ATTR_USERNAME);
},
/**
* Logs in user.
*
* @example <code>
* var user = new Kinvey.User();
* user.login('username', 'password', {
* success: function() {
* console.log('Login successful');
* },
* error: function(error) {
* console.log('Login failed', error);
* }
* });
* </code>
*
* @param {string} username Username.
* @param {string} password Password.
* @param {Object} [options]
* @param {function(entity, info)} [options.success] Success callback.
* @param {function(error, info)} [options.error] Failure callback.
*/
login: function(username, password, options) {
this._doLogin({
username: username,
password: password
}, options || {});
},
/**
* Logs in user given a Facebook oAuth token.
*
* @param {string} token oAuth token.
* @param {Object} [attr] User attributes.
* @param {Object} [options]
* @param {function(entity, info)} [options.success] Success callback.
* @param {function(error, info)} [options.error] Failure callback.
*/
loginWithFacebook: function(token, attr, options) {
attr || (attr = {});
attr._socialIdentity = { facebook: { access_token: token } };
options || (options = {});
// Login, or create when there is no user with this Facebook identity.
this._doLogin(attr, merge(options, {
error: bind(this, function(error, info) {
// If user could not be found, register.
// if(Kinvey.Error.USER_NOT_FOUND === error.error) {
// Pass current instance as (private) option to create.
this.attr = attr;// Required as we set a specific target below.
return Kinvey.User.create(attr, merge(options, {
_target: this
}));
//}
// Something else went wrong (invalid token?), error out.
options.error && options.error(error, info);
})
}));
},
/**
* Logs in user given a Facebook oAuth token.
*
* @param {string} token oAuth token.
* @param {Object} [options]
* @param {function(entity, info)} [options.success] Success callback.
* @param {function(error, info)} [options.error] Failure callback.
*/
hasFacebook: function(token, options) {
var attr ={};
attr._socialIdentity = { facebook: { access_token: token } };
options || (options = {});
// Login, or create when there is no user with this Facebook identity.
this._doLogin(attr, merge(options, {
error: bind(this, function(error, info) {
// If user could not be found, register.
if(Kinvey.Error.USER_NOT_FOUND === error.error) {
// Pass current instance as (private) option to create.
this.attr = attr;// Required as we set a specific target below.
return Kinvey.User.create(attr, merge(options, {
_target: this
}));
}
// Something else went wrong (invalid token?), error out.
options.error && options.error(error, info);
}),
success : bind(this, function(response, info) {
Ti.API.info(' data ' + response);
options.success && options.success(response, info);
})
}));
},
/**
* Logs out user.
*
* @param {Object} [options] Options.
* @param {function(info)} [options.success] Success callback.
* @param {function(error, info)} [options.error] Failure callback.
*/
logout: function(options) {
options || (options = {});
// Make sure we only logout the current user.
if(!this.isLoggedIn) {
options.success && options.success({});
return;
}
this.store.logout({}, merge(options, {
success: bind(this, function(_, info) {
this._logout();
options.success && options.success(info);
})
}));
},
/**
* Saves a user.
*
* @override
* @see Kinvey.Entity#save
*/
save: function(options) {
options || (options = {});
if(!this.isLoggedIn) {
options.error && options.error({
code: Kinvey.Error.OPERATION_DENIED,
description: 'This operation is not allowed',
debug: 'Cannot save a user which is not logged in.'
}, {});
return;
}
// Parent method will always update.
Kinvey.Entity.prototype.save.call(this, merge(options, {
success: bind(this, function(_, info) {
this._saveToDisk();// Refresh cache.
options.success && options.success(this, info);
})
}));
},
/**
* Removes any user saved on disk.
*
* @private
*/
_deleteFromDisk: function() {
Storage.remove(CACHE_TAG());
},
/**
* Performs login.
*
* @private
* @param {Object} attr Attributes.
* @param {Object} options Options.
*/
_doLogin: function(attr, options) {
// Make sure only one user is active at the time.
var currentUser = Kinvey.getCurrentUser();
if(null !== currentUser) {
currentUser.logout(merge(options, {
success: bind(this, function() {
this._doLogin(attr, options);
})
}));
return;
}
// Send request.
this.store.login(attr, merge(options, {
success: bind(this, function(response, info) {
// Extract token.
var token = response._kmd.authtoken;
delete response._kmd.authtoken;
// Update attributes. This does not include the users password.
this.attr = this._parseAttr(response);
this._login(token);
options.success && options.success(this, info);
})
}));
},
/**
* Marks user as logged in. This method should never be called standalone,
* but always involve some network request.
*
* @private
* @param {string} token Token.
*/
_login: function(token) {
// The master secret does not need a current user.
if(null == Kinvey.masterSecret) {
Kinvey.setCurrentUser(this);
this.isLoggedIn = true;
this.token = token;
this._saveToDisk();
}
},
/**
* Marks user no longer as logged in.
*
* @private
*/
_logout: function() {
// The master secret does not need a current user.
if(null == Kinvey.masterSecret) {
Kinvey.setCurrentUser(null);
this.isLoggedIn = false;
this.token = null;
this._deleteFromDisk();
}
},
/**
* Saves current user to disk.
*
* @private
*/
_saveToDisk: function() {
Storage.set(CACHE_TAG(), {
token: this.token,
user: this.toJSON()
});
}
}, {
/** @lends Kinvey.User */
/**
* Creates the current user.
*
* @example <code>
* Kinvey.User.create({
* username: 'username'
* }, {
* success: function(user) {
* console.log('User created', user);
* },
* error: function(error) {
* console.log('User not created', error.message);
* }
* });
* </code>
*
* @param {Object} attr Attributes.
* @param {Object} [options]
* @param {function(user)} [options.success] Success callback.
* @param {function(error)} [options.error] Failure callback.
* @return {Kinvey.User} The user instance (not necessarily persisted yet).
*/
create: function(attr, options) {
options || (options = {});
// Make sure only one user is active at the time.
var currentUser = Kinvey.getCurrentUser();
if(null !== currentUser) {
currentUser.logout(merge(options, {
success: function() {
Kinvey.User.create(attr, options);
}
}));
return;
}
// Create a new user.
var user = options._target || new Kinvey.User(attr);
Kinvey.Entity.prototype.save.call(user, merge(options, {
success: bind(user, function(_, info) {
// Extract token.
var token = this.attr._kmd.authtoken;
delete this.attr._kmd.authtoken;
this._login(token);
options.success && options.success(this, info);
})
}));
return user;// return the instance
},
/**
* Initializes a current user. Returns the current user, otherwise creates
* an anonymous user. This method is called internally when doing a network
* request. Manually invoking this function is however allowed.
*
* @param {Object} [options]
* @param {function(user)} [options.success] Success callback.
* @param {function(error)} [options.error] Failure callback.
* @return {Kinvey.User} The user instance. (not necessarily persisted yet).
*/
init: function(options) {
options || (options = {});
// Check whether there already is a current user.
var user = Kinvey.getCurrentUser();
if(null !== user) {
options.success && options.success(user, {});
return user;
}
// No cached user available, create anonymous user.
return Kinvey.User.create({}, options);
},
/**
* Restores user stored locally on the device. This method is called by
* Kinvey.init(), and should not be called anywhere else.
*
* @private
*/
_restore: function() {
// Return if there already is a current user. Safety check.
if(null !== Kinvey.getCurrentUser()) {
return;
}
// Retrieve and restore user from storage.
var data = Storage.get(CACHE_TAG());
if(null !== data && null != data.token && null != data.user) {
new Kinvey.User(data.user)._login(data.token);
}
}
});
// Define the Kinvey UserCollection class.
Kinvey.UserCollection = Kinvey.Collection.extend({
// Mapped entity class.
entity: Kinvey.User,
/**
* Creates new user collection.
*
* @example <code>
* var collection = new Kinvey.UserCollection();
* </code>
*
* @name Kinvey.UserCollection
* @constructor
* @extends Kinvey.Collection
* @param {Object} options Options.
*/
constructor: function(options) {
Kinvey.Collection.prototype.constructor.call(this, 'user', options);
},
/** @lends Kinvey.UserCollection# */
/**
* Clears collection. This action is not allowed.
*
* @override
*/
clear: function(options) {
options && options.error && options.error({
code: Kinvey.Error.OPERATION_DENIED,
description: 'This operation is not allowed',
debug: ''
});
}
});
// Define the Kinvey Metadata class.
Kinvey.Metadata = Base.extend({
/**
* Creates a new metadata instance.
*
* @name Kinvey.Metadata
* @constructor
* @param {Object} [attr] Attributes containing metadata.
*/
constructor: function(attr) {
attr || (attr = {});
this.acl = attr._acl || {};
this.kmd = attr._kmd || {};
},
/** @lends Kinvey.Metadata# */
/**
* Adds item read permissions for user.
*
* @param {string} user User id.
*/
addReader: function(user) {
this.acl.r || (this.acl.r = []);
if(-1 === this.acl.r.indexOf(user)) {
this.acl.r.push(user);
}
},
/**
* Adds item write permissions for user.
*
* @param {string} user User id.
*/
addWriter: function(user) {
this.acl.w || (this.acl.w = []);
if(-1 === this.acl.w.indexOf(user)) {
this.acl.w.push(user);
}
},
/**
* Returns all readers.
*
* @return {Array} List of readers.
*/
getReaders: function() {
return this.acl.r || [];
},
/**
* Returns all writers.
*
* @return {Array} List of writers.
*/
getWriters: function() {
return this.acl.w || [];
},
/**
* Returns whether the current user owns the item. This method
* is only useful when the class is created with a predefined
* ACL.
*
* @returns {boolean}
*/
isOwner: function() {
var owner = this.acl.creator;
var currentUser = Kinvey.getCurrentUser();
// If owner is undefined, assume entity is just created.
if(owner) {
return !!currentUser && owner === currentUser.getId();
}
return true;
},
/**
* Returns last modified date, or null if not set.
*
* @return {string} ISO-8601 formatted date.
*/
lastModified: function() {
return this.kmd.lmt || null;
},
/**
* Returns whether the current user has write permissions.
*
* @returns {Boolean}
*/
hasWritePermissions: function() {
if(this.isOwner() || this.isGloballyWritable()) {
return true;
}
var currentUser = Kinvey.getCurrentUser();
if(currentUser && this.acl.w) {
return -1 !== this.acl.w.indexOf(currentUser.getId());
}
return false;
},
/**
* Returns whether the item is globally readable.
*
* @returns {Boolean}
*/
isGloballyReadable: function() {
return !!this.acl.gr;
},
/**
* Returns whether the item is globally writable.
*
* @returns {Boolean}
*/
isGloballyWritable: function() {
return !!this.acl.gw;
},
/**
* Removes item read permissions for user.
*
* @param {string} user User id.
*/
removeReader: function(user) {
if(this.acl.r) {
var index = this.acl.r.indexOf(user);
if(-1 !== index) {
this.acl.r.splice(index, 1);
}
}
},
/**
* Removes item write permissions for user.
*
* @param {string} user User id.
*/
removeWriter: function(user) {
if(this.acl.w) {
var index = this.acl.w.indexOf(user);
if(-1 !== index) {
this.acl.w.splice(index, 1);
}
}
},
/**
* Sets whether the item is globally readable.
*
* @param {Boolean} flag
*/
setGloballyReadable: function(flag) {
this.acl.gr = !!flag;
},
/**
* Sets whether the item is globally writable.
*
* @param {Boolean} flag
*/
setGloballyWritable: function(flag) {
this.acl.gw = !!flag;
},
/**
* Returns JSON representation. Used by JSON#stringify.
*
* @returns {object} JSON representation.
*/
toJSON: function() {
return {
_acl: this.acl,
_kmd: this.kmd
};
}
});
/**
* Kinvey Resource namespace definition.
*
* @namespace
*/
Kinvey.Resource = {
/**
* Destroys a file.
*
* @param {string} name Filename.
* @param {Object} [options] Options.
*/
destroy: function(name, options) {
Kinvey.Resource._store || (Kinvey.Resource._store = Kinvey.Store.factory(Kinvey.Store.BLOB));
Kinvey.Resource._store.remove({ name: name }, options);
},
/**
* Downloads a file, or returns the download URI.
*
* @param {string} name Filename.
* @param {Object} [options] Options.
*/
download: function(name, options) {
Kinvey.Resource._store || (Kinvey.Resource._store = Kinvey.Store.factory(Kinvey.Store.BLOB));
Kinvey.Resource._store.query(name, options);
},
/**
* Uploads a file.
*
* @param {Object} file File.
* @param {Object} [options] Options.
* @throws {Error} On invalid file.
*/
upload: function(file, options) {
// Validate file.
if(null == file || null == file.name || null == file.data) {
throw new Error('File should be an object containing name and data');
}
Kinvey.Resource._store || (Kinvey.Resource._store = Kinvey.Store.factory(Kinvey.Store.BLOB));
Kinvey.Resource._store.save(file, options);
},
/**
* We only need one instance of the blob store.
*
* @private
*/
_store: null
};
// Define the Kinvey Query class.
Kinvey.Query = Base.extend({
// Key under condition.
currentKey: null,
/**
* Creates a new query.
*
* @example <code>
* var query = new Kinvey.Query();
* </code>
*
* @name Kinvey.Query
* @constructor
* @param {Object} [builder] One of Kinvey.Query.* builders.
*/
constructor: function(builder) {
this.builder = builder || Kinvey.Query.factory();
},
/** @lends Kinvey.Query# */
/**
* Sets an all condition on the current key.
*
* @example <code>
* // Attribute "field" must be an Array containing both "foo" and "bar".
* var query = new Kinvey.Query();
* query.on('field').all(['foo', 'bar']);
* </code>
*
* @param {Array} expected Array of expected values.
* @throws {Error}
* <ul>
* <li>On invalid argument,</li>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
all: function(expected) {
// if(!(expected instanceof Array)) {
// throw new Error('Argument must be of type Array');
// }
this._set(Kinvey.Query.ALL, expected);
return this;
},
/**
* Sets an AND condition.
*
* @example <code>
* // Attribute "field1" must have value "foo", and "field2" must have value "bar".
* var query1 = new Kinvey.Query();
* var query2 = new Kinvey.Query();
* query1.on('field1').equal('foo');
* query2.on('field2').equal('bar');
* query1.and(query2);
* </code>
*
* @param {Kinvey.Query} query Query to AND.
* @throws {Error} On invalid instance.
* @return {Kinvey.Query} Current instance.
*/
and: function(query) {
this._set(Kinvey.Query.AND, query.builder, true);// do not throw.
return this;
},
/**
* Sets an equal condition on the current key.
*
* @example <code>
* // Attribute "field" must have value "foo".
* var query = new Kinvey.Query();
* query.on('field').equal('foo');
* </code>
*
* @param {*} expected Expected value.
* @throws {Error}
* <ul>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
equal: function(expected) {
this._set(Kinvey.Query.EQUAL, expected);
return this;
},
/**
* Sets an exist condition on the current key.
*
* @example <code>
* // Attribute "field" must exist.
* var query = new Kinvey.Query();
* query.on('field').exist();
* </code>
*
* @param {boolean} [expected] Boolean indicating whether field must be
* present. Defaults to true.
* @throws {Error}
* <ul>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
exist: function(expected) {
// Make sure the argument is of type boolean.
expected = 'undefined' !== typeof expected ? !!expected : true;
this._set(Kinvey.Query.EXIST, expected);
return this;
},
/**
* Sets a greater than condition on the current key.
*
* @example <code>
* // Attribute "field" must have a value greater than 25.
* var query = new Kinvey.Query();
* query.on('field').greaterThan(25);
* </code>
*
* @param {*} value Compared value.
* @throws {Error}
* <ul>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
greaterThan: function(value) {
this._set(Kinvey.Query.GREATER_THAN, value);
return this;
},
/**
* Sets a greater than equal condition on the current key.
*
* @example <code>
* // Attribute "field" must have a value greater than or equal to 25.
* var query = new Kinvey.Query();
* query.on('field').greaterThanEqual(25);
* </code>
*
* @param {*} value Compared value.
* @throws {Error}
* <ul>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
greaterThanEqual: function(value) {
this._set(Kinvey.Query.GREATER_THAN_EQUAL, value);
return this;
},
/**
* Sets an in condition on the current key. Method has underscore
* postfix since "in" is a reserved word.
*
* @example <code>
* // Attribute "field" must be an Array containing "foo" and/or "bar".
* var query = new Kinvey.Query();
* query.on('field').in_(['foo', 'bar']);
* </code>
*
* @param {Array} expected Array of expected values.
* @throws {Error}
* <ul>
* <li>On invalid argument,</li>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
in_: function(expected) {
// if(!(expected instanceof Array)) {
// throw new Error('Argument must be of type Array');
// }
this._set(Kinvey.Query.IN, expected);
return this;
},
/**
* Sets a less than condition on the current key.
*
* @example <code>
* // Attribute "field" must have a value less than 25.
* var query = new Kinvey.Query();
* query.on('field').lessThan(25);
* </code>
*
* @param {*} value Compared value.
* @throws {Error}
* <ul>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
lessThan: function(value) {
this._set(Kinvey.Query.LESS_THAN, value);
return this;
},
/**
* Sets a less than equal condition on the current key.
*
* @example <code>
* // Attribute "field" must have a value less than or equal to 25.
* var query = new Kinvey.Query();
* query.on('field').lessThanEqual(25);
* </code>
*
* @param {*} value Compared value.
* @throws {Error}
* <ul>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
lessThanEqual: function(value) {
this._set(Kinvey.Query.LESS_THAN_EQUAL, value);
return this;
},
/**
* Sets a near sphere condition on the current key.
*
* @example <code>
* // Attribute "field" must be a point within a 10 mile radius of [-71, 42].
* var query = new Kinvey.Query();
* query.on('field').nearSphere([-71, 42], 10);
* </code>
*
* @param {Array} point Point [lng, lat].
* @param {number} [maxDistance] Max distance from point in miles.
* @throws {Error}
* <ul>
* <li>On invalid argument,</li>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
nearSphere: function(point, maxDistance) {
if(null == point || 2 !== point.length) {
// if(!(point instanceof Array) || 2 !== point.length) {
throw new Error('Point must be of type Array[lng, lat]');
}
this._set(Kinvey.Query.NEAR_SPHERE, {
point: point,
maxDistance: 'undefined' !== typeof maxDistance ? maxDistance : null
});
return this;
},
/**
* Sets a not equal condition on the current key.
*
* @example <code>
* // Attribute "field" must have a value not equal to "foo".
* var query = new Kinvey.Query();
* query.on('field').notEqual('foo');
* </code>
*
* @param {*} value Unexpected value.
* @throws {Error}
* <ul>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
notEqual: function(unexpected) {
this._set(Kinvey.Query.NOT_EQUAL, unexpected);
return this;
},
/**
* Sets a not in condition on the current key.
*
* @example <code>
* // Attribute "field" must have a value not equal to "foo" or "bar".
* var query = new Kinvey.Query();
* query.on('field').notIn(['foo', 'bar']);
* </code>
*
* @param {Array} unexpected Array of unexpected values.
* @throws {Error}
* <ul>
* <li>On invalid argument,</li>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
notIn: function(unexpected) {
// if(!(unexpected instanceof Array)) {
// throw new Error('Argument must be of type Array');
// }
this._set(Kinvey.Query.NOT_IN, unexpected);
return this;
},
/**
* Sets key under condition.
*
* @param {string} key Key under condition.
* @return {Kinvey.Query} Current instance.
*/
on: function(key) {
this.currentKey = key;
return this;
},
/**
* Sets an OR condition.
*
* @example <code>
* // Attribute "field1" must have value "foo", or "field2" must have value "bar".
* var query1 = new Kinvey.Query();
* var query2 = new Kinvey.Query();
* query1.on('field1').equal('foo');
* query2.on('field2').equal('bar');
* query1.or(query2);
* </code>
*
* @param {Kinvey.Query} query Query to OR.
* @throws {Error} On invalid instance.
* @return {Kinvey.Query} Current instance.
*/
or: function(query) {
this._set(Kinvey.Query.OR, query.builder, true);// do not throw.
return this;
},
/**
* Sets a not in condition on the current key.
*
* @example <code>
* // Attribute "field" must have a value starting with foo.
* var query = new Kinvey.Query();
* query.on('field').regex(/^foo/);
* </code>
*
* @param {object} expected Regular expression.
* @throws {Error} On invalid regular expression.
* @return {Kinvey.Query} Current instance.
*/
regex: function(expected) {
this._set(Kinvey.Query.REGEX, expected);
return this;
},
/**
* Resets all filters.
*
* @return {Kinvey.Query} Current instance.
*/
reset: function() {
this.builder.reset();
return this;
},
/**
* Sets the query limit.
*
* @param {number} limit Limit.
* @return {Kinvey.Query} Current instance.
*/
setLimit: function(limit) {
this.builder.setLimit(limit);
return this;
},
/**
* Sets the query skip.
*
* @param {number} skip Skip.
* @return {Kinvey.Query} Current instance.
*/
setSkip: function(skip) {
this.builder.setSkip(skip);
return this;
},
/**
* Sets a size condition on the current key.
*
* @example <code>
* // Attribute "field" must be an Array with 25 elements.
* var query = new Kinvey.Query();
* query.on('field').size(25);
* </code>
*
* @param {number} expected Expected value.
* @throws {Error}
* <ul>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
size: function(expected) {
this._set(Kinvey.Query.SIZE, expected);
return this;
},
/**
* Sets the query sort.
*
* @param {number} [direction] Sort direction, or null to reset sort.
* Defaults to ascending.
* @return {Kinvey.Query} Current instance.
*/
sort: function(direction) {
if(null !== direction) {
direction = direction || Kinvey.Query.ASC;
}
this.builder.setSort(this.currentKey, direction);
return this;
},
/**
* Returns JSON representation.
*
* @return {Object} JSON representation.
*/
toJSON: function() {
return this.builder.toJSON();
},
/**
* Sets a within box condition on the current key.
*
* @example <code>
* // Attribute "field" must be a point within the box [-72, 41], [-70, 43].
* var query = new Kinvey.Query();
* query.on('field').withinBox([[-72, 41], [-70, 43]]);
* </code>
*
* @param {Array} points Array of two points [[lng, lat], [lng, lat]].
* @throws {Error}
* <ul>
* <li>On invalid argument,</li>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
withinBox: function(points) {
if(null == points || 2 !== points.length) {
// if(!(points instanceof Array) || 2 !== points.length) {
throw new Error('Points must be of type Array[[lng, lat], [lng, lat]]');
}
this._set(Kinvey.Query.WITHIN_BOX, points);
return this;
},
/**
* Sets a within center sphere condition on the current key.
*
* @example <code>
* // Attribute "field" must be a point within a 10 mile radius of [-71, 42].
* var query = new Kinvey.Query();
* query.on('field').withinCenterSphere([-72, 41], 0.0025);
* </code>
*
* @param {Array} point Point [lng, lat].
* @param {number} radius Radius in radians.
* @throws {Error}
* <ul>
* <li>On invalid argument,</li>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
withinCenterSphere: function(point, radius) {
if(null == point || 2 !== point.length) {
// if(!(point instanceof Array) || 2 !== point.length) {
throw new Error('Point must be of type Array[lng, lat]');
}
this._set(Kinvey.Query.WITHIN_CENTER_SPHERE, {
center: point,
radius: radius
});
return this;
},
/**
* Sets a within polygon condition on the current key.
*
* @param {Array} points Array of points [[lng, lat], ...].
* @throws {Error}
* <ul>
* <li>On invalid argument,</li>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
* @return {Kinvey.Query} Current instance.
*/
withinPolygon: function(points) {
// if(!(points instanceof Array)) {
// throw new Error('Points must be of type Array[[lng, lat], ...]');
// }
this._set(Kinvey.Query.WITHIN_POLYGON, points);
return this;
},
/**
* Helper function to forward condition to builder.
*
* @private
* @throws {Error}
* <ul>
* <li>When there is no key under condition,</li>
* <li>When the condition is not supported by the builder.</li>
* </ul>
*/
_set: function(operator, value, bypass) {
// Bypass flag can be used to avoid throwing an error.
if(null === this.currentKey && !bypass) {
throw new Error('Key under condition must not be null');
}
this.builder.addCondition(this.currentKey, operator, value);
}
}, {
/** @lends Kinvey.Query */
// Basic operators.
/**
* Equal operator. Checks if an element equals the specified expression.
*
* @constant
*/
EQUAL: 16,
/**
* Exist operator. Checks if an element exists.
*
* @constant
*/
EXIST: 17,
/**
* Less than operator. Checks if an element is less than the specified
* expression.
*
* @constant
*/
LESS_THAN: 18,
/**
* Less than or equal to operator. Checks if an element is less than or
* equal to the specified expression.
*
* @constant
*/
LESS_THAN_EQUAL: 19,
/**
* Greater than operator. Checks if an element is greater than the
* specified expression.
*
* @constant
*/
GREATER_THAN: 20,
/**
* Greater than or equal to operator. Checks if an element is greater
* than or equal to the specified expression.
*
* @constant
*/
GREATER_THAN_EQUAL: 21,
/**
* Not equal operator. Checks if an element does not equals the
* specified expression.
*
* @constant
*/
NOT_EQUAL: 22,
/**
* Regular expression operator. Checks if an element matches the specified
* expression.
*
* @constant
*/
REGEX: 23,
// Geoqueries.
/**
* Near sphere operator. Checks if an element is close to the point in
* the specified expression.
*
* @constant
*/
NEAR_SPHERE: 1024,
/**
* Within box operator. Checks if an element is within the box shape as
* defined by the expression.
*
* @constant
*/
WITHIN_BOX: 1025,
/**
* Within center sphere operator. Checks if an element is within a
* center sphere as defined by the expression.
*
* @constant
*/
WITHIN_CENTER_SPHERE: 1026,
/**
* Within polygon operator. Checks if an element is within a polygon
* shape as defined by the expression.
*
* @constant
*/
WITHIN_POLYGON: 1027,
/**
* Max distance operator. Checks if an element is within a certain
* distance to the point in the specified expression. This operator
* requires the use of the near operator as well.
*
* @constant
*/
MAX_DISTANCE: 1028,
// Set membership
/**
* In operator. Checks if an element matches any values in the specified
* expression.
*
* @constant
*/
IN: 2048,
/**
* Not in operator. Checks if an element does not match any value in the
* specified expression.
*
* @constant
*/
NOT_IN: 2049,
// Joining operators.
/**
* And operator. Supported implicitly.
*
* @constant
*/
AND: 4096,
/**
* Or operator. Not supported.
*
* @constant
*/
OR: 4097,
// Array operators.
/**
* All operator. Checks if an element matches all values in the
* specified expression
*
* @constant
*/
ALL: 8192,
/**
* Size operator. Checks if the size of an element matches the specified
* expression.
*
* @constant
*/
SIZE: 8193,
// Sort operators.
/**
* Ascending sort operator.
*
* @constant
*/
ASC: 16384,
/**
* Descending sort operator.
*
* @constant
*/
DESC: 16385,
/**
* Returns a query builder.
*
* @return {Object} One of Kinvey.Query.* builders.
*/
factory: function() {
// Currently, only the Mongo builder is supported.
return new Kinvey.Query.MongoBuilder();
}
});
// Define the Kinvey Query MongoBuilder class.
Kinvey.Query.MongoBuilder = Base.extend({
// Conditions.
limit: null,
skip: null,
sort: null,
query: null,
/**
* Creates a new MongoDB query builder.
*
* @name Kinvey.Query.MongoBuilder
* @constructor
*/
constructor: function() {
//
},
/** @lends Kinvey.Query.MongoBuilder# */
/**
* Adds condition.
*
* @param {string} field Field.
* @param {number} condition Condition.
* @param {*} value Expression.
* @throws {Error} On unsupported condition.
*/
addCondition: function(field, condition, value) {
switch(condition) {
// Basic operators.
// @see http://www.mongodb.org/display/DOCS/Advanced+Queries
case Kinvey.Query.EQUAL:
this._set(field, value);
break;
case Kinvey.Query.EXIST:
this._set(field, { $exists: value });
break;
case Kinvey.Query.LESS_THAN:
this._set(field, {$lt: value});
break;
case Kinvey.Query.LESS_THAN_EQUAL:
this._set(field, {$lte: value});
break;
case Kinvey.Query.GREATER_THAN:
this._set(field, {$gt: value});
break;
case Kinvey.Query.GREATER_THAN_EQUAL:
this._set(field, {$gte: value});
break;
case Kinvey.Query.NOT_EQUAL:
this._set(field, {$ne: value});
break;
case Kinvey.Query.REGEX:
// Filter through RegExp, this will throw an error on invalid regex.
var regex = new RegExp(value);
var options = ((regex.global) ? 'g' : '') + ((regex.ignoreCase) ? 'i' : '') + ((regex.multiline) ? 'm' : '');
this._set(field, { $regex: regex.source, $options: options });
break;
// Geoqueries.
// @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing
case Kinvey.Query.NEAR_SPHERE:
var query = { $nearSphere: value.point };
value.maxDistance && (query.$maxDistance = value.maxDistance);
this._set(field, query);
break;
case Kinvey.Query.WITHIN_BOX:
this._set(field, {$within: {$box: value}});
break;
case Kinvey.Query.WITHIN_CENTER_SPHERE:
this._set(field, {$within: {$centerSphere: [value.center, value.radius] }});
break;
case Kinvey.Query.WITHIN_POLYGON:
this._set(field, {$within: {$polygon: value}});
break;
// Set membership.
// @see http://www.mongodb.org/display/DOCS/Advanced+Queries
case Kinvey.Query.IN:
this._set(field, {$in: value});
break;
case Kinvey.Query.NOT_IN:
this._set(field, {$nin: value});
break;
// Joining operators.
case Kinvey.Query.AND:
if(!(value instanceof Kinvey.Query.MongoBuilder)) {
throw new Error('Query must be of type Kinvey.Query.Mongobuilder');
}
this.query = { $and: [this.query || {}, value.query || {}] };
break;
case Kinvey.Query.OR:
if(!(value instanceof Kinvey.Query.MongoBuilder)) {
throw new Error('Query must be of type Kinvey.Query.Mongobuilder');
}
this.query = { $or: [this.query || {}, value.query || {}] };
break;
// Array operators.
// @see http://www.mongodb.org/display/DOCS/Advanced+Queries
case Kinvey.Query.ALL:
this._set(field, {$all: value});
break;
case Kinvey.Query.SIZE:
this._set(field, {$size: value});
break;
// Other operator.
default:
throw new Error('Condition ' + condition + ' is not supported');
}
},
/**
* Resets query.
*
*/
reset: function() {
this.query = null;
},
/**
* Sets query limit.
*
* @param {number} limit Limit, or null to reset limit.
*/
setLimit: function(limit) {
this.limit = limit;
},
/**
* Sets query skip.
*
* @param {number} skip Skip, or null to reset skip.
*/
setSkip: function(skip) {
this.skip = skip;
},
/**
* Sets query sort.
*
* @param {string} field Field.
* @param {number} direction Sort direction, or null to reset sort.
*/
setSort: function(field, direction) {
if(null == direction) {
this.sort = null;// hard reset
return;
}
// Set sort value.
var value = Kinvey.Query.ASC === direction ? 1 : -1;
this.sort = {};// reset
this.sort[field] = value;
},
/**
* Returns JSON representation. Used by JSON#stringify.
*
* @return {Object} JSON representation.
*/
toJSON: function() {
var result = {};
this.limit && (result.limit = this.limit);
this.skip && (result.skip = this.skip);
this.sort && (result.sort = this.sort);
this.query && (result.query = this.query);
return result;
},
/**
* Helper function to add expression to field.
*
* @private
*/
_set: function(field, expression) {
this.query || (this.query = {});
if(!(expression instanceof Object)) {// simple condition
this.query[field] = expression;
return;
}
// Complex condition.
this.query[field] instanceof Object || (this.query[field] = {});
for(var operator in expression) {
if(expression.hasOwnProperty(operator)) {
this.query[field][operator] = expression[operator];
}
}
}
});
// Define the Kinvey Aggregation class.
Kinvey.Aggregation = Base.extend({
/**
* Creates a new aggregation.
*
* @example <code>
* var aggregation = new Kinvey.Aggregation();
* </code>
*
* @name Kinvey.Aggregation
* @constructor
* @param {Object} [builder] One of Kinvey.Aggregation.* builders.
*/
constructor: function(builder) {
this.builder = builder || Kinvey.Aggregation.factory();
},
/** @lends Kinvey.Aggregation# */
/**
* Adds key under condition.
*
* @param {string} key Key under condition.
* @return {Kinvey.Aggregation} Current instance.
*/
on: function(key) {
this.builder.on(key);
return this;
},
/**
* Sets the finalize function. Currently not supported.
*
* @param {function(doc, counter)} fn Finalize function.
* @return {Kinvey.Aggregation} Current instance.
*/
setFinalize: function(fn) {
this.builder.setFinalize(fn);
},
/**
* Sets the initial counter object.
*
* @param {Object} counter Counter object.
* @return {Kinvey.Aggregation} Current instance.
*/
setInitial: function(counter) {
this.builder.setInitial(counter);
return this;
},
/**
* Sets query.
*
* @param {Kinvey.Query} [query] query.
* @throws {Error} On invalid instance.
* @return {Kinvey.Aggregation} Current instance.
*/
setQuery: function(query) {
if(query && !(query instanceof Kinvey.Query)) {
throw new Error('Query must be an instanceof Kinvey.Query');
}
this.builder.setQuery(query);
return this;
},
/**
* Sets the reduce function.
*
* @param {function(doc, counter)} fn Reduce function.
* @return {Kinvey.Aggregation} Current instance.
*/
setReduce: function(fn) {
this.builder.setReduce(fn);
return this;
},
/**
* Returns JSON representation.
*
* @return {Object} JSON representation.
*/
toJSON: function() {
return this.builder.toJSON();
}
}, {
/** @lends Kinvey.Aggregation */
/**
* Returns an aggregation builder.
*
* @return {Object} One of Kinvey.Aggregation.* builders.
*/
factory: function() {
// Currently, only the Mongo builder is supported.
return new Kinvey.Aggregation.MongoBuilder();
}
});
// Define the Kinvey Aggregation MongoBuilder class.
Kinvey.Aggregation.MongoBuilder = Base.extend({
// Fields.
finalize: function() { },
initial: { count: 0 },
keys: null,
reduce: function(doc, out) {
out.count++;
},
query: null,
/**
* Creates a new MongoDB aggregation builder.
*
* @name Kinvey.Aggregation.MongoBuilder
* @constructor
*/
constructor: function() {
// Set keys property explicitly on this instance, otherwise the prototype
// will be overloaded.
this.keys = {};
},
/** @lends Kinvey.Aggregation.MongoBuilder# */
/**
* Adds key under condition.
*
* @param {string} key Key under condition.
* @return {Kinvey.Aggregation} Current instance.
*/
on: function(key) {
this.keys[key] = true;
},
/**
* Sets the finalize function.
*
* @param {function(counter)} fn Finalize function.
*/
setFinalize: function(fn) {
this.finalize = fn;
},
/**
* Sets the initial counter object.
*
* @param {Object} counter Counter object.
*/
setInitial: function(counter) {
this.initial = counter;
},
/**
* Sets query.
*
* @param {Kinvey.Query} [query] query.
*/
setQuery: function(query) {
this.query = query;
return this;
},
/**
* Sets the reduce function.
*
* @param {function(doc, out)} fn Reduce function.
*/
setReduce: function(fn) {
this.reduce = fn;
},
/**
* Returns JSON representation.
*
* @return {Object} JSON representation.
*/
toJSON: function() {
// Required fields.
var result = {
finalize: this.finalize.toString(),
initial: this.initial,
key: this.keys,
reduce: this.reduce.toString()
};
// Optional fields.
var query = this.query && this.query.toJSON().query;
query && (result.condition = query);
return result;
}
});
/**
* Kinvey Store namespace. Home to all stores.
*
* @namespace
*/
Kinvey.Store = {
/**
* AppData store.
*
* @constant
*/
APPDATA: 'appdata',
/**
* Blob store.
*
* @constant
*/
BLOB: 'blob',
/**
* Returns store.
*
* @param {string} name Store name.
* @param {string} collection Collection name.
* @param {Object} options Store options.
* @return {Kinvey.Store.*} One of Kinvey.Store.*.
*/
factory: function(name, collection, options) {
// Create store by name.
if(Kinvey.Store.BLOB === name) {
return new Kinvey.Store.Blob(collection, options);
}
// By default, use the AppData store.
return new Kinvey.Store.AppData(collection, options);
}
};
// Define the Kinvey.Store.AppData class.
Kinvey.Store.AppData = Base.extend({
// Store name.
name: Kinvey.Store.APPDATA,
// Default options.
options: {
timeout: 10000,// Timeout in ms.
success: function() { },
error: function() { }
},
/**
* Creates a new store.
*
* @name Kinvey.Store.AppData
* @constructor
* @param {string} collection Collection name.
* @param {Object} [options] Options.
*/
constructor: function(collection, options) {
this.api = Kinvey.Store.AppData.USER_API === collection ? Kinvey.Store.AppData.USER_API : Kinvey.Store.AppData.APPDATA_API;
this.collection = collection;
// Options.
options && this.configure(options);
},
/** @lends Kinvey.Store.AppData# */
/**
* Aggregates objects from the store.
*
* @param {Object} aggregation Aggregation.
* @param {Object} [options] Options.
*/
aggregate: function(aggregation, options) {
var url = this._getUrl({ id: '_group' });
this._send('POST', url, JSON.stringify(aggregation), options);
},
/**
* Configures store.
*
* @param {Object} options
* @param {function(response, info)} [options.success] Success callback.
* @param {function(error, info)} [options.error] Failure callback.
* @param {integer} [options.timeout] Request timeout (in milliseconds).
*/
configure: function(options) {
'undefined' !== typeof options.timeout && (this.options.timeout = options.timeout);
options.success && (this.options.success = options.success);
options.error && (this.options.error = options.error);
},
/**
* Logs in user.
*
* @param {Object} object
* @param {Object} [options] Options.
*/
login: function(object, options) {
var url = this._getUrl({ id: 'login' });
this._send('POST', url, JSON.stringify(object), options);
},
/**
* Logs out user.
*
* @param {Object} object
* @param {Object} [options] Options.
*/
logout: function(object, options) {
var url = this._getUrl({ id: '_logout' });
this._send('POST', url, null, options);
},
/**
* Queries the store for a specific object.
*
* @param {string} id Object id.
* @param {Object} [options] Options.
*/
query: function(id, options) {
options || (options = {});
var url = this._getUrl({ id: id, resolve: options.resolve });
this._send('GET', url, null, options);
},
/**
* Queries the store for multiple objects.
*
* @param {Object} query Query object.
* @param {Object} [options] Options.
*/
queryWithQuery: function(query, options) {
options || (options = {});
var url = this._getUrl({ query: query, resolve: options.resolve });
this._send('GET', url, null, options);
},
/**
* Removes object from the store.
*
* @param {Object} object Object to be removed.
* @param {Object} [options] Options.
*/
remove: function(object, options) {
var url = this._getUrl({ id: object._id });
this._send('DELETE', url, null, options);
},
/**
* Removes multiple objects from the store.
*
* @param {Object} query Query object.
* @param {Object} [options] Options.
*/
removeWithQuery: function(query, options) {
var url = this._getUrl({ query: query });
this._send('DELETE', url, null, options);
},
/**
* Saves object to the store.
*
* @param {Object} object Object to be saved.
* @param {Object} [options] Options.
*/
save: function(object, options) {
// Create the object if nonexistent, update otherwise.
var method = object._id ? 'PUT' : 'POST';
var url = this._getUrl({ id: object._id });
this._send(method, url, JSON.stringify(object), options);
},
/**
* Encodes value for use in query string.
*
* @private
* @param {*} value Value to be encoded.
* @return {string} Encoded value.
*/
_encode: function(value) {
if(value instanceof Object) {
value = JSON.stringify(value);
}
return encodeURIComponent(value);
},
/**
* Constructs URL.
*
* @private
* @param {Object} parts URL parts.
* @return {string} URL.
*/
_getUrl: function(parts) {
var url = '/' + this.api + '/' + Kinvey.appKey + '/';
// Only the AppData API has explicit collections.
if(Kinvey.Store.AppData.APPDATA_API === this.api && null != this.collection) {
url += this.collection + '/';
}
parts.id && (url += parts.id);
// Build query string.
var param = [];
if(null != parts.query) {
// Required query parts.
param.push('query=' + this._encode(parts.query.query || {}));
// Optional query parts.
parts.query.limit && param.push('limit=' + this._encode(parts.query.limit));
parts.query.skip && param.push('skip=' + this._encode(parts.query.skip));
parts.query.sort && param.push('sort=' + this._encode(parts.query.sort));
}
// Resolve references.
if(parts.resolve) {
param.push('resolve=' + parts.resolve.join(','));
}
// Android < 4.0 caches all requests aggressively. For now, work around
// by adding a cache busting query string.
param.push('_=' + new Date().getTime());
return url + '?' + param.join('&');
}
}, {
// Path constants.
APPDATA_API: 'appdata',
USER_API: 'user'
});
// Apply mixin.
Xhr.call(Kinvey.Store.AppData.prototype);
// Define the Kinvey.Store.Blob class.
Kinvey.Store.Blob = Base.extend({
// Store name.
name: Kinvey.Store.BLOB,
// Default options.
options: {
timeout: 10000,// Timeout in ms.
success: function() { },
error: function() { }
},
/**
* Creates a new store.
*
* @name Kinvey.Store.Blob
* @constructor
* @param {string} collection Collection name.
* @param {Object} [options] Options.
*/
constructor: function(collection, options) {
// Ignore the collection name, as the blob API has only one collection.
options && this.configure(options);
},
/** @lends Kinvey.Store.Blob# */
/**
* Configures store.
*
* @param {Object} options
* @param {function(response, info)} [options.success] Success callback.
* @param {function(error, info)} [options.error] Failure callback.
* @param {integer} [options.timeout] Request timeout (in milliseconds).
*/
configure: function(options) {
'undefined' !== typeof options.timeout && (this.options.timeout = options.timeout);
options.success && (this.options.success = options.success);
options.error && (this.options.error = options.error);
},
/**
* Downloads a file.
*
* @param {string} name Filename.
* @param {Object} [options] Options.
*/
query: function(name, options) {
options = this._options(options);
// Send request to obtain the download URL.
var url = this._getUrl('download-loc', name);
this._send('GET', url, null, merge(options, {
success: bind(this, function(response, info) {
// Stop here if the user wants us to.
if('undefined' !== typeof options.download && !options.download) {
return options.success(response, info);
}
// Otherwise, download the file.
this._xhr('GET', response.URI, null, merge(options, {
success: function(response, info) {
options.success({
name: name,
data: info.data, // [aks] we want the blob, not the text
}, info);
},
error: function(_, info) {
options.error({
error: Kinvey.Error.RESPONSE_PROBLEM,
description: 'There was a problem downloading the file.',
debug: ''
}, info);
}
}));
})
}));
},
/**
* Removes a file.
*
* @param {Object} file File to be removed.
* @param {Object} [options] Options.
* @throws {Error} On invalid file.
*/
remove: function(file, options) {
// Validate file.
if(null == file || null == file.name) {
throw new Error('File should be an object containing name');
}
options = this._options(options);
// Send request to obtain the delete URL.
var url = this._getUrl('remove-loc', file.name);
this._send('GET', url, null, merge(options, {
success: bind(this, function(response, info) {
// Delete the file.
this._xhr('DELETE', response.URI, null, merge(options, {
success: function(_, info) {
options.success && options.success(null, info);
},
error: function(_, info) {
options.error({
error: Kinvey.Error.RESPONSE_PROBLEM,
description: 'There was a problem deleting the file.',
debug: ''
}, info);
}
}));
})
}));
},
/**
* Uploads a file.
*
* @param {Object} file File to be uploaded.
* @param {Object} [options] Options.
*/
save: function(file, options) {
options = this._options(options);
// Send request to obtain the upload URL.
this._send('GET', this._getUrl('upload-loc', file.name), null, merge(options, {
success: bind(this, function(response, info) {
// Upload the file.
this._xhr('PUT', response.URI, file.data, merge(options, {
success: function(_, info) {
options.success(file, info);
},
error: function(_, info) {
options.error({
error: Kinvey.Error.RESPONSE_PROBLEM,
description: 'There was a problem uploading the file.',
debug: ''
}, info);
}
}));
})
}));
},
/**
* Constructs URL.
*
* @private
* @param {string} type One of download-loc, upload-loc or remove-loc.
* @param {string} filename Filename.
* @return {string} URL.
*/
_getUrl: function(type, filename) {
return '/' + Kinvey.Store.Blob.BLOB_API + '/' + Kinvey.appKey + '/' + type + '/' + filename;
},
/**
* Returns full options object.
*
* @private
* @param {Object} options Options.
* @return {Object} Options.
*/
_options: function(options) {
options || (options = {});
'undefined' !== typeof options.timeout || (options.timeout = this.options.timeout);
options.success || (options.success = this.options.success);
options.error || (options.error = this.options.error);
return options;
}
}, {
// Path constants.
BLOB_API: 'blob'
});
// Apply mixin.
Xhr.call(Kinvey.Store.Blob.prototype);
}.call(this));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment