leancloud-storage
Version:
LeanCloud JavaScript SDK.
434 lines (392 loc) • 12.1 kB
JavaScript
'use strict';
var _ = require('underscore');
var _require = require('./utils'),
isNullOrUndefined = _require.isNullOrUndefined;
var AV = global.AV || {};
// All internal configuration items
AV._config = AV._config || {};
var AVConfig = AV._config;
_.extend(AVConfig, {
// 服务器节点地区,默认中国大陆
region: 'cn',
// 服务器的 URL,默认初始化时被设置为大陆节点地址
APIServerURL: AVConfig.APIServerURL || '',
// 当前是否为 nodejs 环境
isNode: false,
// 禁用 currentUser,通常用于多用户环境
disableCurrentUser: false,
// Internal config can modifie the UserAgent
userAgent: null,
// set production environment or test environment
// 1: production environment, 0: test environment, null: default environment
applicationProduction: null
});
/**
* Contains all AV API classes and functions.
* @namespace AV
*/
// Check whether we are running in Node.js.
if (typeof process !== 'undefined' && process.versions && process.versions.node) {
AVConfig.isNode = true;
}
// Helpers
// -------
// Shared empty constructor function to aid in prototype-chain creation.
var EmptyConstructor = function EmptyConstructor() {};
// Helper function to correctly set up the prototype chain, for subclasses.
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
var inherits = function inherits(parent, protoProps, staticProps) {
var child;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor.
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
/** @ignore */
child = function child() {
parent.apply(this, arguments);
};
}
// Inherit class (static) properties from parent.
_.extend(child, parent);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
EmptyConstructor.prototype = parent.prototype;
child.prototype = new EmptyConstructor();
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) {
_.extend(child.prototype, protoProps);
}
// Add static properties to the constructor function, if supplied.
if (staticProps) {
_.extend(child, staticProps);
}
// Correctly set child's `prototype.constructor`.
child.prototype.constructor = child;
// Set a convenience property in case the parent's prototype is
// needed later.
child.__super__ = parent.prototype;
return child;
};
/**
* Call this method to set production environment variable.
* @function AV.setProduction
* @param {Boolean} production True is production environment,and
* it's true by default.
*/
AV.setProduction = function (production) {
if (!isNullOrUndefined(production)) {
AVConfig.applicationProduction = production ? 1 : 0;
} else {
// change to default value
AVConfig.applicationProduction = null;
}
};
/**
* Returns prefix for localStorage keys used by this instance of AV.
* @param {String} path The relative suffix to append to it.
* null or undefined is treated as the empty string.
* @return {String} The full key name.
* @private
*/
AV._getAVPath = function (path) {
if (!AV.applicationId) {
throw "You need to call AV.initialize before using AV.";
}
if (!path) {
path = "";
}
if (!_.isString(path)) {
throw "Tried to get a localStorage path that wasn't a String.";
}
if (path[0] === "/") {
path = path.substring(1);
}
return "AV/" + AV.applicationId + "/" + path;
};
/**
* Returns the unique string for this app on this machine.
* Gets reset when localStorage is cleared.
* @private
*/
AV._installationId = null;
AV._getInstallationId = function () {
// See if it's cached in RAM.
if (AV._installationId) {
return AV.Promise.resolve(AV._installationId);
}
// Try to get it from localStorage.
var path = AV._getAVPath("installationId");
return AV.localStorage.getItemAsync(path).then(function (_installationId) {
AV._installationId = _installationId;
if (!AV._installationId) {
// It wasn't in localStorage, so create a new one.
var hexOctet = function hexOctet() {
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
};
AV._installationId = hexOctet() + hexOctet() + "-" + hexOctet() + "-" + hexOctet() + "-" + hexOctet() + "-" + hexOctet() + hexOctet() + hexOctet();
return AV.localStorage.setItemAsync(path, AV._installationId);
} else {
return _installationId;
}
});
};
AV._parseDate = function (iso8601) {
var regexp = new RegExp("^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})" + "T" + "([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})" + "(.([0-9]+))?" + "Z$");
var match = regexp.exec(iso8601);
if (!match) {
return null;
}
var year = match[1] || 0;
var month = (match[2] || 1) - 1;
var day = match[3] || 0;
var hour = match[4] || 0;
var minute = match[5] || 0;
var second = match[6] || 0;
var milli = match[8] || 0;
return new Date(Date.UTC(year, month, day, hour, minute, second, milli));
};
// A self-propagating extend function.
AV._extend = function (protoProps, classProps) {
var child = inherits(this, protoProps, classProps);
child.extend = this.extend;
return child;
};
// Helper function to get a value from a Backbone object as a property
// or as a function.
AV._getValue = function (object, prop) {
if (!(object && object[prop])) {
return null;
}
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
};
/**
* Converts a value in a AV Object into the appropriate representation.
* This is the JS equivalent of Java's AV.maybeReferenceAndEncode(Object)
* if seenObjects is falsey. Otherwise any AV.Objects not in
* seenObjects will be fully embedded rather than encoded
* as a pointer. This array will be used to prevent going into an infinite
* loop because we have circular references. If <seenObjects>
* is set, then none of the AV Objects that are serialized can be dirty.
* @private
*/
AV._encode = function (value, seenObjects, disallowObjects) {
if (value instanceof AV.Object) {
if (disallowObjects) {
throw "AV.Objects not allowed here";
}
if (!seenObjects || _.include(seenObjects, value) || !value._hasData) {
return value._toPointer();
}
if (!value.dirty()) {
seenObjects = seenObjects.concat(value);
return AV._encode(value._toFullJSON(seenObjects), seenObjects, disallowObjects);
}
throw "Tried to save an object with a pointer to a new, unsaved object.";
}
if (value instanceof AV.ACL) {
return value.toJSON();
}
if (_.isDate(value)) {
return { "__type": "Date", "iso": value.toJSON() };
}
if (value instanceof AV.GeoPoint) {
return value.toJSON();
}
if (_.isArray(value)) {
return _.map(value, function (x) {
return AV._encode(x, seenObjects, disallowObjects);
});
}
if (_.isRegExp(value)) {
return value.source;
}
if (value instanceof AV.Relation) {
return value.toJSON();
}
if (value instanceof AV.Op) {
return value.toJSON();
}
if (value instanceof AV.File) {
if (!value.url() && !value.id) {
throw "Tried to save an object containing an unsaved file.";
}
return {
__type: "File",
id: value.id,
name: value.name(),
url: value.url()
};
}
if (_.isObject(value)) {
var output = {};
AV._objectEach(value, function (v, k) {
output[k] = AV._encode(v, seenObjects, disallowObjects);
});
return output;
}
return value;
};
/**
* The inverse function of AV._encode.
* TODO: make decode not mutate value.
* @private
*/
AV._decode = function (key, value) {
if (!_.isObject(value)) {
return value;
}
if (_.isArray(value)) {
AV._arrayEach(value, function (v, k) {
value[k] = AV._decode(k, v);
});
return value;
}
if (value instanceof AV.Object) {
return value;
}
if (value instanceof AV.File) {
return value;
}
if (value instanceof AV.Op) {
return value;
}
if (value.__op) {
return AV.Op._decode(value);
}
var className;
if (value.__type === "Pointer") {
className = value.className;
var pointer = AV.Object._create(className);
if (Object.keys(value).length > 3) {
delete value.__type;
delete value.className;
pointer._finishFetch(value, true);
} else {
pointer._finishFetch({ objectId: value.objectId }, false);
}
return pointer;
}
if (value.__type === "Object") {
// It's an Object included in a query result.
className = value.className;
delete value.__type;
delete value.className;
var object = AV.Object._create(className);
object._finishFetch(value, true);
return object;
}
if (value.__type === "Date") {
return AV._parseDate(value.iso);
}
if (value.__type === "GeoPoint") {
return new AV.GeoPoint({
latitude: value.latitude,
longitude: value.longitude
});
}
if (key === "ACL") {
if (value instanceof AV.ACL) {
return value;
}
return new AV.ACL(value);
}
if (value.__type === "Relation") {
var relation = new AV.Relation(null, key);
relation.targetClassName = value.className;
return relation;
}
if (value.__type === 'File') {
var file = new AV.File(value.name);
file.attributes.metaData = value.metaData || {};
file.attributes.url = value.url;
file.id = value.objectId;
return file;
}
AV._objectEach(value, function (v, k) {
value[k] = AV._decode(k, v);
});
return value;
};
AV._encodeObjectOrArray = function (value) {
var encodeAVObject = function encodeAVObject(object) {
if (object && object._toFullJSON) {
object = object._toFullJSON([]);
}
return _.mapObject(object, function (value) {
return AV._encode(value, []);
});
};
if (_.isArray(value)) {
return value.map(function (object) {
return encodeAVObject(object);
});
} else {
return encodeAVObject(value);
}
};
AV._arrayEach = _.each;
/**
* Does a deep traversal of every item in object, calling func on every one.
* @param {Object} object The object or array to traverse deeply.
* @param {Function} func The function to call for every item. It will
* be passed the item as an argument. If it returns a truthy value, that
* value will replace the item in its parent container.
* @returns {} the result of calling func on the top-level object itself.
* @private
*/
AV._traverse = function (object, func, seen) {
if (object instanceof AV.Object) {
seen = seen || [];
if (_.indexOf(seen, object) >= 0) {
// We've already visited this object in this call.
return;
}
seen.push(object);
AV._traverse(object.attributes, func, seen);
return func(object);
}
if (object instanceof AV.Relation || object instanceof AV.File) {
// Nothing needs to be done, but we don't want to recurse into the
// object's parent infinitely, so we catch this case.
return func(object);
}
if (_.isArray(object)) {
_.each(object, function (child, index) {
var newChild = AV._traverse(child, func, seen);
if (newChild) {
object[index] = newChild;
}
});
return func(object);
}
if (_.isObject(object)) {
AV._each(object, function (child, key) {
var newChild = AV._traverse(child, func, seen);
if (newChild) {
object[key] = newChild;
}
});
return func(object);
}
return func(object);
};
/**
* This is like _.each, except:
* * it doesn't work for so-called array-like objects,
* * it does work for dictionaries with a "length" attribute.
* @private
*/
AV._objectEach = AV._each = function (obj, callback) {
if (_.isObject(obj)) {
_.each(_.keys(obj), function (key) {
callback(obj[key], key);
});
} else {
_.each(obj, callback);
}
};
module.exports = AV;