From 7f01c5355090a53e9f192d7ecc4f003f932a5022 Mon Sep 17 00:00:00 2001
From: Eduard Bosch Bertran
+ * Some functions are only available from Cloud Code.
+ * Open Event - When you call query.subscribe(), we send a subscribe request to
+ * the LiveQuery server, when we get the confirmation from the LiveQuery server,
+ * this event will be emitted. When the client loses WebSocket connection to the
+ * LiveQuery server, we will try to auto reconnect the LiveQuery server. If we
+ * reconnect the LiveQuery server and successfully resubscribe the ParseQuery,
+ * you'll also get this event.
+ *
+ *
+ *
+ * FB.init(). Parse.FacebookUtils will invoke FB.init() for you
+ * with these arguments.
+ *
+ * @method init
+ * @param {Object} options Facebook options argument as described here:
+ *
+ * FB.init(). The status flag will be coerced to 'false' because it
+ * interferes with Parse Facebook integration. Call FB.getLoginStatus()
+ * explicitly if this behavior is required by your application.
+ */
+ init: function (options) {
+ if (typeof FB === 'undefined') {
+ throw new Error('The Facebook JavaScript SDK must be loaded before calling init.');
+ }
+ initOptions = {};
+ if (options) {
+ for (var key in options) {
+ initOptions[key] = options[key];
+ }
+ }
+ if (initOptions.status && typeof console !== 'undefined') {
+ var warn = console.warn || console.log || function () {};
+ warn.call(console, 'The "status" flag passed into' + ' FB.init, when set to true, can interfere with Parse Facebook' + ' integration, so it has been suppressed. Please call' + ' FB.getLoginStatus() explicitly if you require this behavior.');
+ }
+ initOptions.status = false;
+ FB.init(initOptions);
+ _ParseUser2.default._registerAuthenticationProvider(provider);
+ initialized = true;
+ },
+
+ /**
+ * Gets whether the user has their account linked to Facebook.
+ *
+ * @method isLinked
+ * @param {Parse.User} user User to check for a facebook link.
+ * The user must be logged in on this device.
+ * @return {Boolean} true if the user has their account
+ * linked to Facebook.
+ */
+ isLinked: function (user) {
+ return user._isLinked('facebook');
+ },
+
+ /**
+ * Logs in a user using Facebook. This method delegates to the Facebook
+ * SDK to authenticate the user, and then automatically logs in (or
+ * creates, in the case where it is a new user) a Parse.User.
+ *
+ * @method logIn
+ * @param {String, Object} permissions The permissions required for Facebook
+ * log in. This is a comma-separated string of permissions.
+ * Alternatively, supply a Facebook authData object as described in our
+ * REST API docs if you want to handle getting facebook auth tokens
+ * yourself.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ logIn: function (permissions, options) {
+ if (!permissions || typeof permissions === 'string') {
+ if (!initialized) {
+ throw new Error('You must initialize FacebookUtils before calling logIn.');
+ }
+ requestedPermissions = permissions;
+ return _ParseUser2.default._logInWith('facebook', options);
+ } else {
+ var newOptions = {};
+ if (options) {
+ for (var key in options) {
+ newOptions[key] = options[key];
+ }
+ }
+ newOptions.authData = permissions;
+ return _ParseUser2.default._logInWith('facebook', newOptions);
+ }
+ },
+
+ /**
+ * Links Facebook to an existing PFUser. This method delegates to the
+ * Facebook SDK to authenticate the user, and then automatically links
+ * the account to the Parse.User.
+ *
+ * @method link
+ * @param {Parse.User} user User to link to Facebook. This must be the
+ * current user.
+ * @param {String, Object} permissions The permissions required for Facebook
+ * log in. This is a comma-separated string of permissions.
+ * Alternatively, supply a Facebook authData object as described in our
+ * REST API docs if you want to handle getting facebook auth tokens
+ * yourself.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ link: function (user, permissions, options) {
+ if (!permissions || typeof permissions === 'string') {
+ if (!initialized) {
+ throw new Error('You must initialize FacebookUtils before calling link.');
+ }
+ requestedPermissions = permissions;
+ return user._linkWith('facebook', options);
+ } else {
+ var newOptions = {};
+ if (options) {
+ for (var key in options) {
+ newOptions[key] = options[key];
+ }
+ }
+ newOptions.authData = permissions;
+ return user._linkWith('facebook', newOptions);
+ }
+ },
+
+ /**
+ * Unlinks the Parse.User from a Facebook account.
+ *
+ * @method unlink
+ * @param {Parse.User} user User to unlink from Facebook. This must be the
+ * current user.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ unlink: function (user, options) {
+ if (!initialized) {
+ throw new Error('You must initialize FacebookUtils before calling unlink.');
+ }
+ return user._unlinkFrom('facebook', options);
+ }
+};
+
+exports.default = FacebookUtils;
+},{"./ParseUser":25,"./parseDate":40}],6:[function(_dereq_,module,exports){
+'use strict';
+
+var _CoreManager = _dereq_('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _ParsePromise = _dereq_('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+var _Storage = _dereq_('./Storage');
+
+var _Storage2 = _interopRequireDefault(_Storage);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+var iidCache = null; /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function hexOctet() {
+ return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
+}
+
+function generateId() {
+ return hexOctet() + hexOctet() + '-' + hexOctet() + '-' + hexOctet() + '-' + hexOctet() + '-' + hexOctet() + hexOctet() + hexOctet();
+}
+
+var InstallationController = {
+ currentInstallationId: function () {
+ if (typeof iidCache === 'string') {
+ return _ParsePromise2.default.as(iidCache);
+ }
+ var path = _Storage2.default.generatePath('installationId');
+ return _Storage2.default.getItemAsync(path).then(function (iid) {
+ if (!iid) {
+ iid = generateId();
+ return _Storage2.default.setItemAsync(path, iid).then(function () {
+ iidCache = iid;
+ return iid;
+ });
+ }
+ iidCache = iid;
+ return iid;
+ });
+ },
+ _clearCache: function () {
+ iidCache = null;
+ },
+ _setInstallationIdCache: function (iid) {
+ iidCache = iid;
+ }
+};
+
+module.exports = InstallationController;
+},{"./CoreManager":3,"./ParsePromise":20,"./Storage":29}],7:[function(_dereq_,module,exports){
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _typeof2 = _dereq_('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _getIterator2 = _dereq_('babel-runtime/core-js/get-iterator');
+
+var _getIterator3 = _interopRequireDefault(_getIterator2);
+
+var _stringify = _dereq_('babel-runtime/core-js/json/stringify');
+
+var _stringify2 = _interopRequireDefault(_stringify);
+
+var _map = _dereq_('babel-runtime/core-js/map');
+
+var _map2 = _interopRequireDefault(_map);
+
+var _getPrototypeOf = _dereq_('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = _dereq_('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _possibleConstructorReturn2 = _dereq_('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = _dereq_('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _EventEmitter2 = _dereq_('./EventEmitter');
+
+var _EventEmitter3 = _interopRequireDefault(_EventEmitter2);
+
+var _ParsePromise = _dereq_('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+var _ParseObject = _dereq_('./ParseObject');
+
+var _ParseObject2 = _interopRequireDefault(_ParseObject);
+
+var _LiveQuerySubscription = _dereq_('./LiveQuerySubscription');
+
+var _LiveQuerySubscription2 = _interopRequireDefault(_LiveQuerySubscription);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+// The LiveQuery client inner state
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+var CLIENT_STATE = {
+ INITIALIZED: 'initialized',
+ CONNECTING: 'connecting',
+ CONNECTED: 'connected',
+ CLOSED: 'closed',
+ RECONNECTING: 'reconnecting',
+ DISCONNECTED: 'disconnected'
+};
+
+// The event type the LiveQuery client should sent to server
+var OP_TYPES = {
+ CONNECT: 'connect',
+ SUBSCRIBE: 'subscribe',
+ UNSUBSCRIBE: 'unsubscribe',
+ ERROR: 'error'
+};
+
+// The event we get back from LiveQuery server
+var OP_EVENTS = {
+ CONNECTED: 'connected',
+ SUBSCRIBED: 'subscribed',
+ UNSUBSCRIBED: 'unsubscribed',
+ ERROR: 'error',
+ CREATE: 'create',
+ UPDATE: 'update',
+ ENTER: 'enter',
+ LEAVE: 'leave',
+ DELETE: 'delete'
+};
+
+// The event the LiveQuery client should emit
+var CLIENT_EMMITER_TYPES = {
+ CLOSE: 'close',
+ ERROR: 'error',
+ OPEN: 'open'
+};
+
+// The event the LiveQuery subscription should emit
+var SUBSCRIPTION_EMMITER_TYPES = {
+ OPEN: 'open',
+ CLOSE: 'close',
+ ERROR: 'error',
+ CREATE: 'create',
+ UPDATE: 'update',
+ ENTER: 'enter',
+ LEAVE: 'leave',
+ DELETE: 'delete'
+};
+
+var generateInterval = function (k) {
+ return Math.random() * Math.min(30, Math.pow(2, k) - 1) * 1000;
+};
+
+/**
+ * Creates a new LiveQueryClient.
+ * Extends events.EventEmitter
+ * cloud functions.
+ *
+ * A wrapper of a standard WebSocket client. We add several useful methods to
+ * help you connect/disconnect to LiveQueryServer, subscribe/unsubscribe a ParseQuery easily.
+ *
+ * javascriptKey and masterKey are used for verifying the LiveQueryClient when it tries
+ * to connect to the LiveQuery server
+ *
+ * @class Parse.LiveQueryClient
+ * @constructor
+ * @param {Object} options
+ * @param {string} options.applicationId - applicationId of your Parse app
+ * @param {string} options.serverURL - the URL of your LiveQuery server
+ * @param {string} options.javascriptKey (optional)
+ * @param {string} options.masterKey (optional) Your Parse Master Key. (Node.js only!)
+ * @param {string} options.sessionToken (optional)
+ *
+ *
+ * We expose three events to help you monitor the status of the LiveQueryClient.
+ *
+ *
+ * let Parse = require('parse/node');
+ * let LiveQueryClient = Parse.LiveQueryClient;
+ * let client = new LiveQueryClient({
+ * applicationId: '',
+ * serverURL: '',
+ * javascriptKey: '',
+ * masterKey: ''
+ * });
+ *
+ *
+ * Open - When we establish the WebSocket connection to the LiveQuery server, you'll get this event.
+ *
+ * client.on('open', () => {
+ *
+ * });
+ *
+ * Close - When we lose the WebSocket connection to the LiveQuery server, you'll get this event.
+ *
+ * client.on('close', () => {
+ *
+ * });
+ *
+ * Error - When some network error or LiveQuery server error happens, you'll get this event.
+ *
+ * client.on('error', (error) => {
+ *
+ * });
+ *
+ *
+ */
+
+var LiveQueryClient = function (_EventEmitter) {
+ (0, _inherits3.default)(LiveQueryClient, _EventEmitter);
+
+ function LiveQueryClient(_ref) {
+ var applicationId = _ref.applicationId,
+ serverURL = _ref.serverURL,
+ javascriptKey = _ref.javascriptKey,
+ masterKey = _ref.masterKey,
+ sessionToken = _ref.sessionToken;
+ (0, _classCallCheck3.default)(this, LiveQueryClient);
+
+ var _this = (0, _possibleConstructorReturn3.default)(this, (LiveQueryClient.__proto__ || (0, _getPrototypeOf2.default)(LiveQueryClient)).call(this));
+
+ if (!serverURL || serverURL.indexOf('ws') !== 0) {
+ throw new Error('You need to set a proper Parse LiveQuery server url before using LiveQueryClient');
+ }
+
+ _this.reconnectHandle = null;
+ _this.attempts = 1;;
+ _this.id = 0;
+ _this.requestId = 1;
+ _this.serverURL = serverURL;
+ _this.applicationId = applicationId;
+ _this.javascriptKey = javascriptKey;
+ _this.masterKey = masterKey;
+ _this.sessionToken = sessionToken;
+ _this.connectPromise = new _ParsePromise2.default();
+ _this.subscriptions = new _map2.default();
+ _this.state = CLIENT_STATE.INITIALIZED;
+ return _this;
+ }
+
+ (0, _createClass3.default)(LiveQueryClient, [{
+ key: 'shouldOpen',
+ value: function () {
+ return this.state === CLIENT_STATE.INITIALIZED || this.state === CLIENT_STATE.DISCONNECTED;
+ }
+
+ /**
+ * Subscribes to a ParseQuery
+ *
+ * If you provide the sessionToken, when the LiveQuery server gets ParseObject's
+ * updates from parse server, it'll try to check whether the sessionToken fulfills
+ * the ParseObject's ACL. The LiveQuery server will only send updates to clients whose
+ * sessionToken is fit for the ParseObject's ACL. You can check the LiveQuery protocol
+ * here for more details. The subscription you get is the same subscription you get
+ * from our Standard API.
+ *
+ * @method subscribe
+ * @param {Object} query - the ParseQuery you want to subscribe to
+ * @param {string} sessionToken (optional)
+ * @return {Object} subscription
+ */
+
+ }, {
+ key: 'subscribe',
+ value: function (query, sessionToken) {
+ var _this2 = this;
+
+ if (!query) {
+ return;
+ }
+ var where = query.toJSON().where;
+ var className = query.className;
+ var subscribeRequest = {
+ op: OP_TYPES.SUBSCRIBE,
+ requestId: this.requestId,
+ query: {
+ className: className,
+ where: where
+ }
+ };
+
+ if (sessionToken) {
+ subscribeRequest.sessionToken = sessionToken;
+ }
+
+ var subscription = new _LiveQuerySubscription2.default(this.requestId, query, sessionToken);
+ this.subscriptions.set(this.requestId, subscription);
+ this.requestId += 1;
+ this.connectPromise.then(function () {
+ _this2.socket.send((0, _stringify2.default)(subscribeRequest));
+ });
+
+ // adding listener so process does not crash
+ // best practice is for developer to register their own listener
+ subscription.on('error', function () {});
+
+ return subscription;
+ }
+
+ /**
+ * After calling unsubscribe you'll stop receiving events from the subscription object.
+ *
+ * @method unsubscribe
+ * @param {Object} subscription - subscription you would like to unsubscribe from.
+ */
+
+ }, {
+ key: 'unsubscribe',
+ value: function (subscription) {
+ var _this3 = this;
+
+ if (!subscription) {
+ return;
+ }
+
+ this.subscriptions.delete(subscription.id);
+ var unsubscribeRequest = {
+ op: OP_TYPES.UNSUBSCRIBE,
+ requestId: subscription.id
+ };
+ this.connectPromise.then(function () {
+ _this3.socket.send((0, _stringify2.default)(unsubscribeRequest));
+ });
+ }
+
+ /**
+ * After open is called, the LiveQueryClient will try to send a connect request
+ * to the LiveQuery server.
+ *
+ * @method open
+ */
+
+ }, {
+ key: 'open',
+ value: function () {
+ var _this4 = this;
+
+ var WebSocketImplementation = this._getWebSocketImplementation();
+ if (!WebSocketImplementation) {
+ this.emit(CLIENT_EMMITER_TYPES.ERROR, 'Can not find WebSocket implementation');
+ return;
+ }
+
+ if (this.state !== CLIENT_STATE.RECONNECTING) {
+ this.state = CLIENT_STATE.CONNECTING;
+ }
+
+ // Get WebSocket implementation
+ this.socket = new WebSocketImplementation(this.serverURL);
+
+ // Bind WebSocket callbacks
+ this.socket.onopen = function () {
+ _this4._handleWebSocketOpen();
+ };
+
+ this.socket.onmessage = function (event) {
+ _this4._handleWebSocketMessage(event);
+ };
+
+ this.socket.onclose = function () {
+ _this4._handleWebSocketClose();
+ };
+
+ this.socket.onerror = function (error) {
+ _this4._handleWebSocketError(error);
+ };
+ }
+ }, {
+ key: 'resubscribe',
+ value: function () {
+ var _this5 = this;
+
+ this.subscriptions.forEach(function (subscription, requestId) {
+ var query = subscription.query;
+ var where = query.toJSON().where;
+ var className = query.className;
+ var sessionToken = subscription.sessionToken;
+ var subscribeRequest = {
+ op: OP_TYPES.SUBSCRIBE,
+ requestId: requestId,
+ query: {
+ className: className,
+ where: where
+ }
+ };
+
+ if (sessionToken) {
+ subscribeRequest.sessionToken = sessionToken;
+ }
+
+ _this5.connectPromise.then(function () {
+ _this5.socket.send((0, _stringify2.default)(subscribeRequest));
+ });
+ });
+ }
+
+ /**
+ * This method will close the WebSocket connection to this LiveQueryClient,
+ * cancel the auto reconnect and unsubscribe all subscriptions based on it.
+ *
+ * @method close
+ */
+
+ }, {
+ key: 'close',
+ value: function () {
+ if (this.state === CLIENT_STATE.INITIALIZED || this.state === CLIENT_STATE.DISCONNECTED) {
+ return;
+ }
+ this.state = CLIENT_STATE.DISCONNECTED;
+ this.socket.close();
+ // Notify each subscription about the close
+ var _iteratorNormalCompletion = true;
+ var _didIteratorError = false;
+ var _iteratorError = undefined;
+
+ try {
+ for (var _iterator = (0, _getIterator3.default)(this.subscriptions.values()), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+ var subscription = _step.value;
+
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.CLOSE);
+ }
+ } catch (err) {
+ _didIteratorError = true;
+ _iteratorError = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion && _iterator.return) {
+ _iterator.return();
+ }
+ } finally {
+ if (_didIteratorError) {
+ throw _iteratorError;
+ }
+ }
+ }
+
+ this._handleReset();
+ this.emit(CLIENT_EMMITER_TYPES.CLOSE);
+ }
+ }, {
+ key: '_getWebSocketImplementation',
+ value: function () {
+ return typeof WebSocket === 'function' || (typeof WebSocket === 'undefined' ? 'undefined' : (0, _typeof3.default)(WebSocket)) === 'object' ? WebSocket : null;
+ }
+
+ // ensure we start with valid state if connect is called again after close
+
+ }, {
+ key: '_handleReset',
+ value: function () {
+ this.attempts = 1;;
+ this.id = 0;
+ this.requestId = 1;
+ this.connectPromise = new _ParsePromise2.default();
+ this.subscriptions = new _map2.default();
+ }
+ }, {
+ key: '_handleWebSocketOpen',
+ value: function () {
+ this.attempts = 1;
+ var connectRequest = {
+ op: OP_TYPES.CONNECT,
+ applicationId: this.applicationId,
+ javascriptKey: this.javascriptKey,
+ masterKey: this.masterKey,
+ sessionToken: this.sessionToken
+ };
+ this.socket.send((0, _stringify2.default)(connectRequest));
+ }
+ }, {
+ key: '_handleWebSocketMessage',
+ value: function (event) {
+ var data = event.data;
+ if (typeof data === 'string') {
+ data = JSON.parse(data);
+ }
+ var subscription = null;
+ if (data.requestId) {
+ subscription = this.subscriptions.get(data.requestId);
+ }
+ switch (data.op) {
+ case OP_EVENTS.CONNECTED:
+ if (this.state === CLIENT_STATE.RECONNECTING) {
+ this.resubscribe();
+ }
+ this.emit(CLIENT_EMMITER_TYPES.OPEN);
+ this.id = data.clientId;
+ this.connectPromise.resolve();
+ this.state = CLIENT_STATE.CONNECTED;
+ break;
+ case OP_EVENTS.SUBSCRIBED:
+ if (subscription) {
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.OPEN);
+ }
+ break;
+ case OP_EVENTS.ERROR:
+ if (data.requestId) {
+ if (subscription) {
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.ERROR, data.error);
+ }
+ } else {
+ this.emit(CLIENT_EMMITER_TYPES.ERROR, data.error);
+ }
+ break;
+ case OP_EVENTS.UNSUBSCRIBED:
+ // We have already deleted subscription in unsubscribe(), do nothing here
+ break;
+ default:
+ // create, update, enter, leave, delete cases
+ var className = data.object.className;
+ // Delete the extrea __type and className fields during transfer to full JSON
+ delete data.object.__type;
+ delete data.object.className;
+ var parseObject = new _ParseObject2.default(className);
+ parseObject._finishFetch(data.object);
+ if (!subscription) {
+ break;
+ }
+ subscription.emit(data.op, parseObject);
+ }
+ }
+ }, {
+ key: '_handleWebSocketClose',
+ value: function () {
+ if (this.state === CLIENT_STATE.DISCONNECTED) {
+ return;
+ }
+ this.state = CLIENT_STATE.CLOSED;
+ this.emit(CLIENT_EMMITER_TYPES.CLOSE);
+ // Notify each subscription about the close
+ var _iteratorNormalCompletion2 = true;
+ var _didIteratorError2 = false;
+ var _iteratorError2 = undefined;
+
+ try {
+ for (var _iterator2 = (0, _getIterator3.default)(this.subscriptions.values()), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+ var subscription = _step2.value;
+
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.CLOSE);
+ }
+ } catch (err) {
+ _didIteratorError2 = true;
+ _iteratorError2 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion2 && _iterator2.return) {
+ _iterator2.return();
+ }
+ } finally {
+ if (_didIteratorError2) {
+ throw _iteratorError2;
+ }
+ }
+ }
+
+ this._handleReconnect();
+ }
+ }, {
+ key: '_handleWebSocketError',
+ value: function (error) {
+ this.emit(CLIENT_EMMITER_TYPES.ERROR, error);
+ var _iteratorNormalCompletion3 = true;
+ var _didIteratorError3 = false;
+ var _iteratorError3 = undefined;
+
+ try {
+ for (var _iterator3 = (0, _getIterator3.default)(this.subscriptions.values()), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
+ var subscription = _step3.value;
+
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.ERROR);
+ }
+ } catch (err) {
+ _didIteratorError3 = true;
+ _iteratorError3 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion3 && _iterator3.return) {
+ _iterator3.return();
+ }
+ } finally {
+ if (_didIteratorError3) {
+ throw _iteratorError3;
+ }
+ }
+ }
+
+ this._handleReconnect();
+ }
+ }, {
+ key: '_handleReconnect',
+ value: function () {
+ var _this6 = this;
+
+ // if closed or currently reconnecting we stop attempting to reconnect
+ if (this.state === CLIENT_STATE.DISCONNECTED) {
+ return;
+ }
+
+ this.state = CLIENT_STATE.RECONNECTING;
+ var time = generateInterval(this.attempts);
+
+ // handle case when both close/error occur at frequent rates we ensure we do not reconnect unnecessarily.
+ // we're unable to distinguish different between close/error when we're unable to reconnect therefore
+ // we try to reonnect in both cases
+ // server side ws and browser WebSocket behave differently in when close/error get triggered
+
+ if (this.reconnectHandle) {
+ clearTimeout(this.reconnectHandle);
+ }
+
+ this.reconnectHandle = setTimeout(function () {
+ _this6.attempts++;
+ _this6.connectPromise = new _ParsePromise2.default();
+ _this6.open();
+ }.bind(this), time);
+ }
+ }]);
+ return LiveQueryClient;
+}(_EventEmitter3.default);
+
+exports.default = LiveQueryClient;
+},{"./EventEmitter":4,"./LiveQuerySubscription":8,"./ParseObject":18,"./ParsePromise":20,"babel-runtime/core-js/get-iterator":43,"babel-runtime/core-js/json/stringify":44,"babel-runtime/core-js/map":45,"babel-runtime/core-js/object/get-prototype-of":50,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57,"babel-runtime/helpers/inherits":59,"babel-runtime/helpers/possibleConstructorReturn":60,"babel-runtime/helpers/typeof":61}],8:[function(_dereq_,module,exports){
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _getPrototypeOf = _dereq_('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = _dereq_('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _possibleConstructorReturn2 = _dereq_('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = _dereq_('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _EventEmitter2 = _dereq_('./EventEmitter');
+
+var _EventEmitter3 = _interopRequireDefault(_EventEmitter2);
+
+var _CoreManager = _dereq_('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Creates a new LiveQuery Subscription.
+ * Extends events.EventEmitter
+ * cloud functions.
+ *
+ * @constructor
+ * @param {string} id - subscription id
+ * @param {string} query - query to subscribe to
+ * @param {string} sessionToken - optional session token
+ *
+ *
+ * subscription.on('open', () => {
+ *
+ * });
Create Event - When a new ParseObject is created and it fulfills the ParseQuery you subscribe, + * you'll get this event. The object is the ParseObject which is created. + * + *
+ * subscription.on('create', (object) => {
+ *
+ * });
+ *
+ * Update Event - When an existing ParseObject which fulfills the ParseQuery you subscribe + * is updated (The ParseObject fulfills the ParseQuery before and after changes), + * you'll get this event. The object is the ParseObject which is updated. + * Its content is the latest value of the ParseObject. + * + *
+ * subscription.on('update', (object) => {
+ *
+ * });
+ *
+ * Enter Event - When an existing ParseObject's old value doesn't fulfill the ParseQuery + * but its new value fulfills the ParseQuery, you'll get this event. The object is the + * ParseObject which enters the ParseQuery. Its content is the latest value of the ParseObject. + * + *
+ * subscription.on('enter', (object) => {
+ *
+ * });
+ *
+ *
+ * Update Event - When an existing ParseObject's old value fulfills the ParseQuery but its new value + * doesn't fulfill the ParseQuery, you'll get this event. The object is the ParseObject + * which leaves the ParseQuery. Its content is the latest value of the ParseObject. + * + *
+ * subscription.on('leave', (object) => {
+ *
+ * });
+ *
+ *
+ * Delete Event - When an existing ParseObject which fulfills the ParseQuery is deleted, you'll + * get this event. The object is the ParseObject which is deleted. + * + *
+ * subscription.on('delete', (object) => {
+ *
+ * });
+ *
+ *
+ * Close Event - When the client loses the WebSocket connection to the LiveQuery + * server and we stop receiving events, you'll get this event. + * + *
+ * subscription.on('close', () => {
+ *
+ * });
+ *
+ *
+ */
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+var Subscription = function (_EventEmitter) {
+ (0, _inherits3.default)(Subscription, _EventEmitter);
+
+ function Subscription(id, query, sessionToken) {
+ (0, _classCallCheck3.default)(this, Subscription);
+
+ var _this2 = (0, _possibleConstructorReturn3.default)(this, (Subscription.__proto__ || (0, _getPrototypeOf2.default)(Subscription)).call(this));
+
+ _this2.id = id;
+ _this2.query = query;
+ _this2.sessionToken = sessionToken;
+ return _this2;
+ }
+
+ /**
+ * @method unsubscribe
+ */
+
+ (0, _createClass3.default)(Subscription, [{
+ key: 'unsubscribe',
+ value: function () {
+ var _this3 = this;
+
+ var _this = this;
+ _CoreManager2.default.getLiveQueryController().getDefaultLiveQueryClient().then(function (liveQueryClient) {
+ liveQueryClient.unsubscribe(_this);
+ _this.emit('close');
+ _this3.resolve();
+ });
+ }
+ }]);
+ return Subscription;
+}(_EventEmitter3.default);
+
+exports.default = Subscription;
+},{"./CoreManager":3,"./EventEmitter":4,"babel-runtime/core-js/object/get-prototype-of":50,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57,"babel-runtime/helpers/inherits":59,"babel-runtime/helpers/possibleConstructorReturn":60}],9:[function(_dereq_,module,exports){
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _stringify = _dereq_('babel-runtime/core-js/json/stringify');
+
+var _stringify2 = _interopRequireDefault(_stringify);
+
+var _typeof2 = _dereq_('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+exports.defaultState = defaultState;
+exports.setServerData = setServerData;
+exports.setPendingOp = setPendingOp;
+exports.pushPendingState = pushPendingState;
+exports.popPendingState = popPendingState;
+exports.mergeFirstPendingState = mergeFirstPendingState;
+exports.estimateAttribute = estimateAttribute;
+exports.estimateAttributes = estimateAttributes;
+exports.commitServerChanges = commitServerChanges;
+
+var _encode = _dereq_('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _ParseFile = _dereq_('./ParseFile');
+
+var _ParseFile2 = _interopRequireDefault(_ParseFile);
+
+var _ParseObject = _dereq_('./ParseObject');
+
+var _ParseObject2 = _interopRequireDefault(_ParseObject);
+
+var _ParsePromise = _dereq_('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+var _ParseRelation = _dereq_('./ParseRelation');
+
+var _ParseRelation2 = _interopRequireDefault(_ParseRelation);
+
+var _TaskQueue = _dereq_('./TaskQueue');
+
+var _TaskQueue2 = _interopRequireDefault(_TaskQueue);
+
+var _ParseOp = _dereq_('./ParseOp');
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+function defaultState() {
+ return {
+ serverData: {},
+ pendingOps: [{}],
+ objectCache: {},
+ tasks: new _TaskQueue2.default(),
+ existed: false
+ };
+} /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function setServerData(serverData, attributes) {
+ for (var _attr in attributes) {
+ if (typeof attributes[_attr] !== 'undefined') {
+ serverData[_attr] = attributes[_attr];
+ } else {
+ delete serverData[_attr];
+ }
+ }
+}
+
+function setPendingOp(pendingOps, attr, op) {
+ var last = pendingOps.length - 1;
+ if (op) {
+ pendingOps[last][attr] = op;
+ } else {
+ delete pendingOps[last][attr];
+ }
+}
+
+function pushPendingState(pendingOps) {
+ pendingOps.push({});
+}
+
+function popPendingState(pendingOps) {
+ var first = pendingOps.shift();
+ if (!pendingOps.length) {
+ pendingOps[0] = {};
+ }
+ return first;
+}
+
+function mergeFirstPendingState(pendingOps) {
+ var first = popPendingState(pendingOps);
+ var next = pendingOps[0];
+ for (var _attr2 in first) {
+ if (next[_attr2] && first[_attr2]) {
+ var merged = next[_attr2].mergeWith(first[_attr2]);
+ if (merged) {
+ next[_attr2] = merged;
+ }
+ } else {
+ next[_attr2] = first[_attr2];
+ }
+ }
+}
+
+function estimateAttribute(serverData, pendingOps, className, id, attr) {
+ var value = serverData[attr];
+ for (var i = 0; i < pendingOps.length; i++) {
+ if (pendingOps[i][attr]) {
+ if (pendingOps[i][attr] instanceof _ParseOp.RelationOp) {
+ if (id) {
+ value = pendingOps[i][attr].applyTo(value, { className: className, id: id }, attr);
+ }
+ } else {
+ value = pendingOps[i][attr].applyTo(value);
+ }
+ }
+ }
+ return value;
+}
+
+function estimateAttributes(serverData, pendingOps, className, id) {
+ var data = {};
+ var attr = void 0;
+ for (attr in serverData) {
+ data[attr] = serverData[attr];
+ }
+ for (var i = 0; i < pendingOps.length; i++) {
+ for (attr in pendingOps[i]) {
+ if (pendingOps[i][attr] instanceof _ParseOp.RelationOp) {
+ if (id) {
+ data[attr] = pendingOps[i][attr].applyTo(data[attr], { className: className, id: id }, attr);
+ }
+ } else {
+ data[attr] = pendingOps[i][attr].applyTo(data[attr]);
+ }
+ }
+ }
+ return data;
+}
+
+function commitServerChanges(serverData, objectCache, changes) {
+ for (var _attr3 in changes) {
+ var val = changes[_attr3];
+ serverData[_attr3] = val;
+ if (val && (typeof val === 'undefined' ? 'undefined' : (0, _typeof3.default)(val)) === 'object' && !(val instanceof _ParseObject2.default) && !(val instanceof _ParseFile2.default) && !(val instanceof _ParseRelation2.default)) {
+ var json = (0, _encode2.default)(val, false, true);
+ objectCache[_attr3] = (0, _stringify2.default)(json);
+ }
+ }
+}
+},{"./ParseFile":14,"./ParseObject":18,"./ParseOp":19,"./ParsePromise":20,"./ParseRelation":22,"./TaskQueue":31,"./encode":36,"babel-runtime/core-js/json/stringify":44,"babel-runtime/helpers/typeof":61}],10:[function(_dereq_,module,exports){
+'use strict';
+
+var _decode = _dereq_('./decode');
+
+var _decode2 = _interopRequireDefault(_decode);
+
+var _encode = _dereq_('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _CoreManager = _dereq_('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _InstallationController = _dereq_('./InstallationController');
+
+var _InstallationController2 = _interopRequireDefault(_InstallationController);
+
+var _ParseOp = _dereq_('./ParseOp');
+
+var ParseOp = _interopRequireWildcard(_ParseOp);
+
+var _RESTController = _dereq_('./RESTController');
+
+var _RESTController2 = _interopRequireDefault(_RESTController);
+
+function _interopRequireWildcard(obj) {
+ if (obj && obj.__esModule) {
+ return obj;
+ } else {
+ var newObj = {};if (obj != null) {
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];
+ }
+ }newObj.default = obj;return newObj;
+ }
+}
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Contains all Parse API classes and functions.
+ * @class Parse
+ * @static
+ */
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+var Parse = {
+ /**
+ * Call this method first to set up your authentication tokens for Parse.
+ * You can get your keys from the Data Browser on parse.com.
+ * @method initialize
+ * @param {String} applicationId Your Parse Application ID.
+ * @param {String} javaScriptKey (optional) Your Parse JavaScript Key (Not needed for parse-server)
+ * @param {String} masterKey (optional) Your Parse Master Key. (Node.js only!)
+ * @static
+ */
+ initialize: function (applicationId, javaScriptKey) {
+ if ('browser' === 'browser' && _CoreManager2.default.get('IS_NODE')) {
+ console.log('It looks like you\'re using the browser version of the SDK in a ' + 'node.js environment. You should require(\'parse/node\') instead.');
+ }
+ Parse._initialize(applicationId, javaScriptKey);
+ },
+ _initialize: function (applicationId, javaScriptKey, masterKey) {
+ _CoreManager2.default.set('APPLICATION_ID', applicationId);
+ _CoreManager2.default.set('JAVASCRIPT_KEY', javaScriptKey);
+ _CoreManager2.default.set('MASTER_KEY', masterKey);
+ _CoreManager2.default.set('USE_MASTER_KEY', false);
+ }
+};
+
+/** These legacy setters may eventually be deprecated **/
+Object.defineProperty(Parse, 'applicationId', {
+ get: function () {
+ return _CoreManager2.default.get('APPLICATION_ID');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('APPLICATION_ID', value);
+ }
+});
+Object.defineProperty(Parse, 'javaScriptKey', {
+ get: function () {
+ return _CoreManager2.default.get('JAVASCRIPT_KEY');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('JAVASCRIPT_KEY', value);
+ }
+});
+Object.defineProperty(Parse, 'masterKey', {
+ get: function () {
+ return _CoreManager2.default.get('MASTER_KEY');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('MASTER_KEY', value);
+ }
+});
+Object.defineProperty(Parse, 'serverURL', {
+ get: function () {
+ return _CoreManager2.default.get('SERVER_URL');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('SERVER_URL', value);
+ }
+});
+Object.defineProperty(Parse, 'liveQueryServerURL', {
+ get: function () {
+ return _CoreManager2.default.get('LIVEQUERY_SERVER_URL');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('LIVEQUERY_SERVER_URL', value);
+ }
+});
+/** End setters **/
+
+Parse.ACL = _dereq_('./ParseACL').default;
+Parse.Analytics = _dereq_('./Analytics');
+Parse.Cloud = _dereq_('./Cloud');
+Parse.CoreManager = _dereq_('./CoreManager');
+Parse.Config = _dereq_('./ParseConfig').default;
+Parse.Error = _dereq_('./ParseError').default;
+Parse.FacebookUtils = _dereq_('./FacebookUtils').default;
+Parse.File = _dereq_('./ParseFile').default;
+Parse.GeoPoint = _dereq_('./ParseGeoPoint').default;
+Parse.Installation = _dereq_('./ParseInstallation').default;
+Parse.Object = _dereq_('./ParseObject').default;
+Parse.Op = {
+ Set: ParseOp.SetOp,
+ Unset: ParseOp.UnsetOp,
+ Increment: ParseOp.IncrementOp,
+ Add: ParseOp.AddOp,
+ Remove: ParseOp.RemoveOp,
+ AddUnique: ParseOp.AddUniqueOp,
+ Relation: ParseOp.RelationOp
+};
+Parse.Promise = _dereq_('./ParsePromise').default;
+Parse.Push = _dereq_('./Push');
+Parse.Query = _dereq_('./ParseQuery').default;
+Parse.Relation = _dereq_('./ParseRelation').default;
+Parse.Role = _dereq_('./ParseRole').default;
+Parse.Session = _dereq_('./ParseSession').default;
+Parse.Storage = _dereq_('./Storage');
+Parse.User = _dereq_('./ParseUser').default;
+Parse.LiveQuery = _dereq_('./ParseLiveQuery').default;
+Parse.LiveQueryClient = _dereq_('./LiveQueryClient').default;
+
+Parse._request = function () {
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ return _CoreManager2.default.getRESTController().request.apply(null, args);
+};
+Parse._ajax = function () {
+ for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+
+ return _CoreManager2.default.getRESTController().ajax.apply(null, args);
+};
+// We attempt to match the signatures of the legacy versions of these methods
+Parse._decode = function (_, value) {
+ return (0, _decode2.default)(value);
+};
+Parse._encode = function (value, _, disallowObjects) {
+ return (0, _encode2.default)(value, disallowObjects);
+};
+Parse._getInstallationId = function () {
+ return _CoreManager2.default.getInstallationController().currentInstallationId();
+};
+
+_CoreManager2.default.setInstallationController(_InstallationController2.default);
+_CoreManager2.default.setRESTController(_RESTController2.default);
+
+// For legacy requires, of the form `var Parse = require('parse').Parse`
+Parse.Parse = Parse;
+
+module.exports = Parse;
+},{"./Analytics":1,"./Cloud":2,"./CoreManager":3,"./FacebookUtils":5,"./InstallationController":6,"./LiveQueryClient":7,"./ParseACL":11,"./ParseConfig":12,"./ParseError":13,"./ParseFile":14,"./ParseGeoPoint":15,"./ParseInstallation":16,"./ParseLiveQuery":17,"./ParseObject":18,"./ParseOp":19,"./ParsePromise":20,"./ParseQuery":21,"./ParseRelation":22,"./ParseRole":23,"./ParseSession":24,"./ParseUser":25,"./Push":26,"./RESTController":27,"./Storage":29,"./decode":35,"./encode":36}],11:[function(_dereq_,module,exports){
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _keys = _dereq_('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
+var _typeof2 = _dereq_('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = _dereq_('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _ParseRole = _dereq_('./ParseRole');
+
+var _ParseRole2 = _interopRequireDefault(_ParseRole);
+
+var _ParseUser = _dereq_('./ParseUser');
+
+var _ParseUser2 = _interopRequireDefault(_ParseUser);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+var PUBLIC_KEY = '*';
+
+/**
+ * Creates a new ACL.
+ * If no argument is given, the ACL has no permissions for anyone.
+ * If the argument is a Parse.User, the ACL will have read and write
+ * permission for only that user.
+ * If the argument is any other JSON object, that object will be interpretted
+ * as a serialized ACL created with toJSON().
+ * @class Parse.ACL
+ * @constructor
+ *
+ * An ACL, or Access Control List can be added to any
+ * Parse.Object to restrict access to only a subset of users
+ * of your application.
Parse.Error.
+ * @param {String} message A detailed description of the error.
+ */
+var ParseError = function ParseError(code, message) {
+ (0, _classCallCheck3.default)(this, ParseError);
+
+ this.code = code;
+ this.message = message;
+};
+
+/**
+ * Error code indicating some error other than those enumerated here.
+ * @property OTHER_CAUSE
+ * @static
+ * @final
+ */
+
+exports.default = ParseError;
+ParseError.OTHER_CAUSE = -1;
+
+/**
+ * Error code indicating that something has gone wrong with the server.
+ * If you get this error code, it is Parse's fault. Contact us at
+ * https://parse.com/help
+ * @property INTERNAL_SERVER_ERROR
+ * @static
+ * @final
+ */
+ParseError.INTERNAL_SERVER_ERROR = 1;
+
+/**
+ * Error code indicating the connection to the Parse servers failed.
+ * @property CONNECTION_FAILED
+ * @static
+ * @final
+ */
+ParseError.CONNECTION_FAILED = 100;
+
+/**
+ * Error code indicating the specified object doesn't exist.
+ * @property OBJECT_NOT_FOUND
+ * @static
+ * @final
+ */
+ParseError.OBJECT_NOT_FOUND = 101;
+
+/**
+ * Error code indicating you tried to query with a datatype that doesn't
+ * support it, like exact matching an array or object.
+ * @property INVALID_QUERY
+ * @static
+ * @final
+ */
+ParseError.INVALID_QUERY = 102;
+
+/**
+ * Error code indicating a missing or invalid classname. Classnames are
+ * case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the
+ * only valid characters.
+ * @property INVALID_CLASS_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_CLASS_NAME = 103;
+
+/**
+ * Error code indicating an unspecified object id.
+ * @property MISSING_OBJECT_ID
+ * @static
+ * @final
+ */
+ParseError.MISSING_OBJECT_ID = 104;
+
+/**
+ * Error code indicating an invalid key name. Keys are case-sensitive. They
+ * must start with a letter, and a-zA-Z0-9_ are the only valid characters.
+ * @property INVALID_KEY_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_KEY_NAME = 105;
+
+/**
+ * Error code indicating a malformed pointer. You should not see this unless
+ * you have been mucking about changing internal Parse code.
+ * @property INVALID_POINTER
+ * @static
+ * @final
+ */
+ParseError.INVALID_POINTER = 106;
+
+/**
+ * Error code indicating that badly formed JSON was received upstream. This
+ * either indicates you have done something unusual with modifying how
+ * things encode to JSON, or the network is failing badly.
+ * @property INVALID_JSON
+ * @static
+ * @final
+ */
+ParseError.INVALID_JSON = 107;
+
+/**
+ * Error code indicating that the feature you tried to access is only
+ * available internally for testing purposes.
+ * @property COMMAND_UNAVAILABLE
+ * @static
+ * @final
+ */
+ParseError.COMMAND_UNAVAILABLE = 108;
+
+/**
+ * You must call Parse.initialize before using the Parse library.
+ * @property NOT_INITIALIZED
+ * @static
+ * @final
+ */
+ParseError.NOT_INITIALIZED = 109;
+
+/**
+ * Error code indicating that a field was set to an inconsistent type.
+ * @property INCORRECT_TYPE
+ * @static
+ * @final
+ */
+ParseError.INCORRECT_TYPE = 111;
+
+/**
+ * Error code indicating an invalid channel name. A channel name is either
+ * an empty string (the broadcast channel) or contains only a-zA-Z0-9_
+ * characters and starts with a letter.
+ * @property INVALID_CHANNEL_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_CHANNEL_NAME = 112;
+
+/**
+ * Error code indicating that push is misconfigured.
+ * @property PUSH_MISCONFIGURED
+ * @static
+ * @final
+ */
+ParseError.PUSH_MISCONFIGURED = 115;
+
+/**
+ * Error code indicating that the object is too large.
+ * @property OBJECT_TOO_LARGE
+ * @static
+ * @final
+ */
+ParseError.OBJECT_TOO_LARGE = 116;
+
+/**
+ * Error code indicating that the operation isn't allowed for clients.
+ * @property OPERATION_FORBIDDEN
+ * @static
+ * @final
+ */
+ParseError.OPERATION_FORBIDDEN = 119;
+
+/**
+ * Error code indicating the result was not found in the cache.
+ * @property CACHE_MISS
+ * @static
+ * @final
+ */
+ParseError.CACHE_MISS = 120;
+
+/**
+ * Error code indicating that an invalid key was used in a nested
+ * JSONObject.
+ * @property INVALID_NESTED_KEY
+ * @static
+ * @final
+ */
+ParseError.INVALID_NESTED_KEY = 121;
+
+/**
+ * Error code indicating that an invalid filename was used for ParseFile.
+ * A valid file name contains only a-zA-Z0-9_. characters and is between 1
+ * and 128 characters.
+ * @property INVALID_FILE_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_FILE_NAME = 122;
+
+/**
+ * Error code indicating an invalid ACL was provided.
+ * @property INVALID_ACL
+ * @static
+ * @final
+ */
+ParseError.INVALID_ACL = 123;
+
+/**
+ * Error code indicating that the request timed out on the server. Typically
+ * this indicates that the request is too expensive to run.
+ * @property TIMEOUT
+ * @static
+ * @final
+ */
+ParseError.TIMEOUT = 124;
+
+/**
+ * Error code indicating that the email address was invalid.
+ * @property INVALID_EMAIL_ADDRESS
+ * @static
+ * @final
+ */
+ParseError.INVALID_EMAIL_ADDRESS = 125;
+
+/**
+ * Error code indicating a missing content type.
+ * @property MISSING_CONTENT_TYPE
+ * @static
+ * @final
+ */
+ParseError.MISSING_CONTENT_TYPE = 126;
+
+/**
+ * Error code indicating a missing content length.
+ * @property MISSING_CONTENT_LENGTH
+ * @static
+ * @final
+ */
+ParseError.MISSING_CONTENT_LENGTH = 127;
+
+/**
+ * Error code indicating an invalid content length.
+ * @property INVALID_CONTENT_LENGTH
+ * @static
+ * @final
+ */
+ParseError.INVALID_CONTENT_LENGTH = 128;
+
+/**
+ * Error code indicating a file that was too large.
+ * @property FILE_TOO_LARGE
+ * @static
+ * @final
+ */
+ParseError.FILE_TOO_LARGE = 129;
+
+/**
+ * Error code indicating an error saving a file.
+ * @property FILE_SAVE_ERROR
+ * @static
+ * @final
+ */
+ParseError.FILE_SAVE_ERROR = 130;
+
+/**
+ * Error code indicating that a unique field was given a value that is
+ * already taken.
+ * @property DUPLICATE_VALUE
+ * @static
+ * @final
+ */
+ParseError.DUPLICATE_VALUE = 137;
+
+/**
+ * Error code indicating that a role's name is invalid.
+ * @property INVALID_ROLE_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_ROLE_NAME = 139;
+
+/**
+ * Error code indicating that an application quota was exceeded. Upgrade to
+ * resolve.
+ * @property EXCEEDED_QUOTA
+ * @static
+ * @final
+ */
+ParseError.EXCEEDED_QUOTA = 140;
+
+/**
+ * Error code indicating that a Cloud Code script failed.
+ * @property SCRIPT_FAILED
+ * @static
+ * @final
+ */
+ParseError.SCRIPT_FAILED = 141;
+
+/**
+ * Error code indicating that a Cloud Code validation failed.
+ * @property VALIDATION_ERROR
+ * @static
+ * @final
+ */
+ParseError.VALIDATION_ERROR = 142;
+
+/**
+ * Error code indicating that invalid image data was provided.
+ * @property INVALID_IMAGE_DATA
+ * @static
+ * @final
+ */
+ParseError.INVALID_IMAGE_DATA = 143;
+
+/**
+ * Error code indicating an unsaved file.
+ * @property UNSAVED_FILE_ERROR
+ * @static
+ * @final
+ */
+ParseError.UNSAVED_FILE_ERROR = 151;
+
+/**
+ * Error code indicating an invalid push time.
+ * @property INVALID_PUSH_TIME_ERROR
+ * @static
+ * @final
+ */
+ParseError.INVALID_PUSH_TIME_ERROR = 152;
+
+/**
+ * Error code indicating an error deleting a file.
+ * @property FILE_DELETE_ERROR
+ * @static
+ * @final
+ */
+ParseError.FILE_DELETE_ERROR = 153;
+
+/**
+ * Error code indicating that the application has exceeded its request
+ * limit.
+ * @property REQUEST_LIMIT_EXCEEDED
+ * @static
+ * @final
+ */
+ParseError.REQUEST_LIMIT_EXCEEDED = 155;
+
+/**
+ * Error code indicating an invalid event name.
+ * @property INVALID_EVENT_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_EVENT_NAME = 160;
+
+/**
+ * Error code indicating that the username is missing or empty.
+ * @property USERNAME_MISSING
+ * @static
+ * @final
+ */
+ParseError.USERNAME_MISSING = 200;
+
+/**
+ * Error code indicating that the password is missing or empty.
+ * @property PASSWORD_MISSING
+ * @static
+ * @final
+ */
+ParseError.PASSWORD_MISSING = 201;
+
+/**
+ * Error code indicating that the username has already been taken.
+ * @property USERNAME_TAKEN
+ * @static
+ * @final
+ */
+ParseError.USERNAME_TAKEN = 202;
+
+/**
+ * Error code indicating that the email has already been taken.
+ * @property EMAIL_TAKEN
+ * @static
+ * @final
+ */
+ParseError.EMAIL_TAKEN = 203;
+
+/**
+ * Error code indicating that the email is missing, but must be specified.
+ * @property EMAIL_MISSING
+ * @static
+ * @final
+ */
+ParseError.EMAIL_MISSING = 204;
+
+/**
+ * Error code indicating that a user with the specified email was not found.
+ * @property EMAIL_NOT_FOUND
+ * @static
+ * @final
+ */
+ParseError.EMAIL_NOT_FOUND = 205;
+
+/**
+ * Error code indicating that a user object without a valid session could
+ * not be altered.
+ * @property SESSION_MISSING
+ * @static
+ * @final
+ */
+ParseError.SESSION_MISSING = 206;
+
+/**
+ * Error code indicating that a user can only be created through signup.
+ * @property MUST_CREATE_USER_THROUGH_SIGNUP
+ * @static
+ * @final
+ */
+ParseError.MUST_CREATE_USER_THROUGH_SIGNUP = 207;
+
+/**
+ * Error code indicating that an an account being linked is already linked
+ * to another user.
+ * @property ACCOUNT_ALREADY_LINKED
+ * @static
+ * @final
+ */
+ParseError.ACCOUNT_ALREADY_LINKED = 208;
+
+/**
+ * Error code indicating that the current session token is invalid.
+ * @property INVALID_SESSION_TOKEN
+ * @static
+ * @final
+ */
+ParseError.INVALID_SESSION_TOKEN = 209;
+
+/**
+ * Error code indicating that a user cannot be linked to an account because
+ * that account's id could not be found.
+ * @property LINKED_ID_MISSING
+ * @static
+ * @final
+ */
+ParseError.LINKED_ID_MISSING = 250;
+
+/**
+ * Error code indicating that a user with a linked (e.g. Facebook) account
+ * has an invalid session.
+ * @property INVALID_LINKED_SESSION
+ * @static
+ * @final
+ */
+ParseError.INVALID_LINKED_SESSION = 251;
+
+/**
+ * Error code indicating that a service being linked (e.g. Facebook or
+ * Twitter) is unsupported.
+ * @property UNSUPPORTED_SERVICE
+ * @static
+ * @final
+ */
+ParseError.UNSUPPORTED_SERVICE = 252;
+
+/**
+ * Error code indicating that there were multiple errors. Aggregate errors
+ * have an "errors" property, which is an array of error objects with more
+ * detail about each error that occurred.
+ * @property AGGREGATE_ERROR
+ * @static
+ * @final
+ */
+ParseError.AGGREGATE_ERROR = 600;
+
+/**
+ * Error code indicating the client was unable to read an input file.
+ * @property FILE_READ_ERROR
+ * @static
+ * @final
+ */
+ParseError.FILE_READ_ERROR = 601;
+
+/**
+ * Error code indicating a real error code is unavailable because
+ * we had to use an XDomainRequest object to allow CORS requests in
+ * Internet Explorer, which strips the body from HTTP responses that have
+ * a non-2XX status code.
+ * @property X_DOMAIN_REQUEST
+ * @static
+ * @final
+ */
+ParseError.X_DOMAIN_REQUEST = 602;
+},{"babel-runtime/helpers/classCallCheck":56}],14:[function(_dereq_,module,exports){
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = _dereq_('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _CoreManager = _dereq_('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _ParsePromise = _dereq_('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+var dataUriRegexp = /^data:([a-zA-Z]*\/[a-zA-Z+.-]*);(charset=[a-zA-Z0-9\-\/\s]*,)?base64,/;
+
+function b64Digit(number) {
+ if (number < 26) {
+ return String.fromCharCode(65 + number);
+ }
+ if (number < 52) {
+ return String.fromCharCode(97 + (number - 26));
+ }
+ if (number < 62) {
+ return String.fromCharCode(48 + (number - 52));
+ }
+ if (number === 62) {
+ return '+';
+ }
+ if (number === 63) {
+ return '/';
+ }
+ throw new TypeError('Tried to encode large digit ' + number + ' in base64.');
+}
+
+/**
+ * A Parse.File is a local representation of a file that is saved to the Parse
+ * cloud.
+ * @class Parse.File
+ * @constructor
+ * @param name {String} The file's name. This will be prefixed by a unique
+ * value once the file has finished saving. The file name must begin with
+ * an alphanumeric character, and consist of alphanumeric characters,
+ * periods, spaces, underscores, or dashes.
+ * @param data {Array} The data for the file, as either:
+ * 1. an Array of byte value Numbers, or
+ * 2. an Object like { base64: "..." } with a base64-encoded String.
+ * 3. a File object selected with a file upload control. (3) only works
+ * in Firefox 3.6+, Safari 6.0.2+, Chrome 7+, and IE 10+.
+ * For example:
+ * var fileUploadControl = $("#profilePhotoFileUpload")[0];
+ * if (fileUploadControl.files.length > 0) {
+ * var file = fileUploadControl.files[0];
+ * var name = "photo.jpg";
+ * var parseFile = new Parse.File(name, file);
+ * parseFile.save().then(function() {
+ * // The file has been saved to Parse.
+ * }, function(error) {
+ * // The file either could not be read, or could not be saved to Parse.
+ * });
+ * }
+ * @param type {String} Optional Content-Type header to use for the file. If
+ * this is omitted, the content type will be inferred from the name's
+ * extension.
+ */
+
+var ParseFile = function () {
+ function ParseFile(name, data, type) {
+ (0, _classCallCheck3.default)(this, ParseFile);
+
+ var specifiedType = type || '';
+
+ this._name = name;
+
+ if (data !== undefined) {
+ if (Array.isArray(data)) {
+ this._source = {
+ format: 'base64',
+ base64: ParseFile.encodeBase64(data),
+ type: specifiedType
+ };
+ } else if (typeof File !== 'undefined' && data instanceof File) {
+ this._source = {
+ format: 'file',
+ file: data,
+ type: specifiedType
+ };
+ } else if (data && typeof data.base64 === 'string') {
+ var _base = data.base64;
+ var commaIndex = _base.indexOf(',');
+
+ if (commaIndex !== -1) {
+ var matches = dataUriRegexp.exec(_base.slice(0, commaIndex + 1));
+ // if data URI with type and charset, there will be 4 matches.
+ this._source = {
+ format: 'base64',
+ base64: _base.slice(commaIndex + 1),
+ type: matches[1]
+ };
+ } else {
+ this._source = {
+ format: 'base64',
+ base64: _base,
+ type: specifiedType
+ };
+ }
+ } else {
+ throw new TypeError('Cannot create a Parse.File with that data.');
+ }
+ }
+ }
+
+ /**
+ * Gets the name of the file. Before save is called, this is the filename
+ * given by the user. After save is called, that name gets prefixed with a
+ * unique identifier.
+ * @method name
+ * @return {String}
+ */
+
+ (0, _createClass3.default)(ParseFile, [{
+ key: 'name',
+ value: function () {
+ return this._name;
+ }
+
+ /**
+ * Gets the url of the file. It is only available after you save the file or
+ * after you get the file from a Parse.Object.
+ * @method url
+ * @param {Object} options An object to specify url options
+ * @return {String}
+ */
+
+ }, {
+ key: 'url',
+ value: function (options) {
+ options = options || {};
+ if (!this._url) {
+ return;
+ }
+ if (options.forceSecure) {
+ return this._url.replace(/^http:\/\//i, 'https://');
+ } else {
+ return this._url;
+ }
+ }
+
+ /**
+ * Saves the file to the Parse cloud.
+ * @method save
+ * @param {Object} options A Backbone-style options object.
+ * @return {Parse.Promise} Promise that is resolved when the save finishes.
+ */
+
+ }, {
+ key: 'save',
+ value: function (options) {
+ var _this = this;
+
+ options = options || {};
+ var controller = _CoreManager2.default.getFileController();
+ if (!this._previousSave) {
+ if (this._source.format === 'file') {
+ this._previousSave = controller.saveFile(this._name, this._source).then(function (res) {
+ _this._name = res.name;
+ _this._url = res.url;
+ return _this;
+ });
+ } else {
+ this._previousSave = controller.saveBase64(this._name, this._source).then(function (res) {
+ _this._name = res.name;
+ _this._url = res.url;
+ return _this;
+ });
+ }
+ }
+ if (this._previousSave) {
+ return this._previousSave._thenRunCallbacks(options);
+ }
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return {
+ __type: 'File',
+ name: this._name,
+ url: this._url
+ };
+ }
+ }, {
+ key: 'equals',
+ value: function (other) {
+ if (this === other) {
+ return true;
+ }
+ // Unsaved Files are never equal, since they will be saved to different URLs
+ return other instanceof ParseFile && this.name() === other.name() && this.url() === other.url() && typeof this.url() !== 'undefined';
+ }
+ }], [{
+ key: 'fromJSON',
+ value: function (obj) {
+ if (obj.__type !== 'File') {
+ throw new TypeError('JSON object does not represent a ParseFile');
+ }
+ var file = new ParseFile(obj.name);
+ file._url = obj.url;
+ return file;
+ }
+ }, {
+ key: 'encodeBase64',
+ value: function (bytes) {
+ var chunks = [];
+ chunks.length = Math.ceil(bytes.length / 3);
+ for (var i = 0; i < chunks.length; i++) {
+ var b1 = bytes[i * 3];
+ var b2 = bytes[i * 3 + 1] || 0;
+ var b3 = bytes[i * 3 + 2] || 0;
+
+ var has2 = i * 3 + 1 < bytes.length;
+ var has3 = i * 3 + 2 < bytes.length;
+
+ chunks[i] = [b64Digit(b1 >> 2 & 0x3F), b64Digit(b1 << 4 & 0x30 | b2 >> 4 & 0x0F), has2 ? b64Digit(b2 << 2 & 0x3C | b3 >> 6 & 0x03) : '=', has3 ? b64Digit(b3 & 0x3F) : '='].join('');
+ }
+
+ return chunks.join('');
+ }
+ }]);
+ return ParseFile;
+}();
+
+exports.default = ParseFile;
+
+var DefaultController = {
+ saveFile: function (name, source) {
+ if (source.format !== 'file') {
+ throw new Error('saveFile can only be used with File-type sources.');
+ }
+ // To directly upload a File, we use a REST-style AJAX request
+ var headers = {
+ 'X-Parse-Application-ID': _CoreManager2.default.get('APPLICATION_ID'),
+ 'X-Parse-JavaScript-Key': _CoreManager2.default.get('JAVASCRIPT_KEY'),
+ 'Content-Type': source.type || (source.file ? source.file.type : null)
+ };
+ var url = _CoreManager2.default.get('SERVER_URL');
+ if (url[url.length - 1] !== '/') {
+ url += '/';
+ }
+ url += 'files/' + name;
+ return _CoreManager2.default.getRESTController().ajax('POST', url, source.file, headers);
+ },
+
+ saveBase64: function (name, source) {
+ if (source.format !== 'base64') {
+ throw new Error('saveBase64 can only be used with Base64-type sources.');
+ }
+ var data = {
+ base64: source.base64
+ };
+ if (source.type) {
+ data._ContentType = source.type;
+ }
+
+ return _CoreManager2.default.getRESTController().request('POST', 'files/' + name, data);
+ }
+};
+
+_CoreManager2.default.setFileController(DefaultController);
+},{"./CoreManager":3,"./ParsePromise":20,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57}],15:[function(_dereq_,module,exports){
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _typeof2 = _dereq_('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = _dereq_('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _ParsePromise = _dereq_('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Creates a new GeoPoint with any of the following forms:
+ * new GeoPoint(otherGeoPoint)
+ * new GeoPoint(30, 30)
+ * new GeoPoint([30, 30])
+ * new GeoPoint({latitude: 30, longitude: 30})
+ * new GeoPoint() // defaults to (0, 0)
+ *
+ * @class Parse.GeoPoint
+ * @constructor
+ *
+ * Represents a latitude / longitude point that may be associated + * with a key in a ParseObject or used as a reference point for geo queries. + * This allows proximity-based queries on the key.
+ * + *Only one key in a class may contain a GeoPoint.
+ * + *Example:
+ * var point = new Parse.GeoPoint(30.0, -20.0);
+ * var object = new Parse.Object("PlaceObject");
+ * object.set("location", point);
+ * object.save();
+ */
+var ParseGeoPoint = function () {
+ function ParseGeoPoint(arg1, arg2) {
+ (0, _classCallCheck3.default)(this, ParseGeoPoint);
+
+ if (Array.isArray(arg1)) {
+ ParseGeoPoint._validate(arg1[0], arg1[1]);
+ this._latitude = arg1[0];
+ this._longitude = arg1[1];
+ } else if ((typeof arg1 === 'undefined' ? 'undefined' : (0, _typeof3.default)(arg1)) === 'object') {
+ ParseGeoPoint._validate(arg1.latitude, arg1.longitude);
+ this._latitude = arg1.latitude;
+ this._longitude = arg1.longitude;
+ } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
+ ParseGeoPoint._validate(arg1, arg2);
+ this._latitude = arg1;
+ this._longitude = arg2;
+ } else {
+ this._latitude = 0;
+ this._longitude = 0;
+ }
+ }
+
+ /**
+ * North-south portion of the coordinate, in range [-90, 90].
+ * Throws an exception if set out of range in a modern browser.
+ * @property latitude
+ * @type Number
+ */
+
+ (0, _createClass3.default)(ParseGeoPoint, [{
+ key: 'toJSON',
+
+ /**
+ * Returns a JSON representation of the GeoPoint, suitable for Parse.
+ * @method toJSON
+ * @return {Object}
+ */
+ value: function () {
+ ParseGeoPoint._validate(this._latitude, this._longitude);
+ return {
+ __type: 'GeoPoint',
+ latitude: this._latitude,
+ longitude: this._longitude
+ };
+ }
+ }, {
+ key: 'equals',
+ value: function (other) {
+ return other instanceof ParseGeoPoint && this.latitude === other.latitude && this.longitude === other.longitude;
+ }
+
+ /**
+ * Returns the distance from this GeoPoint to another in radians.
+ * @method radiansTo
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+
+ }, {
+ key: 'radiansTo',
+ value: function (point) {
+ var d2r = Math.PI / 180.0;
+ var lat1rad = this.latitude * d2r;
+ var long1rad = this.longitude * d2r;
+ var lat2rad = point.latitude * d2r;
+ var long2rad = point.longitude * d2r;
+
+ var sinDeltaLatDiv2 = Math.sin((lat1rad - lat2rad) / 2);
+ var sinDeltaLongDiv2 = Math.sin((long1rad - long2rad) / 2);
+ // Square of half the straight line chord distance between both points.
+ var a = sinDeltaLatDiv2 * sinDeltaLatDiv2 + Math.cos(lat1rad) * Math.cos(lat2rad) * sinDeltaLongDiv2 * sinDeltaLongDiv2;
+ a = Math.min(1.0, a);
+ return 2 * Math.asin(Math.sqrt(a));
+ }
+
+ /**
+ * Returns the distance from this GeoPoint to another in kilometers.
+ * @method kilometersTo
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+
+ }, {
+ key: 'kilometersTo',
+ value: function (point) {
+ return this.radiansTo(point) * 6371.0;
+ }
+
+ /**
+ * Returns the distance from this GeoPoint to another in miles.
+ * @method milesTo
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+
+ }, {
+ key: 'milesTo',
+ value: function (point) {
+ return this.radiansTo(point) * 3958.8;
+ }
+
+ /**
+ * Throws an exception if the given lat-long is out of bounds.
+ */
+
+ }, {
+ key: 'latitude',
+ get: function () {
+ return this._latitude;
+ },
+ set: function (val) {
+ ParseGeoPoint._validate(val, this.longitude);
+ this._latitude = val;
+ }
+
+ /**
+ * East-west portion of the coordinate, in range [-180, 180].
+ * Throws if set out of range in a modern browser.
+ * @property longitude
+ * @type Number
+ */
+
+ }, {
+ key: 'longitude',
+ get: function () {
+ return this._longitude;
+ },
+ set: function (val) {
+ ParseGeoPoint._validate(this.latitude, val);
+ this._longitude = val;
+ }
+ }], [{
+ key: '_validate',
+ value: function (latitude, longitude) {
+ if (latitude !== latitude || longitude !== longitude) {
+ throw new TypeError('GeoPoint latitude and longitude must be valid numbers');
+ }
+ if (latitude < -90.0) {
+ throw new TypeError('GeoPoint latitude out of bounds: ' + latitude + ' < -90.0.');
+ }
+ if (latitude > 90.0) {
+ throw new TypeError('GeoPoint latitude out of bounds: ' + latitude + ' > 90.0.');
+ }
+ if (longitude < -180.0) {
+ throw new TypeError('GeoPoint longitude out of bounds: ' + longitude + ' < -180.0.');
+ }
+ if (longitude > 180.0) {
+ throw new TypeError('GeoPoint longitude out of bounds: ' + longitude + ' > 180.0.');
+ }
+ }
+
+ /**
+ * Creates a GeoPoint with the user's current location, if available.
+ * Calls options.success with a new GeoPoint instance or calls options.error.
+ * @method current
+ * @param {Object} options An object with success and error callbacks.
+ * @static
+ */
+
+ }, {
+ key: 'current',
+ value: function (options) {
+ var promise = new _ParsePromise2.default();
+ navigator.geolocation.getCurrentPosition(function (location) {
+ promise.resolve(new ParseGeoPoint(location.coords.latitude, location.coords.longitude));
+ }, function (error) {
+ promise.reject(error);
+ });
+
+ return promise._thenRunCallbacks(options);
+ }
+ }]);
+ return ParseGeoPoint;
+}(); /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+exports.default = ParseGeoPoint;
+},{"./ParsePromise":20,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57,"babel-runtime/helpers/typeof":61}],16:[function(_dereq_,module,exports){
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _typeof2 = _dereq_('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _getPrototypeOf = _dereq_('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _possibleConstructorReturn2 = _dereq_('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = _dereq_('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _ParseObject2 = _dereq_('./ParseObject');
+
+var _ParseObject3 = _interopRequireDefault(_ParseObject2);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+var Installation = function (_ParseObject) {
+ (0, _inherits3.default)(Installation, _ParseObject);
+
+ function Installation(attributes) {
+ (0, _classCallCheck3.default)(this, Installation);
+
+ var _this = (0, _possibleConstructorReturn3.default)(this, (Installation.__proto__ || (0, _getPrototypeOf2.default)(Installation)).call(this, '_Installation'));
+
+ if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') {
+ if (!_this.set(attributes || {})) {
+ throw new Error('Can\'t create an invalid Session');
+ }
+ }
+ return _this;
+ }
+
+ return Installation;
+}(_ParseObject3.default); /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+exports.default = Installation;
+
+_ParseObject3.default.registerSubclass('_Installation', Installation);
+},{"./ParseObject":18,"babel-runtime/core-js/object/get-prototype-of":50,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/inherits":59,"babel-runtime/helpers/possibleConstructorReturn":60,"babel-runtime/helpers/typeof":61}],17:[function(_dereq_,module,exports){
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _EventEmitter = _dereq_('./EventEmitter');
+
+var _EventEmitter2 = _interopRequireDefault(_EventEmitter);
+
+var _LiveQueryClient = _dereq_('./LiveQueryClient');
+
+var _LiveQueryClient2 = _interopRequireDefault(_LiveQueryClient);
+
+var _CoreManager = _dereq_('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _ParsePromise = _dereq_('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function open() {
+ var LiveQueryController = _CoreManager2.default.getLiveQueryController();
+ LiveQueryController.open();
+}
+
+function close() {
+ var LiveQueryController = _CoreManager2.default.getLiveQueryController();
+ LiveQueryController.close();
+}
+
+/**
+ *
+ * We expose three events to help you monitor the status of the WebSocket connection:
+ *
+ * Open - When we establish the WebSocket connection to the LiveQuery server, you'll get this event. + * + *
+ * Parse.LiveQuery.on('open', () => {
+ *
+ * });
+ *
+ * Close - When we lose the WebSocket connection to the LiveQuery server, you'll get this event. + * + *
+ * Parse.LiveQuery.on('close', () => {
+ *
+ * });
+ *
+ * Error - When some network error or LiveQuery server error happens, you'll get this event. + * + *
+ * Parse.LiveQuery.on('error', (error) => {
+ *
+ * });
+ *
+ * @class Parse.LiveQuery
+ * @static
+ *
+ */
+var LiveQuery = new _EventEmitter2.default();
+
+/**
+ * After open is called, the LiveQuery will try to send a connect request
+ * to the LiveQuery server.
+ *
+ * @method open
+ */
+LiveQuery.open = open;
+
+/**
+ * When you're done using LiveQuery, you can call Parse.LiveQuery.close().
+ * This function will close the WebSocket connection to the LiveQuery server,
+ * cancel the auto reconnect, and unsubscribe all subscriptions based on it.
+ * If you call query.subscribe() after this, we'll create a new WebSocket
+ * connection to the LiveQuery server.
+ *
+ * @method close
+ */
+
+LiveQuery.close = close;
+// Register a default onError callback to make sure we do not crash on error
+LiveQuery.on('error', function () {});
+
+exports.default = LiveQuery;
+
+function getSessionToken() {
+ var controller = _CoreManager2.default.getUserController();
+ return controller.currentUserAsync().then(function (currentUser) {
+ return currentUser ? currentUser.getSessionToken() : undefined;
+ });
+}
+
+function getLiveQueryClient() {
+ return _CoreManager2.default.getLiveQueryController().getDefaultLiveQueryClient();
+}
+
+var defaultLiveQueryClient = void 0;
+var DefaultLiveQueryController = {
+ setDefaultLiveQueryClient: function (liveQueryClient) {
+ defaultLiveQueryClient = liveQueryClient;
+ },
+ getDefaultLiveQueryClient: function () {
+ if (defaultLiveQueryClient) {
+ return _ParsePromise2.default.as(defaultLiveQueryClient);
+ }
+
+ return getSessionToken().then(function (sessionToken) {
+ var liveQueryServerURL = _CoreManager2.default.get('LIVEQUERY_SERVER_URL');
+
+ if (liveQueryServerURL && liveQueryServerURL.indexOf('ws') !== 0) {
+ throw new Error('You need to set a proper Parse LiveQuery server url before using LiveQueryClient');
+ }
+
+ // If we can not find Parse.liveQueryServerURL, we try to extract it from Parse.serverURL
+ if (!liveQueryServerURL) {
+ var tempServerURL = _CoreManager2.default.get('SERVER_URL');
+ var protocol = 'ws://';
+ // If Parse is being served over SSL/HTTPS, ensure LiveQuery Server uses 'wss://' prefix
+ if (tempServerURL.indexOf('https') === 0) {
+ protocol = 'wss://';
+ }
+ var host = tempServerURL.replace(/^https?:\/\//, '');
+ liveQueryServerURL = protocol + host;
+ _CoreManager2.default.set('LIVEQUERY_SERVER_URL', liveQueryServerURL);
+ }
+
+ var applicationId = _CoreManager2.default.get('APPLICATION_ID');
+ var javascriptKey = _CoreManager2.default.get('JAVASCRIPT_KEY');
+ var masterKey = _CoreManager2.default.get('MASTER_KEY');
+ // Get currentUser sessionToken if possible
+ defaultLiveQueryClient = new _LiveQueryClient2.default({
+ applicationId: applicationId,
+ serverURL: liveQueryServerURL,
+ javascriptKey: javascriptKey,
+ masterKey: masterKey,
+ sessionToken: sessionToken
+ });
+ // Register a default onError callback to make sure we do not crash on error
+ // Cannot create these events on a nested way because of EventEmiiter from React Native
+ defaultLiveQueryClient.on('error', function (error) {
+ LiveQuery.emit('error', error);
+ });
+ defaultLiveQueryClient.on('open', function () {
+ LiveQuery.emit('open');
+ });
+ defaultLiveQueryClient.on('close', function () {
+ LiveQuery.emit('close');
+ });
+
+ return defaultLiveQueryClient;
+ });
+ },
+ open: function () {
+ var _this = this;
+
+ getLiveQueryClient().then(function (liveQueryClient) {
+ _this.resolve(liveQueryClient.open());
+ });
+ },
+ close: function () {
+ var _this2 = this;
+
+ getLiveQueryClient().then(function (liveQueryClient) {
+ _this2.resolve(liveQueryClient.close());
+ });
+ },
+ subscribe: function (query) {
+ var _this3 = this;
+
+ var subscriptionWrap = new _EventEmitter2.default();
+
+ getLiveQueryClient().then(function (liveQueryClient) {
+ if (liveQueryClient.shouldOpen()) {
+ liveQueryClient.open();
+ }
+ var promiseSessionToken = getSessionToken();
+ // new event emitter
+ return promiseSessionToken.then(function (sessionToken) {
+
+ var subscription = liveQueryClient.subscribe(query, sessionToken);
+ // enter, leave create, etc
+
+ subscriptionWrap.id = subscription.id;
+ subscriptionWrap.query = subscription.query;
+ subscriptionWrap.sessionToken = subscription.sessionToken;
+ subscriptionWrap.unsubscribe = subscription.unsubscribe;
+ // Cannot create these events on a nested way because of EventEmiiter from React Native
+ subscription.on('open', function () {
+ subscriptionWrap.emit('open');
+ });
+ subscription.on('create', function (object) {
+ subscriptionWrap.emit('create', object);
+ });
+ subscription.on('update', function (object) {
+ subscriptionWrap.emit('update', object);
+ });
+ subscription.on('enter', function (object) {
+ subscriptionWrap.emit('enter', object);
+ });
+ subscription.on('leave', function (object) {
+ subscriptionWrap.emit('leave', object);
+ });
+ subscription.on('delete', function (object) {
+ subscriptionWrap.emit('delete', object);
+ });
+
+ _this3.resolve();
+ });
+ });
+ return subscriptionWrap;
+ },
+ unsubscribe: function (subscription) {
+ var _this4 = this;
+
+ getLiveQueryClient().then(function (liveQueryClient) {
+ _this4.resolve(liveQueryClient.unsubscribe(subscription));
+ });
+ },
+ _clearCachedDefaultClient: function () {
+ defaultLiveQueryClient = null;
+ }
+};
+
+_CoreManager2.default.setLiveQueryController(DefaultLiveQueryController);
+},{"./CoreManager":3,"./EventEmitter":4,"./LiveQueryClient":7,"./ParsePromise":20}],18:[function(_dereq_,module,exports){
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _defineProperty = _dereq_('babel-runtime/core-js/object/define-property');
+
+var _defineProperty2 = _interopRequireDefault(_defineProperty);
+
+var _create = _dereq_('babel-runtime/core-js/object/create');
+
+var _create2 = _interopRequireDefault(_create);
+
+var _freeze = _dereq_('babel-runtime/core-js/object/freeze');
+
+var _freeze2 = _interopRequireDefault(_freeze);
+
+var _stringify = _dereq_('babel-runtime/core-js/json/stringify');
+
+var _stringify2 = _interopRequireDefault(_stringify);
+
+var _keys = _dereq_('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
+var _typeof2 = _dereq_('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = _dereq_('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _CoreManager = _dereq_('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _canBeSerialized = _dereq_('./canBeSerialized');
+
+var _canBeSerialized2 = _interopRequireDefault(_canBeSerialized);
+
+var _decode = _dereq_('./decode');
+
+var _decode2 = _interopRequireDefault(_decode);
+
+var _encode = _dereq_('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _equals = _dereq_('./equals');
+
+var _equals2 = _interopRequireDefault(_equals);
+
+var _escape2 = _dereq_('./escape');
+
+var _escape3 = _interopRequireDefault(_escape2);
+
+var _ParseACL = _dereq_('./ParseACL');
+
+var _ParseACL2 = _interopRequireDefault(_ParseACL);
+
+var _parseDate = _dereq_('./parseDate');
+
+var _parseDate2 = _interopRequireDefault(_parseDate);
+
+var _ParseError = _dereq_('./ParseError');
+
+var _ParseError2 = _interopRequireDefault(_ParseError);
+
+var _ParseFile = _dereq_('./ParseFile');
+
+var _ParseFile2 = _interopRequireDefault(_ParseFile);
+
+var _ParseOp = _dereq_('./ParseOp');
+
+var _ParsePromise = _dereq_('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+var _ParseQuery = _dereq_('./ParseQuery');
+
+var _ParseQuery2 = _interopRequireDefault(_ParseQuery);
+
+var _ParseRelation = _dereq_('./ParseRelation');
+
+var _ParseRelation2 = _interopRequireDefault(_ParseRelation);
+
+var _SingleInstanceStateController = _dereq_('./SingleInstanceStateController');
+
+var SingleInstanceStateController = _interopRequireWildcard(_SingleInstanceStateController);
+
+var _unique = _dereq_('./unique');
+
+var _unique2 = _interopRequireDefault(_unique);
+
+var _UniqueInstanceStateController = _dereq_('./UniqueInstanceStateController');
+
+var UniqueInstanceStateController = _interopRequireWildcard(_UniqueInstanceStateController);
+
+var _unsavedChildren = _dereq_('./unsavedChildren');
+
+var _unsavedChildren2 = _interopRequireDefault(_unsavedChildren);
+
+function _interopRequireWildcard(obj) {
+ if (obj && obj.__esModule) {
+ return obj;
+ } else {
+ var newObj = {};if (obj != null) {
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];
+ }
+ }newObj.default = obj;return newObj;
+ }
+}
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+// Mapping of class names to constructors, so we can populate objects from the
+// server with appropriate subclasses of ParseObject
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+var classMap = {};
+
+// Global counter for generating unique local Ids
+var localCount = 0;
+// Global counter for generating unique Ids for non-single-instance objects
+var objectCount = 0;
+// On web clients, objects are single-instance: any two objects with the same Id
+// will have the same attributes. However, this may be dangerous default
+// behavior in a server scenario
+var singleInstance = !_CoreManager2.default.get('IS_NODE');
+if (singleInstance) {
+ _CoreManager2.default.setObjectStateController(SingleInstanceStateController);
+} else {
+ _CoreManager2.default.setObjectStateController(UniqueInstanceStateController);
+}
+
+function getServerUrlPath() {
+ var serverUrl = _CoreManager2.default.get('SERVER_URL');
+ if (serverUrl[serverUrl.length - 1] !== '/') {
+ serverUrl += '/';
+ }
+ var url = serverUrl.replace(/https?:\/\//, '');
+ return url.substr(url.indexOf('/'));
+}
+
+/**
+ * Creates a new model with defined attributes.
+ *
+ * You won't normally call this method directly. It is recommended that
+ * you use a subclass of Parse.Object instead, created by calling
+ * extend.
However, if you don't want to use a subclass, or aren't sure which + * subclass is appropriate, you can use this form:
+ * var object = new Parse.Object("ClassName");
+ *
+ * That is basically equivalent to:
+ * var MyClass = Parse.Object.extend("ClassName");
+ * var object = new MyClass();
+ *
+ *
+ * @class Parse.Object
+ * @constructor
+ * @param {String} className The class name for the object
+ * @param {Object} attributes The initial set of data to store in the object.
+ * @param {Object} options The options for this object instance.
+ */
+
+var ParseObject = function () {
+ /**
+ * The ID of this object, unique within its class.
+ * @property id
+ * @type String
+ */
+ function ParseObject(className, attributes, options) {
+ (0, _classCallCheck3.default)(this, ParseObject);
+
+ // Enable legacy initializers
+ if (typeof this.initialize === 'function') {
+ this.initialize.apply(this, arguments);
+ }
+
+ var toSet = null;
+ this._objCount = objectCount++;
+ if (typeof className === 'string') {
+ this.className = className;
+ if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') {
+ toSet = attributes;
+ }
+ } else if (className && (typeof className === 'undefined' ? 'undefined' : (0, _typeof3.default)(className)) === 'object') {
+ this.className = className.className;
+ toSet = {};
+ for (var attr in className) {
+ if (attr !== 'className') {
+ toSet[attr] = className[attr];
+ }
+ }
+ if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') {
+ options = attributes;
+ }
+ }
+ if (toSet && !this.set(toSet, options)) {
+ throw new Error('Can\'t create an invalid Parse Object');
+ }
+ }
+
+ /** Prototype getters / setters **/
+
+ (0, _createClass3.default)(ParseObject, [{
+ key: '_getId',
+
+ /** Private methods **/
+
+ /**
+ * Returns a local or server Id used uniquely identify this object
+ */
+ value: function () {
+ if (typeof this.id === 'string') {
+ return this.id;
+ }
+ if (typeof this._localId === 'string') {
+ return this._localId;
+ }
+ var localId = 'local' + String(localCount++);
+ this._localId = localId;
+ return localId;
+ }
+
+ /**
+ * Returns a unique identifier used to pull data from the State Controller.
+ */
+
+ }, {
+ key: '_getStateIdentifier',
+ value: function () {
+ if (singleInstance) {
+ var _id = this.id;
+ if (!_id) {
+ _id = this._getId();
+ }
+ return {
+ id: _id,
+ className: this.className
+ };
+ } else {
+ return this;
+ }
+ }
+ }, {
+ key: '_getServerData',
+ value: function () {
+ var stateController = _CoreManager2.default.getObjectStateController();
+ return stateController.getServerData(this._getStateIdentifier());
+ }
+ }, {
+ key: '_clearServerData',
+ value: function () {
+ var serverData = this._getServerData();
+ var unset = {};
+ for (var attr in serverData) {
+ unset[attr] = undefined;
+ }
+ var stateController = _CoreManager2.default.getObjectStateController();
+ stateController.setServerData(this._getStateIdentifier(), unset);
+ }
+ }, {
+ key: '_getPendingOps',
+ value: function () {
+ var stateController = _CoreManager2.default.getObjectStateController();
+ return stateController.getPendingOps(this._getStateIdentifier());
+ }
+ }, {
+ key: '_clearPendingOps',
+ value: function () {
+ var pending = this._getPendingOps();
+ var latest = pending[pending.length - 1];
+ var keys = (0, _keys2.default)(latest);
+ keys.forEach(function (key) {
+ delete latest[key];
+ });
+ }
+ }, {
+ key: '_getDirtyObjectAttributes',
+ value: function () {
+ var attributes = this.attributes;
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var objectCache = stateController.getObjectCache(this._getStateIdentifier());
+ var dirty = {};
+ for (var attr in attributes) {
+ var val = attributes[attr];
+ if (val && (typeof val === 'undefined' ? 'undefined' : (0, _typeof3.default)(val)) === 'object' && !(val instanceof ParseObject) && !(val instanceof _ParseFile2.default) && !(val instanceof _ParseRelation2.default)) {
+ // Due to the way browsers construct maps, the key order will not change
+ // unless the object is changed
+ try {
+ var json = (0, _encode2.default)(val, false, true);
+ var stringified = (0, _stringify2.default)(json);
+ if (objectCache[attr] !== stringified) {
+ dirty[attr] = val;
+ }
+ } catch (e) {
+ // Error occurred, possibly by a nested unsaved pointer in a mutable container
+ // No matter how it happened, it indicates a change in the attribute
+ dirty[attr] = val;
+ }
+ }
+ }
+ return dirty;
+ }
+ }, {
+ key: '_toFullJSON',
+ value: function (seen) {
+ var json = this.toJSON(seen);
+ json.__type = 'Object';
+ json.className = this.className;
+ return json;
+ }
+ }, {
+ key: '_getSaveJSON',
+ value: function () {
+ var pending = this._getPendingOps();
+ var dirtyObjects = this._getDirtyObjectAttributes();
+ var json = {};
+
+ for (var attr in dirtyObjects) {
+ json[attr] = new _ParseOp.SetOp(dirtyObjects[attr]).toJSON();
+ }
+ for (attr in pending[0]) {
+ json[attr] = pending[0][attr].toJSON();
+ }
+ return json;
+ }
+ }, {
+ key: '_getSaveParams',
+ value: function () {
+ var method = this.id ? 'PUT' : 'POST';
+ var body = this._getSaveJSON();
+ var path = 'classes/' + this.className;
+ if (this.id) {
+ path += '/' + this.id;
+ } else if (this.className === '_User') {
+ path = 'users';
+ }
+ return {
+ method: method,
+ body: body,
+ path: path
+ };
+ }
+ }, {
+ key: '_finishFetch',
+ value: function (serverData) {
+ if (!this.id && serverData.objectId) {
+ this.id = serverData.objectId;
+ }
+ var stateController = _CoreManager2.default.getObjectStateController();
+ stateController.initializeState(this._getStateIdentifier());
+ var decoded = {};
+ for (var attr in serverData) {
+ if (attr === 'ACL') {
+ decoded[attr] = new _ParseACL2.default(serverData[attr]);
+ } else if (attr !== 'objectId') {
+ decoded[attr] = (0, _decode2.default)(serverData[attr]);
+ if (decoded[attr] instanceof _ParseRelation2.default) {
+ decoded[attr]._ensureParentAndKey(this, attr);
+ }
+ }
+ }
+ if (decoded.createdAt && typeof decoded.createdAt === 'string') {
+ decoded.createdAt = (0, _parseDate2.default)(decoded.createdAt);
+ }
+ if (decoded.updatedAt && typeof decoded.updatedAt === 'string') {
+ decoded.updatedAt = (0, _parseDate2.default)(decoded.updatedAt);
+ }
+ if (!decoded.updatedAt && decoded.createdAt) {
+ decoded.updatedAt = decoded.createdAt;
+ }
+ stateController.commitServerChanges(this._getStateIdentifier(), decoded);
+ }
+ }, {
+ key: '_setExisted',
+ value: function (existed) {
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var state = stateController.getState(this._getStateIdentifier());
+ if (state) {
+ state.existed = existed;
+ }
+ }
+ }, {
+ key: '_migrateId',
+ value: function (serverId) {
+ if (this._localId && serverId) {
+ if (singleInstance) {
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var oldState = stateController.removeState(this._getStateIdentifier());
+ this.id = serverId;
+ delete this._localId;
+ if (oldState) {
+ stateController.initializeState(this._getStateIdentifier(), oldState);
+ }
+ } else {
+ this.id = serverId;
+ delete this._localId;
+ }
+ }
+ }
+ }, {
+ key: '_handleSaveResponse',
+ value: function (response, status) {
+ var changes = {};
+
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var pending = stateController.popPendingState(this._getStateIdentifier());
+ for (var attr in pending) {
+ if (pending[attr] instanceof _ParseOp.RelationOp) {
+ changes[attr] = pending[attr].applyTo(undefined, this, attr);
+ } else if (!(attr in response)) {
+ // Only SetOps and UnsetOps should not come back with results
+ changes[attr] = pending[attr].applyTo(undefined);
+ }
+ }
+ for (attr in response) {
+ if ((attr === 'createdAt' || attr === 'updatedAt') && typeof response[attr] === 'string') {
+ changes[attr] = (0, _parseDate2.default)(response[attr]);
+ } else if (attr === 'ACL') {
+ changes[attr] = new _ParseACL2.default(response[attr]);
+ } else if (attr !== 'objectId') {
+ changes[attr] = (0, _decode2.default)(response[attr]);
+ if (changes[attr] instanceof _ParseOp.UnsetOp) {
+ changes[attr] = undefined;
+ }
+ }
+ }
+ if (changes.createdAt && !changes.updatedAt) {
+ changes.updatedAt = changes.createdAt;
+ }
+
+ this._migrateId(response.objectId);
+
+ if (status !== 201) {
+ this._setExisted(true);
+ }
+
+ stateController.commitServerChanges(this._getStateIdentifier(), changes);
+ }
+ }, {
+ key: '_handleSaveError',
+ value: function () {
+ this._getPendingOps();
+
+ var stateController = _CoreManager2.default.getObjectStateController();
+ stateController.mergeFirstPendingState(this._getStateIdentifier());
+ }
+
+ /** Public methods **/
+
+ }, {
+ key: 'initialize',
+ value: function () {}
+ // NOOP
+
+
+ /**
+ * Returns a JSON version of the object suitable for saving to Parse.
+ * @method toJSON
+ * @return {Object}
+ */
+
+ }, {
+ key: 'toJSON',
+ value: function (seen) {
+ var seenEntry = this.id ? this.className + ':' + this.id : this;
+ var seen = seen || [seenEntry];
+ var json = {};
+ var attrs = this.attributes;
+ for (var attr in attrs) {
+ if ((attr === 'createdAt' || attr === 'updatedAt') && attrs[attr].toJSON) {
+ json[attr] = attrs[attr].toJSON();
+ } else {
+ json[attr] = (0, _encode2.default)(attrs[attr], false, false, seen);
+ }
+ }
+ var pending = this._getPendingOps();
+ for (var attr in pending[0]) {
+ json[attr] = pending[0][attr].toJSON();
+ }
+
+ if (this.id) {
+ json.objectId = this.id;
+ }
+ return json;
+ }
+
+ /**
+ * Determines whether this ParseObject is equal to another ParseObject
+ * @method equals
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'equals',
+ value: function (other) {
+ if (this === other) {
+ return true;
+ }
+ return other instanceof ParseObject && this.className === other.className && this.id === other.id && typeof this.id !== 'undefined';
+ }
+
+ /**
+ * Returns true if this object has been modified since its last
+ * save/refresh. If an attribute is specified, it returns true only if that
+ * particular attribute has been modified since the last save/refresh.
+ * @method dirty
+ * @param {String} attr An attribute name (optional).
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'dirty',
+ value: function (attr) {
+ if (!this.id) {
+ return true;
+ }
+ var pendingOps = this._getPendingOps();
+ var dirtyObjects = this._getDirtyObjectAttributes();
+ if (attr) {
+ if (dirtyObjects.hasOwnProperty(attr)) {
+ return true;
+ }
+ for (var i = 0; i < pendingOps.length; i++) {
+ if (pendingOps[i].hasOwnProperty(attr)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ if ((0, _keys2.default)(pendingOps[0]).length !== 0) {
+ return true;
+ }
+ if ((0, _keys2.default)(dirtyObjects).length !== 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns an array of keys that have been modified since last save/refresh
+ * @method dirtyKeys
+ * @return {Array of string}
+ */
+
+ }, {
+ key: 'dirtyKeys',
+ value: function () {
+ var pendingOps = this._getPendingOps();
+ var keys = {};
+ for (var i = 0; i < pendingOps.length; i++) {
+ for (var attr in pendingOps[i]) {
+ keys[attr] = true;
+ }
+ }
+ var dirtyObjects = this._getDirtyObjectAttributes();
+ for (var attr in dirtyObjects) {
+ keys[attr] = true;
+ }
+ return (0, _keys2.default)(keys);
+ }
+
+ /**
+ * Gets a Pointer referencing this Object.
+ * @method toPointer
+ * @return {Object}
+ */
+
+ }, {
+ key: 'toPointer',
+ value: function () {
+ if (!this.id) {
+ throw new Error('Cannot create a pointer to an unsaved ParseObject');
+ }
+ return {
+ __type: 'Pointer',
+ className: this.className,
+ objectId: this.id
+ };
+ }
+
+ /**
+ * Gets the value of an attribute.
+ * @method get
+ * @param {String} attr The string name of an attribute.
+ */
+
+ }, {
+ key: 'get',
+ value: function (attr) {
+ return this.attributes[attr];
+ }
+
+ /**
+ * Gets a relation on the given class for the attribute.
+ * @method relation
+ * @param String attr The attribute to get the relation for.
+ */
+
+ }, {
+ key: 'relation',
+ value: function (attr) {
+ var value = this.get(attr);
+ if (value) {
+ if (!(value instanceof _ParseRelation2.default)) {
+ throw new Error('Called relation() on non-relation field ' + attr);
+ }
+ value._ensureParentAndKey(this, attr);
+ return value;
+ }
+ return new _ParseRelation2.default(this, attr);
+ }
+
+ /**
+ * Gets the HTML-escaped value of an attribute.
+ * @method escape
+ * @param {String} attr The string name of an attribute.
+ */
+
+ }, {
+ key: 'escape',
+ value: function (attr) {
+ var val = this.attributes[attr];
+ if (val == null) {
+ return '';
+ }
+
+ if (typeof val !== 'string') {
+ if (typeof val.toString !== 'function') {
+ return '';
+ }
+ val = val.toString();
+ }
+ return (0, _escape3.default)(val);
+ }
+
+ /**
+ * Returns true if the attribute contains a value that is not
+ * null or undefined.
+ * @method has
+ * @param {String} attr The string name of the attribute.
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'has',
+ value: function (attr) {
+ var attributes = this.attributes;
+ if (attributes.hasOwnProperty(attr)) {
+ return attributes[attr] != null;
+ }
+ return false;
+ }
+
+ /**
+ * Sets a hash of model attributes on the object.
+ *
+ * You can call it with an object containing keys and values, or with one + * key and value. For example:
+ * gameTurn.set({
+ * player: player1,
+ * diceRoll: 2
+ * }, {
+ * error: function(gameTurnAgain, error) {
+ * // The set failed validation.
+ * }
+ * });
+ *
+ * game.set("currentPlayer", player2, {
+ * error: function(gameTurnAgain, error) {
+ * // The set failed validation.
+ * }
+ * });
+ *
+ * game.set("finished", true);
+ *
+ * @method set
+ * @param {String} key The key to set.
+ * @param {} value The value to give it.
+ * @param {Object} options A set of options for the set.
+ * The only supported option is error.
+ * @return {Boolean} true if the set succeeded.
+ */
+
+ }, {
+ key: 'set',
+ value: function (key, value, options) {
+ var changes = {};
+ var newOps = {};
+ if (key && (typeof key === 'undefined' ? 'undefined' : (0, _typeof3.default)(key)) === 'object') {
+ changes = key;
+ options = value;
+ } else if (typeof key === 'string') {
+ changes[key] = value;
+ } else {
+ return this;
+ }
+
+ options = options || {};
+ var readonly = [];
+ if (typeof this.constructor.readOnlyAttributes === 'function') {
+ readonly = readonly.concat(this.constructor.readOnlyAttributes());
+ }
+ for (var k in changes) {
+ if (k === 'createdAt' || k === 'updatedAt') {
+ // This property is read-only, but for legacy reasons we silently
+ // ignore it
+ continue;
+ }
+ if (readonly.indexOf(k) > -1) {
+ throw new Error('Cannot modify readonly attribute: ' + k);
+ }
+ if (options.unset) {
+ newOps[k] = new _ParseOp.UnsetOp();
+ } else if (changes[k] instanceof _ParseOp.Op) {
+ newOps[k] = changes[k];
+ } else if (changes[k] && (0, _typeof3.default)(changes[k]) === 'object' && typeof changes[k].__op === 'string') {
+ newOps[k] = (0, _ParseOp.opFromJSON)(changes[k]);
+ } else if (k === 'objectId' || k === 'id') {
+ if (typeof changes[k] === 'string') {
+ this.id = changes[k];
+ }
+ } else if (k === 'ACL' && (0, _typeof3.default)(changes[k]) === 'object' && !(changes[k] instanceof _ParseACL2.default)) {
+ newOps[k] = new _ParseOp.SetOp(new _ParseACL2.default(changes[k]));
+ } else {
+ newOps[k] = new _ParseOp.SetOp(changes[k]);
+ }
+ }
+
+ // Calculate new values
+ var currentAttributes = this.attributes;
+ var newValues = {};
+ for (var attr in newOps) {
+ if (newOps[attr] instanceof _ParseOp.RelationOp) {
+ newValues[attr] = newOps[attr].applyTo(currentAttributes[attr], this, attr);
+ } else if (!(newOps[attr] instanceof _ParseOp.UnsetOp)) {
+ newValues[attr] = newOps[attr].applyTo(currentAttributes[attr]);
+ }
+ }
+
+ // Validate changes
+ if (!options.ignoreValidation) {
+ var validation = this.validate(newValues);
+ if (validation) {
+ if (typeof options.error === 'function') {
+ options.error(this, validation);
+ }
+ return false;
+ }
+ }
+
+ // Consolidate Ops
+ var pendingOps = this._getPendingOps();
+ var last = pendingOps.length - 1;
+ var stateController = _CoreManager2.default.getObjectStateController();
+ for (var attr in newOps) {
+ var nextOp = newOps[attr].mergeWith(pendingOps[last][attr]);
+ stateController.setPendingOp(this._getStateIdentifier(), attr, nextOp);
+ }
+
+ return this;
+ }
+
+ /**
+ * Remove an attribute from the model. This is a noop if the attribute doesn't
+ * exist.
+ * @method unset
+ * @param {String} attr The string name of an attribute.
+ */
+
+ }, {
+ key: 'unset',
+ value: function (attr, options) {
+ options = options || {};
+ options.unset = true;
+ return this.set(attr, null, options);
+ }
+
+ /**
+ * Atomically increments the value of the given attribute the next time the
+ * object is saved. If no amount is specified, 1 is used by default.
+ *
+ * @method increment
+ * @param attr {String} The key.
+ * @param amount {Number} The amount to increment by (optional).
+ */
+
+ }, {
+ key: 'increment',
+ value: function (attr, amount) {
+ if (typeof amount === 'undefined') {
+ amount = 1;
+ }
+ if (typeof amount !== 'number') {
+ throw new Error('Cannot increment by a non-numeric amount.');
+ }
+ return this.set(attr, new _ParseOp.IncrementOp(amount));
+ }
+
+ /**
+ * Atomically add an object to the end of the array associated with a given
+ * key.
+ * @method add
+ * @param attr {String} The key.
+ * @param item {} The item to add.
+ */
+
+ }, {
+ key: 'add',
+ value: function (attr, item) {
+ return this.set(attr, new _ParseOp.AddOp([item]));
+ }
+
+ /**
+ * Atomically add an object to the array associated with a given key, only
+ * if it is not already present in the array. The position of the insert is
+ * not guaranteed.
+ *
+ * @method addUnique
+ * @param attr {String} The key.
+ * @param item {} The object to add.
+ */
+
+ }, {
+ key: 'addUnique',
+ value: function (attr, item) {
+ return this.set(attr, new _ParseOp.AddUniqueOp([item]));
+ }
+
+ /**
+ * Atomically remove all instances of an object from the array associated
+ * with a given key.
+ *
+ * @method remove
+ * @param attr {String} The key.
+ * @param item {} The object to remove.
+ */
+
+ }, {
+ key: 'remove',
+ value: function (attr, item) {
+ return this.set(attr, new _ParseOp.RemoveOp([item]));
+ }
+
+ /**
+ * Returns an instance of a subclass of Parse.Op describing what kind of
+ * modification has been performed on this field since the last time it was
+ * saved. For example, after calling object.increment("x"), calling
+ * object.op("x") would return an instance of Parse.Op.Increment.
+ *
+ * @method op
+ * @param attr {String} The key.
+ * @returns {Parse.Op} The operation, or undefined if none.
+ */
+
+ }, {
+ key: 'op',
+ value: function (attr) {
+ var pending = this._getPendingOps();
+ for (var i = pending.length; i--;) {
+ if (pending[i][attr]) {
+ return pending[i][attr];
+ }
+ }
+ }
+
+ /**
+ * Creates a new model with identical attributes to this one, similar to Backbone.Model's clone()
+ * @method clone
+ * @return {Parse.Object}
+ */
+
+ }, {
+ key: 'clone',
+ value: function () {
+ var clone = new this.constructor();
+ if (!clone.className) {
+ clone.className = this.className;
+ }
+ var attributes = this.attributes;
+ if (typeof this.constructor.readOnlyAttributes === 'function') {
+ var readonly = this.constructor.readOnlyAttributes() || [];
+ // Attributes are frozen, so we have to rebuild an object,
+ // rather than delete readonly keys
+ var copy = {};
+ for (var a in attributes) {
+ if (readonly.indexOf(a) < 0) {
+ copy[a] = attributes[a];
+ }
+ }
+ attributes = copy;
+ }
+ if (clone.set) {
+ clone.set(attributes);
+ }
+ return clone;
+ }
+
+ /**
+ * Creates a new instance of this object. Not to be confused with clone()
+ * @method newInstance
+ * @return {Parse.Object}
+ */
+
+ }, {
+ key: 'newInstance',
+ value: function () {
+ var clone = new this.constructor();
+ if (!clone.className) {
+ clone.className = this.className;
+ }
+ clone.id = this.id;
+ if (singleInstance) {
+ // Just return an object with the right id
+ return clone;
+ }
+
+ var stateController = _CoreManager2.default.getObjectStateController();
+ if (stateController) {
+ stateController.duplicateState(this._getStateIdentifier(), clone._getStateIdentifier());
+ }
+ return clone;
+ }
+
+ /**
+ * Returns true if this object has never been saved to Parse.
+ * @method isNew
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'isNew',
+ value: function () {
+ return !this.id;
+ }
+
+ /**
+ * Returns true if this object was created by the Parse server when the
+ * object might have already been there (e.g. in the case of a Facebook
+ * login)
+ * @method existed
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'existed',
+ value: function () {
+ if (!this.id) {
+ return false;
+ }
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var state = stateController.getState(this._getStateIdentifier());
+ if (state) {
+ return state.existed;
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the model is currently in a valid state.
+ * @method isValid
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'isValid',
+ value: function () {
+ return !this.validate(this.attributes);
+ }
+
+ /**
+ * You should not call this function directly unless you subclass
+ * Parse.Object, in which case you can override this method
+ * to provide additional validation on set and
+ * save. Your implementation should return
+ *
+ * @method validate
+ * @param {Object} attrs The current data to validate.
+ * @return {} False if the data is valid. An error object otherwise.
+ * @see Parse.Object#set
+ */
+
+ }, {
+ key: 'validate',
+ value: function (attrs) {
+ if (attrs.hasOwnProperty('ACL') && !(attrs.ACL instanceof _ParseACL2.default)) {
+ return new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'ACL must be a Parse ACL.');
+ }
+ for (var key in attrs) {
+ if (!/^[A-Za-z][0-9A-Za-z_]*$/.test(key)) {
+ return new _ParseError2.default(_ParseError2.default.INVALID_KEY_NAME);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the ACL for this object.
+ * @method getACL
+ * @returns {Parse.ACL} An instance of Parse.ACL.
+ * @see Parse.Object#get
+ */
+
+ }, {
+ key: 'getACL',
+ value: function () {
+ var acl = this.get('ACL');
+ if (acl instanceof _ParseACL2.default) {
+ return acl;
+ }
+ return null;
+ }
+
+ /**
+ * Sets the ACL to be used for this object.
+ * @method setACL
+ * @param {Parse.ACL} acl An instance of Parse.ACL.
+ * @param {Object} options Optional Backbone-like options object to be
+ * passed in to set.
+ * @return {Boolean} Whether the set passed validation.
+ * @see Parse.Object#set
+ */
+
+ }, {
+ key: 'setACL',
+ value: function (acl, options) {
+ return this.set('ACL', acl, options);
+ }
+
+ /**
+ * Clears any changes to this object made since the last call to save()
+ * @method revert
+ */
+
+ }, {
+ key: 'revert',
+ value: function () {
+ this._clearPendingOps();
+ }
+
+ /**
+ * Clears all attributes on a model
+ * @method clear
+ */
+
+ }, {
+ key: 'clear',
+ value: function () {
+ var attributes = this.attributes;
+ var erasable = {};
+ var readonly = ['createdAt', 'updatedAt'];
+ if (typeof this.constructor.readOnlyAttributes === 'function') {
+ readonly = readonly.concat(this.constructor.readOnlyAttributes());
+ }
+ for (var attr in attributes) {
+ if (readonly.indexOf(attr) < 0) {
+ erasable[attr] = true;
+ }
+ }
+ return this.set(erasable, { unset: true });
+ }
+
+ /**
+ * Fetch the model from the server. If the server's representation of the
+ * model differs from its current attributes, they will be overriden.
+ *
+ * @method fetch
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:+ * object.save();+ * or
+ * object.save(null, options);+ * or
+ * object.save(attrs, options);+ * or
+ * object.save(key, value, options);+ * + * For example,
+ * gameTurn.save({
+ * player: "Jake Cutter",
+ * diceRoll: 2
+ * }, {
+ * success: function(gameTurnAgain) {
+ * // The save was successful.
+ * },
+ * error: function(gameTurnAgain, error) {
+ * // The save failed. Error is an instance of Parse.Error.
+ * }
+ * });
+ * or with promises:
+ * gameTurn.save({
+ * player: "Jake Cutter",
+ * diceRoll: 2
+ * }).then(function(gameTurnAgain) {
+ * // The save was successful.
+ * }, function(error) {
+ * // The save failed. Error is an instance of Parse.Error.
+ * });
+ *
+ * @method save
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:
+ * Parse.Object.fetchAll([object1, object2, ...], {
+ * success: function(list) {
+ * // All the objects were fetched.
+ * },
+ * error: function(error) {
+ * // An error occurred while fetching one of the objects.
+ * },
+ * });
+ *
+ *
+ * @method fetchAll
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:
+ * Parse.Object.fetchAllIfNeeded([object1, ...], {
+ * success: function(list) {
+ * // Objects were fetched and updated.
+ * },
+ * error: function(error) {
+ * // An error occurred while fetching one of the objects.
+ * },
+ * });
+ *
+ *
+ * @method fetchAllIfNeeded
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:Unlike saveAll, if an error occurs while deleting an individual model, + * this method will continue trying to delete the rest of the models if + * possible, except in the case of a fatal error like a connection error. + * + *
In particular, the Parse.Error object returned in the case of error may + * be one of two types: + * + *
+ * Parse.Object.destroyAll([object1, object2, ...], {
+ * success: function() {
+ * // All the objects were deleted.
+ * },
+ * error: function(error) {
+ * // An error occurred while deleting one or more of the objects.
+ * // If this is an aggregate error, then we can inspect each error
+ * // object individually to determine the reason why a particular
+ * // object was not deleted.
+ * if (error.code === Parse.Error.AGGREGATE_ERROR) {
+ * for (var i = 0; i < error.errors.length; i++) {
+ * console.log("Couldn't delete " + error.errors[i].object.id +
+ * "due to " + error.errors[i].message);
+ * }
+ * } else {
+ * console.log("Delete aborted because of " + error.message);
+ * }
+ * },
+ * });
+ *
+ *
+ * @method destroyAll
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:
+ * Parse.Object.saveAll([object1, object2, ...], {
+ * success: function(list) {
+ * // All the objects were saved.
+ * },
+ * error: function(error) {
+ * // An error occurred while saving one of the objects.
+ * },
+ * });
+ *
+ *
+ * @method saveAll
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:A shortcut for:
+ * var Foo = Parse.Object.extend("Foo");
+ * var pointerToFoo = new Foo();
+ * pointerToFoo.id = "myObjectId";
+ *
+ *
+ * @method createWithoutData
+ * @param {String} id The ID of the object to create a reference to.
+ * @static
+ * @return {Parse.Object} A Parse.Object reference.
+ */
+
+ }, {
+ key: 'createWithoutData',
+ value: function (id) {
+ var obj = new this();
+ obj.id = id;
+ return obj;
+ }
+
+ /**
+ * Creates a new instance of a Parse Object from a JSON representation.
+ * @method fromJSON
+ * @param {Object} json The JSON map of the Object's data
+ * @param {boolean} override In single instance mode, all old server data
+ * is overwritten if this is set to true
+ * @static
+ * @return {Parse.Object} A Parse.Object reference
+ */
+
+ }, {
+ key: 'fromJSON',
+ value: function (json, override) {
+ if (!json.className) {
+ throw new Error('Cannot create an object without a className');
+ }
+ var constructor = classMap[json.className];
+ var o = constructor ? new constructor() : new ParseObject(json.className);
+ var otherAttributes = {};
+ for (var attr in json) {
+ if (attr !== 'className' && attr !== '__type') {
+ otherAttributes[attr] = json[attr];
+ }
+ }
+ if (override) {
+ // id needs to be set before clearServerData can work
+ if (otherAttributes.objectId) {
+ o.id = otherAttributes.objectId;
+ }
+ var preserved = null;
+ if (typeof o._preserveFieldsOnFetch === 'function') {
+ preserved = o._preserveFieldsOnFetch();
+ }
+ o._clearServerData();
+ if (preserved) {
+ o._finishFetch(preserved);
+ }
+ }
+ o._finishFetch(otherAttributes);
+ if (json.objectId) {
+ o._setExisted(true);
+ }
+ return o;
+ }
+
+ /**
+ * Registers a subclass of Parse.Object with a specific class name.
+ * When objects of that class are retrieved from a query, they will be
+ * instantiated with this subclass.
+ * This is only necessary when using ES6 subclassing.
+ * @method registerSubclass
+ * @param {String} className The class name of the subclass
+ * @param {Class} constructor The subclass
+ */
+
+ }, {
+ key: 'registerSubclass',
+ value: function (className, constructor) {
+ if (typeof className !== 'string') {
+ throw new TypeError('The first argument must be a valid class name.');
+ }
+ if (typeof constructor === 'undefined') {
+ throw new TypeError('You must supply a subclass constructor.');
+ }
+ if (typeof constructor !== 'function') {
+ throw new TypeError('You must register the subclass constructor. ' + 'Did you attempt to register an instance of the subclass?');
+ }
+ classMap[className] = constructor;
+ if (!constructor.className) {
+ constructor.className = className;
+ }
+ }
+
+ /**
+ * Creates a new subclass of Parse.Object for the given Parse class name.
+ *
+ * Every extension of a Parse class will inherit from the most recent + * previous extension of that class. When a Parse.Object is automatically + * created by parsing JSON, it will use the most recent extension of that + * class.
+ * + *You should call either:
+ * var MyClass = Parse.Object.extend("MyClass", {
+ * Instance methods,
+ * initialize: function(attrs, options) {
+ * this.someInstanceProperty = [],
+ * Other instance properties
+ * }
+ * }, {
+ * Class properties
+ * });
+ * or, for Backbone compatibility:
+ * var MyClass = Parse.Object.extend({
+ * className: "MyClass",
+ * Instance methods,
+ * initialize: function(attrs, options) {
+ * this.someInstanceProperty = [],
+ * Other instance properties
+ * }
+ * }, {
+ * Class properties
+ * });
+ *
+ * @method extend
+ * @param {String} className The name of the Parse class backing this model.
+ * @param {Object} protoProps Instance properties to add to instances of the
+ * class returned from this method.
+ * @param {Object} classProps Class properties to add the class returned from
+ * this method.
+ * @return {Class} A new subclass of Parse.Object.
+ */
+
+ }, {
+ key: 'extend',
+ value: function (className, protoProps, classProps) {
+ if (typeof className !== 'string') {
+ if (className && typeof className.className === 'string') {
+ return ParseObject.extend(className.className, className, protoProps);
+ } else {
+ throw new Error('Parse.Object.extend\'s first argument should be the className.');
+ }
+ }
+ var adjustedClassName = className;
+
+ if (adjustedClassName === 'User' && _CoreManager2.default.get('PERFORM_USER_REWRITE')) {
+ adjustedClassName = '_User';
+ }
+
+ var parentProto = ParseObject.prototype;
+ if (this.hasOwnProperty('__super__') && this.__super__) {
+ parentProto = this.prototype;
+ } else if (classMap[adjustedClassName]) {
+ parentProto = classMap[adjustedClassName].prototype;
+ }
+ var ParseObjectSubclass = function (attributes, options) {
+ this.className = adjustedClassName;
+ this._objCount = objectCount++;
+ // Enable legacy initializers
+ if (typeof this.initialize === 'function') {
+ this.initialize.apply(this, arguments);
+ }
+
+ if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') {
+ if (!this.set(attributes || {}, options)) {
+ throw new Error('Can\'t create an invalid Parse Object');
+ }
+ }
+ };
+ ParseObjectSubclass.className = adjustedClassName;
+ ParseObjectSubclass.__super__ = parentProto;
+
+ ParseObjectSubclass.prototype = (0, _create2.default)(parentProto, {
+ constructor: {
+ value: ParseObjectSubclass,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+ });
+
+ if (protoProps) {
+ for (var prop in protoProps) {
+ if (prop !== 'className') {
+ (0, _defineProperty2.default)(ParseObjectSubclass.prototype, prop, {
+ value: protoProps[prop],
+ enumerable: false,
+ writable: true,
+ configurable: true
+ });
+ }
+ }
+ }
+
+ if (classProps) {
+ for (var prop in classProps) {
+ if (prop !== 'className') {
+ (0, _defineProperty2.default)(ParseObjectSubclass, prop, {
+ value: classProps[prop],
+ enumerable: false,
+ writable: true,
+ configurable: true
+ });
+ }
+ }
+ }
+
+ ParseObjectSubclass.extend = function (name, protoProps, classProps) {
+ if (typeof name === 'string') {
+ return ParseObject.extend.call(ParseObjectSubclass, name, protoProps, classProps);
+ }
+ return ParseObject.extend.call(ParseObjectSubclass, adjustedClassName, name, protoProps);
+ };
+ ParseObjectSubclass.createWithoutData = ParseObject.createWithoutData;
+
+ classMap[adjustedClassName] = ParseObjectSubclass;
+ return ParseObjectSubclass;
+ }
+
+ /**
+ * Enable single instance objects, where any local objects with the same Id
+ * share the same attributes, and stay synchronized with each other.
+ * This is disabled by default in server environments, since it can lead to
+ * security issues.
+ * @method enableSingleInstance
+ */
+
+ }, {
+ key: 'enableSingleInstance',
+ value: function () {
+ singleInstance = true;
+ _CoreManager2.default.setObjectStateController(SingleInstanceStateController);
+ }
+
+ /**
+ * Disable single instance objects, where any local objects with the same Id
+ * share the same attributes, and stay synchronized with each other.
+ * When disabled, you can have two instances of the same object in memory
+ * without them sharing attributes.
+ * @method disableSingleInstance
+ */
+
+ }, {
+ key: 'disableSingleInstance',
+ value: function () {
+ singleInstance = false;
+ _CoreManager2.default.setObjectStateController(UniqueInstanceStateController);
+ }
+ }]);
+ return ParseObject;
+}();
+
+exports.default = ParseObject;
+
+var DefaultController = {
+ fetch: function (target, forceFetch, options) {
+ if (Array.isArray(target)) {
+ if (target.length < 1) {
+ return _ParsePromise2.default.as([]);
+ }
+ var objs = [];
+ var ids = [];
+ var className = null;
+ var results = [];
+ var error = null;
+ target.forEach(function (el, i) {
+ if (error) {
+ return;
+ }
+ if (!className) {
+ className = el.className;
+ }
+ if (className !== el.className) {
+ error = new _ParseError2.default(_ParseError2.default.INVALID_CLASS_NAME, 'All objects should be of the same class');
+ }
+ if (!el.id) {
+ error = new _ParseError2.default(_ParseError2.default.MISSING_OBJECT_ID, 'All objects must have an ID');
+ }
+ if (forceFetch || (0, _keys2.default)(el._getServerData()).length === 0) {
+ ids.push(el.id);
+ objs.push(el);
+ }
+ results.push(el);
+ });
+ if (error) {
+ return _ParsePromise2.default.error(error);
+ }
+ var query = new _ParseQuery2.default(className);
+ query.containedIn('objectId', ids);
+ query._limit = ids.length;
+ return query.find(options).then(function (objects) {
+ var idMap = {};
+ objects.forEach(function (o) {
+ idMap[o.id] = o;
+ });
+ for (var i = 0; i < objs.length; i++) {
+ var obj = objs[i];
+ if (!obj || !obj.id || !idMap[obj.id]) {
+ if (forceFetch) {
+ return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OBJECT_NOT_FOUND, 'All objects must exist on the server.'));
+ }
+ }
+ }
+ if (!singleInstance) {
+ // If single instance objects are disabled, we need to replace the
+ for (var i = 0; i < results.length; i++) {
+ var obj = results[i];
+ if (obj && obj.id && idMap[obj.id]) {
+ var id = obj.id;
+ obj._finishFetch(idMap[id].toJSON());
+ results[i] = idMap[id];
+ }
+ }
+ }
+ return _ParsePromise2.default.as(results);
+ });
+ } else {
+ var RESTController = _CoreManager2.default.getRESTController();
+ return RESTController.request('GET', 'classes/' + target.className + '/' + target._getId(), {}, options).then(function (response, status, xhr) {
+ if (target instanceof ParseObject) {
+ target._clearPendingOps();
+ target._clearServerData();
+ target._finishFetch(response);
+ }
+ return target;
+ });
+ }
+ },
+ destroy: function (target, options) {
+ var RESTController = _CoreManager2.default.getRESTController();
+ if (Array.isArray(target)) {
+ if (target.length < 1) {
+ return _ParsePromise2.default.as([]);
+ }
+ var batches = [[]];
+ target.forEach(function (obj) {
+ if (!obj.id) {
+ return;
+ }
+ batches[batches.length - 1].push(obj);
+ if (batches[batches.length - 1].length >= 20) {
+ batches.push([]);
+ }
+ });
+ if (batches[batches.length - 1].length === 0) {
+ // If the last batch is empty, remove it
+ batches.pop();
+ }
+ var deleteCompleted = _ParsePromise2.default.as();
+ var errors = [];
+ batches.forEach(function (batch) {
+ deleteCompleted = deleteCompleted.then(function () {
+ return RESTController.request('POST', 'batch', {
+ requests: batch.map(function (obj) {
+ return {
+ method: 'DELETE',
+ path: getServerUrlPath() + 'classes/' + obj.className + '/' + obj._getId(),
+ body: {}
+ };
+ })
+ }, options).then(function (results) {
+ for (var i = 0; i < results.length; i++) {
+ if (results[i] && results[i].hasOwnProperty('error')) {
+ var err = new _ParseError2.default(results[i].error.code, results[i].error.error);
+ err.object = batch[i];
+ errors.push(err);
+ }
+ }
+ });
+ });
+ });
+ return deleteCompleted.then(function () {
+ if (errors.length) {
+ var aggregate = new _ParseError2.default(_ParseError2.default.AGGREGATE_ERROR);
+ aggregate.errors = errors;
+ return _ParsePromise2.default.error(aggregate);
+ }
+ return _ParsePromise2.default.as(target);
+ });
+ } else if (target instanceof ParseObject) {
+ return RESTController.request('DELETE', 'classes/' + target.className + '/' + target._getId(), {}, options).then(function () {
+ return _ParsePromise2.default.as(target);
+ });
+ }
+ return _ParsePromise2.default.as(target);
+ },
+ save: function (target, options) {
+ var RESTController = _CoreManager2.default.getRESTController();
+ var stateController = _CoreManager2.default.getObjectStateController();
+ if (Array.isArray(target)) {
+ if (target.length < 1) {
+ return _ParsePromise2.default.as([]);
+ }
+
+ var unsaved = target.concat();
+ for (var i = 0; i < target.length; i++) {
+ if (target[i] instanceof ParseObject) {
+ unsaved = unsaved.concat((0, _unsavedChildren2.default)(target[i], true));
+ }
+ }
+ unsaved = (0, _unique2.default)(unsaved);
+
+ var filesSaved = _ParsePromise2.default.as();
+ var pending = [];
+ unsaved.forEach(function (el) {
+ if (el instanceof _ParseFile2.default) {
+ filesSaved = filesSaved.then(function () {
+ return el.save();
+ });
+ } else if (el instanceof ParseObject) {
+ pending.push(el);
+ }
+ });
+
+ return filesSaved.then(function () {
+ var objectError = null;
+ return _ParsePromise2.default._continueWhile(function () {
+ return pending.length > 0;
+ }, function () {
+ var batch = [];
+ var nextPending = [];
+ pending.forEach(function (el) {
+ if (batch.length < 20 && (0, _canBeSerialized2.default)(el)) {
+ batch.push(el);
+ } else {
+ nextPending.push(el);
+ }
+ });
+ pending = nextPending;
+ if (batch.length < 1) {
+ return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Tried to save a batch with a cycle.'));
+ }
+
+ // Queue up tasks for each object in the batch.
+ // When every task is ready, the API request will execute
+ var batchReturned = new _ParsePromise2.default();
+ var batchReady = [];
+ var batchTasks = [];
+ batch.forEach(function (obj, index) {
+ var ready = new _ParsePromise2.default();
+ batchReady.push(ready);
+
+ stateController.pushPendingState(obj._getStateIdentifier());
+ batchTasks.push(stateController.enqueueTask(obj._getStateIdentifier(), function () {
+ ready.resolve();
+ return batchReturned.then(function (responses, status) {
+ if (responses[index].hasOwnProperty('success')) {
+ obj._handleSaveResponse(responses[index].success, status);
+ } else {
+ if (!objectError && responses[index].hasOwnProperty('error')) {
+ var serverError = responses[index].error;
+ objectError = new _ParseError2.default(serverError.code, serverError.error);
+ // Cancel the rest of the save
+ pending = [];
+ }
+ obj._handleSaveError();
+ }
+ });
+ }));
+ });
+
+ _ParsePromise2.default.when(batchReady).then(function () {
+ // Kick off the batch request
+ return RESTController.request('POST', 'batch', {
+ requests: batch.map(function (obj) {
+ var params = obj._getSaveParams();
+ params.path = getServerUrlPath() + params.path;
+ return params;
+ })
+ }, options);
+ }).then(function (response, status, xhr) {
+ batchReturned.resolve(response, status);
+ });
+
+ return _ParsePromise2.default.when(batchTasks);
+ }).then(function () {
+ if (objectError) {
+ return _ParsePromise2.default.error(objectError);
+ }
+ return _ParsePromise2.default.as(target);
+ });
+ });
+ } else if (target instanceof ParseObject) {
+ // copying target lets Flow guarantee the pointer isn't modified elsewhere
+ var targetCopy = target;
+ var task = function () {
+ var params = targetCopy._getSaveParams();
+ return RESTController.request(params.method, params.path, params.body, options).then(function (response, status) {
+ targetCopy._handleSaveResponse(response, status);
+ }, function (error) {
+ targetCopy._handleSaveError();
+ return _ParsePromise2.default.error(error);
+ });
+ };
+
+ stateController.pushPendingState(target._getStateIdentifier());
+ return stateController.enqueueTask(target._getStateIdentifier(), task).then(function () {
+ return target;
+ }, function (error) {
+ return _ParsePromise2.default.error(error);
+ });
+ }
+ return _ParsePromise2.default.as();
+ }
+};
+
+_CoreManager2.default.setObjectController(DefaultController);
+},{"./CoreManager":3,"./ParseACL":11,"./ParseError":13,"./ParseFile":14,"./ParseOp":19,"./ParsePromise":20,"./ParseQuery":21,"./ParseRelation":22,"./SingleInstanceStateController":28,"./UniqueInstanceStateController":32,"./canBeSerialized":34,"./decode":35,"./encode":36,"./equals":37,"./escape":38,"./parseDate":40,"./unique":41,"./unsavedChildren":42,"babel-runtime/core-js/json/stringify":44,"babel-runtime/core-js/object/create":46,"babel-runtime/core-js/object/define-property":47,"babel-runtime/core-js/object/freeze":48,"babel-runtime/core-js/object/keys":51,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57,"babel-runtime/helpers/typeof":61}],19:[function(_dereq_,module,exports){
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.RelationOp = exports.RemoveOp = exports.AddUniqueOp = exports.AddOp = exports.IncrementOp = exports.UnsetOp = exports.SetOp = exports.Op = undefined;
+
+var _getPrototypeOf = _dereq_('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _possibleConstructorReturn2 = _dereq_('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = _dereq_('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = _dereq_('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+exports.opFromJSON = opFromJSON;
+
+var _arrayContainsObject = _dereq_('./arrayContainsObject');
+
+var _arrayContainsObject2 = _interopRequireDefault(_arrayContainsObject);
+
+var _decode = _dereq_('./decode');
+
+var _decode2 = _interopRequireDefault(_decode);
+
+var _encode = _dereq_('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _ParseObject = _dereq_('./ParseObject');
+
+var _ParseObject2 = _interopRequireDefault(_ParseObject);
+
+var _ParseRelation = _dereq_('./ParseRelation');
+
+var _ParseRelation2 = _interopRequireDefault(_ParseRelation);
+
+var _unique = _dereq_('./unique');
+
+var _unique2 = _interopRequireDefault(_unique);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function opFromJSON(json) {
+ if (!json || !json.__op) {
+ return null;
+ }
+ switch (json.__op) {
+ case 'Delete':
+ return new UnsetOp();
+ case 'Increment':
+ return new IncrementOp(json.amount);
+ case 'Add':
+ return new AddOp((0, _decode2.default)(json.objects));
+ case 'AddUnique':
+ return new AddUniqueOp((0, _decode2.default)(json.objects));
+ case 'Remove':
+ return new RemoveOp((0, _decode2.default)(json.objects));
+ case 'AddRelation':
+ var toAdd = (0, _decode2.default)(json.objects);
+ if (!Array.isArray(toAdd)) {
+ return new RelationOp([], []);
+ }
+ return new RelationOp(toAdd, []);
+ case 'RemoveRelation':
+ var toRemove = (0, _decode2.default)(json.objects);
+ if (!Array.isArray(toRemove)) {
+ return new RelationOp([], []);
+ }
+ return new RelationOp([], toRemove);
+ case 'Batch':
+ var toAdd = [];
+ var toRemove = [];
+ for (var i = 0; i < json.ops.length; i++) {
+ if (json.ops[i].__op === 'AddRelation') {
+ toAdd = toAdd.concat((0, _decode2.default)(json.ops[i].objects));
+ } else if (json.ops[i].__op === 'RemoveRelation') {
+ toRemove = toRemove.concat((0, _decode2.default)(json.ops[i].objects));
+ }
+ }
+ return new RelationOp(toAdd, toRemove);
+ }
+ return null;
+}
+
+var Op = exports.Op = function () {
+ function Op() {
+ (0, _classCallCheck3.default)(this, Op);
+ }
+
+ (0, _createClass3.default)(Op, [{
+ key: 'applyTo',
+
+ // Empty parent class
+ value: function (value) {}
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {}
+ }, {
+ key: 'toJSON',
+ value: function () {}
+ }]);
+ return Op;
+}();
+
+var SetOp = exports.SetOp = function (_Op) {
+ (0, _inherits3.default)(SetOp, _Op);
+
+ function SetOp(value) {
+ (0, _classCallCheck3.default)(this, SetOp);
+
+ var _this = (0, _possibleConstructorReturn3.default)(this, (SetOp.__proto__ || (0, _getPrototypeOf2.default)(SetOp)).call(this));
+
+ _this._value = value;
+ return _this;
+ }
+
+ (0, _createClass3.default)(SetOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ return this._value;
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ return new SetOp(this._value);
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return (0, _encode2.default)(this._value, false, true);
+ }
+ }]);
+ return SetOp;
+}(Op);
+
+var UnsetOp = exports.UnsetOp = function (_Op2) {
+ (0, _inherits3.default)(UnsetOp, _Op2);
+
+ function UnsetOp() {
+ (0, _classCallCheck3.default)(this, UnsetOp);
+ return (0, _possibleConstructorReturn3.default)(this, (UnsetOp.__proto__ || (0, _getPrototypeOf2.default)(UnsetOp)).apply(this, arguments));
+ }
+
+ (0, _createClass3.default)(UnsetOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ return undefined;
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ return new UnsetOp();
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'Delete' };
+ }
+ }]);
+ return UnsetOp;
+}(Op);
+
+var IncrementOp = exports.IncrementOp = function (_Op3) {
+ (0, _inherits3.default)(IncrementOp, _Op3);
+
+ function IncrementOp(amount) {
+ (0, _classCallCheck3.default)(this, IncrementOp);
+
+ var _this3 = (0, _possibleConstructorReturn3.default)(this, (IncrementOp.__proto__ || (0, _getPrototypeOf2.default)(IncrementOp)).call(this));
+
+ if (typeof amount !== 'number') {
+ throw new TypeError('Increment Op must be initialized with a numeric amount.');
+ }
+ _this3._amount = amount;
+ return _this3;
+ }
+
+ (0, _createClass3.default)(IncrementOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ if (typeof value === 'undefined') {
+ return this._amount;
+ }
+ if (typeof value !== 'number') {
+ throw new TypeError('Cannot increment a non-numeric value.');
+ }
+ return this._amount + value;
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new SetOp(this._amount);
+ }
+ if (previous instanceof IncrementOp) {
+ return new IncrementOp(this.applyTo(previous._amount));
+ }
+ throw new Error('Cannot merge Increment Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'Increment', amount: this._amount };
+ }
+ }]);
+ return IncrementOp;
+}(Op);
+
+var AddOp = exports.AddOp = function (_Op4) {
+ (0, _inherits3.default)(AddOp, _Op4);
+
+ function AddOp(value) {
+ (0, _classCallCheck3.default)(this, AddOp);
+
+ var _this4 = (0, _possibleConstructorReturn3.default)(this, (AddOp.__proto__ || (0, _getPrototypeOf2.default)(AddOp)).call(this));
+
+ _this4._value = Array.isArray(value) ? value : [value];
+ return _this4;
+ }
+
+ (0, _createClass3.default)(AddOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ if (value == null) {
+ return this._value;
+ }
+ if (Array.isArray(value)) {
+ return value.concat(this._value);
+ }
+ throw new Error('Cannot add elements to a non-array value');
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new SetOp(this._value);
+ }
+ if (previous instanceof AddOp) {
+ return new AddOp(this.applyTo(previous._value));
+ }
+ throw new Error('Cannot merge Add Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'Add', objects: (0, _encode2.default)(this._value, false, true) };
+ }
+ }]);
+ return AddOp;
+}(Op);
+
+var AddUniqueOp = exports.AddUniqueOp = function (_Op5) {
+ (0, _inherits3.default)(AddUniqueOp, _Op5);
+
+ function AddUniqueOp(value) {
+ (0, _classCallCheck3.default)(this, AddUniqueOp);
+
+ var _this5 = (0, _possibleConstructorReturn3.default)(this, (AddUniqueOp.__proto__ || (0, _getPrototypeOf2.default)(AddUniqueOp)).call(this));
+
+ _this5._value = (0, _unique2.default)(Array.isArray(value) ? value : [value]);
+ return _this5;
+ }
+
+ (0, _createClass3.default)(AddUniqueOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ if (value == null) {
+ return this._value || [];
+ }
+ if (Array.isArray(value)) {
+ // copying value lets Flow guarantee the pointer isn't modified elsewhere
+ var valueCopy = value;
+ var toAdd = [];
+ this._value.forEach(function (v) {
+ if (v instanceof _ParseObject2.default) {
+ if (!(0, _arrayContainsObject2.default)(valueCopy, v)) {
+ toAdd.push(v);
+ }
+ } else {
+ if (valueCopy.indexOf(v) < 0) {
+ toAdd.push(v);
+ }
+ }
+ });
+ return value.concat(toAdd);
+ }
+ throw new Error('Cannot add elements to a non-array value');
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new SetOp(this._value);
+ }
+ if (previous instanceof AddUniqueOp) {
+ return new AddUniqueOp(this.applyTo(previous._value));
+ }
+ throw new Error('Cannot merge AddUnique Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'AddUnique', objects: (0, _encode2.default)(this._value, false, true) };
+ }
+ }]);
+ return AddUniqueOp;
+}(Op);
+
+var RemoveOp = exports.RemoveOp = function (_Op6) {
+ (0, _inherits3.default)(RemoveOp, _Op6);
+
+ function RemoveOp(value) {
+ (0, _classCallCheck3.default)(this, RemoveOp);
+
+ var _this6 = (0, _possibleConstructorReturn3.default)(this, (RemoveOp.__proto__ || (0, _getPrototypeOf2.default)(RemoveOp)).call(this));
+
+ _this6._value = (0, _unique2.default)(Array.isArray(value) ? value : [value]);
+ return _this6;
+ }
+
+ (0, _createClass3.default)(RemoveOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ if (value == null) {
+ return [];
+ }
+ if (Array.isArray(value)) {
+ var i = value.indexOf(this._value);
+ var removed = value.concat([]);
+ for (var i = 0; i < this._value.length; i++) {
+ var index = removed.indexOf(this._value[i]);
+ while (index > -1) {
+ removed.splice(index, 1);
+ index = removed.indexOf(this._value[i]);
+ }
+ if (this._value[i] instanceof _ParseObject2.default && this._value[i].id) {
+ for (var j = 0; j < removed.length; j++) {
+ if (removed[j] instanceof _ParseObject2.default && this._value[i].id === removed[j].id) {
+ removed.splice(j, 1);
+ j--;
+ }
+ }
+ }
+ }
+ return removed;
+ }
+ throw new Error('Cannot remove elements from a non-array value');
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new UnsetOp();
+ }
+ if (previous instanceof RemoveOp) {
+ var uniques = previous._value.concat([]);
+ for (var i = 0; i < this._value.length; i++) {
+ if (this._value[i] instanceof _ParseObject2.default) {
+ if (!(0, _arrayContainsObject2.default)(uniques, this._value[i])) {
+ uniques.push(this._value[i]);
+ }
+ } else {
+ if (uniques.indexOf(this._value[i]) < 0) {
+ uniques.push(this._value[i]);
+ }
+ }
+ }
+ return new RemoveOp(uniques);
+ }
+ throw new Error('Cannot merge Remove Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'Remove', objects: (0, _encode2.default)(this._value, false, true) };
+ }
+ }]);
+ return RemoveOp;
+}(Op);
+
+var RelationOp = exports.RelationOp = function (_Op7) {
+ (0, _inherits3.default)(RelationOp, _Op7);
+
+ function RelationOp(adds, removes) {
+ (0, _classCallCheck3.default)(this, RelationOp);
+
+ var _this7 = (0, _possibleConstructorReturn3.default)(this, (RelationOp.__proto__ || (0, _getPrototypeOf2.default)(RelationOp)).call(this));
+
+ _this7._targetClassName = null;
+
+ if (Array.isArray(adds)) {
+ _this7.relationsToAdd = (0, _unique2.default)(adds.map(_this7._extractId, _this7));
+ }
+
+ if (Array.isArray(removes)) {
+ _this7.relationsToRemove = (0, _unique2.default)(removes.map(_this7._extractId, _this7));
+ }
+ return _this7;
+ }
+
+ (0, _createClass3.default)(RelationOp, [{
+ key: '_extractId',
+ value: function (obj) {
+ if (typeof obj === 'string') {
+ return obj;
+ }
+ if (!obj.id) {
+ throw new Error('You cannot add or remove an unsaved Parse Object from a relation');
+ }
+ if (!this._targetClassName) {
+ this._targetClassName = obj.className;
+ }
+ if (this._targetClassName !== obj.className) {
+ throw new Error('Tried to create a Relation with 2 different object types: ' + this._targetClassName + ' and ' + obj.className + '.');
+ }
+ return obj.id;
+ }
+ }, {
+ key: 'applyTo',
+ value: function (value, object, key) {
+ if (!value) {
+ if (!object || !key) {
+ throw new Error('Cannot apply a RelationOp without either a previous value, or an object and a key');
+ }
+ var parent = new _ParseObject2.default(object.className);
+ if (object.id && object.id.indexOf('local') === 0) {
+ parent._localId = object.id;
+ } else if (object.id) {
+ parent.id = object.id;
+ }
+ var relation = new _ParseRelation2.default(parent, key);
+ relation.targetClassName = this._targetClassName;
+ return relation;
+ }
+ if (value instanceof _ParseRelation2.default) {
+ if (this._targetClassName) {
+ if (value.targetClassName) {
+ if (this._targetClassName !== value.targetClassName) {
+ throw new Error('Related object must be a ' + value.targetClassName + ', but a ' + this._targetClassName + ' was passed in.');
+ }
+ } else {
+ value.targetClassName = this._targetClassName;
+ }
+ }
+ return value;
+ } else {
+ throw new Error('Relation cannot be applied to a non-relation field');
+ }
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ } else if (previous instanceof UnsetOp) {
+ throw new Error('You cannot modify a relation after deleting it.');
+ } else if (previous instanceof RelationOp) {
+ if (previous._targetClassName && previous._targetClassName !== this._targetClassName) {
+ throw new Error('Related object must be of class ' + previous._targetClassName + ', but ' + (this._targetClassName || 'null') + ' was passed in.');
+ }
+ var newAdd = previous.relationsToAdd.concat([]);
+ this.relationsToRemove.forEach(function (r) {
+ var index = newAdd.indexOf(r);
+ if (index > -1) {
+ newAdd.splice(index, 1);
+ }
+ });
+ this.relationsToAdd.forEach(function (r) {
+ var index = newAdd.indexOf(r);
+ if (index < 0) {
+ newAdd.push(r);
+ }
+ });
+
+ var newRemove = previous.relationsToRemove.concat([]);
+ this.relationsToAdd.forEach(function (r) {
+ var index = newRemove.indexOf(r);
+ if (index > -1) {
+ newRemove.splice(index, 1);
+ }
+ });
+ this.relationsToRemove.forEach(function (r) {
+ var index = newRemove.indexOf(r);
+ if (index < 0) {
+ newRemove.push(r);
+ }
+ });
+
+ var newRelation = new RelationOp(newAdd, newRemove);
+ newRelation._targetClassName = this._targetClassName;
+ return newRelation;
+ }
+ throw new Error('Cannot merge Relation Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ var _this8 = this;
+
+ var idToPointer = function (id) {
+ return {
+ __type: 'Pointer',
+ className: _this8._targetClassName,
+ objectId: id
+ };
+ };
+
+ var adds = null;
+ var removes = null;
+ var pointers = null;
+
+ if (this.relationsToAdd.length > 0) {
+ pointers = this.relationsToAdd.map(idToPointer);
+ adds = { __op: 'AddRelation', objects: pointers };
+ }
+ if (this.relationsToRemove.length > 0) {
+ pointers = this.relationsToRemove.map(idToPointer);
+ removes = { __op: 'RemoveRelation', objects: pointers };
+ }
+
+ if (adds && removes) {
+ return { __op: 'Batch', ops: [adds, removes] };
+ }
+
+ return adds || removes || {};
+ }
+ }]);
+ return RelationOp;
+}(Op);
+},{"./ParseObject":18,"./ParseRelation":22,"./arrayContainsObject":33,"./decode":35,"./encode":36,"./unique":41,"babel-runtime/core-js/object/get-prototype-of":50,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57,"babel-runtime/helpers/inherits":59,"babel-runtime/helpers/possibleConstructorReturn":60}],20:[function(_dereq_,module,exports){
+(function (process){
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _getIterator2 = _dereq_('babel-runtime/core-js/get-iterator');
+
+var _getIterator3 = _interopRequireDefault(_getIterator2);
+
+var _typeof2 = _dereq_('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = _dereq_('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+var _isPromisesAPlusCompliant = true;
+
+/**
+ * A Promise is returned by async methods as a hook to provide callbacks to be
+ * called when the async task is fulfilled.
+ *
+ * Typical usage would be like:
+ * query.find().then(function(results) {
+ * results[0].set("foo", "bar");
+ * return results[0].saveAsync();
+ * }).then(function(result) {
+ * console.log("Updated " + result.id);
+ * });
+ *
+ *
+ * @class Parse.Promise
+ * @constructor
+ */
+
+var ParsePromise = function () {
+ function ParsePromise(executor) {
+ (0, _classCallCheck3.default)(this, ParsePromise);
+
+ this._resolved = false;
+ this._rejected = false;
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+
+ if (typeof executor === 'function') {
+ executor(this.resolve.bind(this), this.reject.bind(this));
+ }
+ }
+
+ /**
+ * Marks this promise as fulfilled, firing any callbacks waiting on it.
+ * @method resolve
+ * @param {Object} result the result to pass to the callbacks.
+ */
+
+ (0, _createClass3.default)(ParsePromise, [{
+ key: 'resolve',
+ value: function () {
+ if (this._resolved || this._rejected) {
+ throw new Error('A promise was resolved even though it had already been ' + (this._resolved ? 'resolved' : 'rejected') + '.');
+ }
+ this._resolved = true;
+
+ for (var _len = arguments.length, results = Array(_len), _key = 0; _key < _len; _key++) {
+ results[_key] = arguments[_key];
+ }
+
+ this._result = results;
+ for (var i = 0; i < this._resolvedCallbacks.length; i++) {
+ this._resolvedCallbacks[i].apply(this, results);
+ }
+
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+ }
+
+ /**
+ * Marks this promise as fulfilled, firing any callbacks waiting on it.
+ * @method reject
+ * @param {Object} error the error to pass to the callbacks.
+ */
+
+ }, {
+ key: 'reject',
+ value: function (error) {
+ if (this._resolved || this._rejected) {
+ throw new Error('A promise was rejected even though it had already been ' + (this._resolved ? 'resolved' : 'rejected') + '.');
+ }
+ this._rejected = true;
+ this._error = error;
+ for (var i = 0; i < this._rejectedCallbacks.length; i++) {
+ this._rejectedCallbacks[i](error);
+ }
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+ }
+
+ /**
+ * Adds callbacks to be called when this promise is fulfilled. Returns a new
+ * Promise that will be fulfilled when the callback is complete. It allows
+ * chaining. If the callback itself returns a Promise, then the one returned
+ * by "then" will not be fulfilled until that one returned by the callback
+ * is fulfilled.
+ * @method then
+ * @param {Function} resolvedCallback Function that is called when this
+ * Promise is resolved. Once the callback is complete, then the Promise
+ * returned by "then" will also be fulfilled.
+ * @param {Function} rejectedCallback Function that is called when this
+ * Promise is rejected with an error. Once the callback is complete, then
+ * the promise returned by "then" with be resolved successfully. If
+ * rejectedCallback is null, or it returns a rejected Promise, then the
+ * Promise returned by "then" will be rejected with that error.
+ * @return {Parse.Promise} A new Promise that will be fulfilled after this
+ * Promise is fulfilled and either callback has completed. If the callback
+ * returned a Promise, then this Promise will not be fulfilled until that
+ * one is.
+ */
+
+ }, {
+ key: 'then',
+ value: function (resolvedCallback, rejectedCallback) {
+ var _this = this;
+
+ var promise = new ParsePromise();
+
+ var wrappedResolvedCallback = function () {
+ for (var _len2 = arguments.length, results = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ results[_key2] = arguments[_key2];
+ }
+
+ if (typeof resolvedCallback === 'function') {
+ if (_isPromisesAPlusCompliant) {
+ try {
+ results = [resolvedCallback.apply(this, results)];
+ } catch (e) {
+ results = [ParsePromise.error(e)];
+ }
+ } else {
+ results = [resolvedCallback.apply(this, results)];
+ }
+ }
+ if (results.length === 1 && ParsePromise.is(results[0])) {
+ results[0].then(function () {
+ promise.resolve.apply(promise, arguments);
+ }, function (error) {
+ promise.reject(error);
+ });
+ } else {
+ promise.resolve.apply(promise, results);
+ }
+ };
+
+ var wrappedRejectedCallback = function (error) {
+ var result = [];
+ if (typeof rejectedCallback === 'function') {
+ if (_isPromisesAPlusCompliant) {
+ try {
+ result = [rejectedCallback(error)];
+ } catch (e) {
+ result = [ParsePromise.error(e)];
+ }
+ } else {
+ result = [rejectedCallback(error)];
+ }
+ if (result.length === 1 && ParsePromise.is(result[0])) {
+ result[0].then(function () {
+ promise.resolve.apply(promise, arguments);
+ }, function (error) {
+ promise.reject(error);
+ });
+ } else {
+ if (_isPromisesAPlusCompliant) {
+ promise.resolve.apply(promise, result);
+ } else {
+ promise.reject(result[0]);
+ }
+ }
+ } else {
+ promise.reject(error);
+ }
+ };
+
+ var runLater = function (fn) {
+ fn.call();
+ };
+ if (_isPromisesAPlusCompliant) {
+ if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
+ runLater = function (fn) {
+ process.nextTick(fn);
+ };
+ } else if (typeof setTimeout === 'function') {
+ runLater = function (fn) {
+ setTimeout(fn, 0);
+ };
+ }
+ }
+
+ if (this._resolved) {
+ runLater(function () {
+ wrappedResolvedCallback.apply(_this, _this._result);
+ });
+ } else if (this._rejected) {
+ runLater(function () {
+ wrappedRejectedCallback(_this._error);
+ });
+ } else {
+ this._resolvedCallbacks.push(wrappedResolvedCallback);
+ this._rejectedCallbacks.push(wrappedRejectedCallback);
+ }
+
+ return promise;
+ }
+
+ /**
+ * Add handlers to be called when the promise
+ * is either resolved or rejected
+ * @method always
+ */
+
+ }, {
+ key: 'always',
+ value: function (callback) {
+ return this.then(callback, callback);
+ }
+
+ /**
+ * Add handlers to be called when the Promise object is resolved
+ * @method done
+ */
+
+ }, {
+ key: 'done',
+ value: function (callback) {
+ return this.then(callback);
+ }
+
+ /**
+ * Add handlers to be called when the Promise object is rejected
+ * Alias for catch().
+ * @method fail
+ */
+
+ }, {
+ key: 'fail',
+ value: function (callback) {
+ return this.then(null, callback);
+ }
+
+ /**
+ * Add handlers to be called when the Promise object is rejected
+ * @method catch
+ */
+
+ }, {
+ key: 'catch',
+ value: function (callback) {
+ return this.then(null, callback);
+ }
+
+ /**
+ * Run the given callbacks after this promise is fulfilled.
+ * @method _thenRunCallbacks
+ * @param optionsOrCallback {} A Backbone-style options callback, or a
+ * callback function. If this is an options object and contains a "model"
+ * attributes, that will be passed to error callbacks as the first argument.
+ * @param model {} If truthy, this will be passed as the first result of
+ * error callbacks. This is for Backbone-compatability.
+ * @return {Parse.Promise} A promise that will be resolved after the
+ * callbacks are run, with the same result as this.
+ */
+
+ }, {
+ key: '_thenRunCallbacks',
+ value: function (optionsOrCallback, model) {
+ var options = {};
+ if (typeof optionsOrCallback === 'function') {
+ options.success = function (result) {
+ optionsOrCallback(result, null);
+ };
+ options.error = function (error) {
+ optionsOrCallback(null, error);
+ };
+ } else if ((typeof optionsOrCallback === 'undefined' ? 'undefined' : (0, _typeof3.default)(optionsOrCallback)) === 'object') {
+ if (typeof optionsOrCallback.success === 'function') {
+ options.success = optionsOrCallback.success;
+ }
+ if (typeof optionsOrCallback.error === 'function') {
+ options.error = optionsOrCallback.error;
+ }
+ }
+
+ return this.then(function () {
+ for (var _len3 = arguments.length, results = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
+ results[_key3] = arguments[_key3];
+ }
+
+ if (options.success) {
+ options.success.apply(this, results);
+ }
+ return ParsePromise.as.apply(ParsePromise, arguments);
+ }, function (error) {
+ if (options.error) {
+ if (typeof model !== 'undefined') {
+ options.error(model, error);
+ } else {
+ options.error(error);
+ }
+ }
+ // By explicitly returning a rejected Promise, this will work with
+ // either jQuery or Promises/A+ semantics.
+ return ParsePromise.error(error);
+ });
+ }
+
+ /**
+ * Adds a callback function that should be called regardless of whether
+ * this promise failed or succeeded. The callback will be given either the
+ * array of results for its first argument, or the error as its second,
+ * depending on whether this Promise was rejected or resolved. Returns a
+ * new Promise, like "then" would.
+ * @method _continueWith
+ * @param {Function} continuation the callback.
+ */
+
+ }, {
+ key: '_continueWith',
+ value: function (continuation) {
+ return this.then(function () {
+ for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
+ args[_key4] = arguments[_key4];
+ }
+
+ return continuation(args, null);
+ }, function (error) {
+ return continuation(null, error);
+ });
+ }
+
+ /**
+ * Returns true iff the given object fulfils the Promise interface.
+ * @method is
+ * @param {Object} promise The object to test
+ * @static
+ * @return {Boolean}
+ */
+
+ }], [{
+ key: 'is',
+ value: function (promise) {
+ return promise != null && typeof promise.then === 'function';
+ }
+
+ /**
+ * Returns a new promise that is resolved with a given value.
+ * @method as
+ * @param value The value to resolve the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'as',
+ value: function () {
+ var promise = new ParsePromise();
+
+ for (var _len5 = arguments.length, values = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
+ values[_key5] = arguments[_key5];
+ }
+
+ promise.resolve.apply(promise, values);
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is resolved with a given value.
+ * If that value is a thenable Promise (has a .then() prototype
+ * method), the new promise will be chained to the end of the
+ * value.
+ * @method resolve
+ * @param value The value to resolve the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'resolve',
+ value: function (value) {
+ return new ParsePromise(function (resolve, reject) {
+ if (ParsePromise.is(value)) {
+ value.then(resolve, reject);
+ } else {
+ resolve(value);
+ }
+ });
+ }
+
+ /**
+ * Returns a new promise that is rejected with a given error.
+ * @method error
+ * @param error The error to reject the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'error',
+ value: function () {
+ var promise = new ParsePromise();
+
+ for (var _len6 = arguments.length, errors = Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
+ errors[_key6] = arguments[_key6];
+ }
+
+ promise.reject.apply(promise, errors);
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is rejected with a given error.
+ * This is an alias for Parse.Promise.error, for compliance with
+ * the ES6 implementation.
+ * @method reject
+ * @param error The error to reject the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'reject',
+ value: function () {
+ for (var _len7 = arguments.length, errors = Array(_len7), _key7 = 0; _key7 < _len7; _key7++) {
+ errors[_key7] = arguments[_key7];
+ }
+
+ return ParsePromise.error.apply(null, errors);
+ }
+
+ /**
+ * Returns a new promise that is fulfilled when all of the input promises
+ * are resolved. If any promise in the list fails, then the returned promise
+ * will be rejected with an array containing the error from each promise.
+ * If they all succeed, then the returned promise will succeed, with the
+ * results being the results of all the input
+ * promises. For example:
+ * var p1 = Parse.Promise.as(1);
+ * var p2 = Parse.Promise.as(2);
+ * var p3 = Parse.Promise.as(3);
+ *
+ * Parse.Promise.when(p1, p2, p3).then(function(r1, r2, r3) {
+ * console.log(r1); // prints 1
+ * console.log(r2); // prints 2
+ * console.log(r3); // prints 3
+ * });
+ *
+ * The input promises can also be specified as an array:
+ * var promises = [p1, p2, p3];
+ * Parse.Promise.when(promises).then(function(results) {
+ * console.log(results); // prints [1,2,3]
+ * });
+ *
+ * @method when
+ * @param {Array} promises a list of promises to wait for.
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'when',
+ value: function (promises) {
+ var objects;
+ var arrayArgument = Array.isArray(promises);
+ if (arrayArgument) {
+ objects = promises;
+ } else {
+ objects = arguments;
+ }
+
+ var total = objects.length;
+ var hadError = false;
+ var results = [];
+ var returnValue = arrayArgument ? [results] : results;
+ var errors = [];
+ results.length = objects.length;
+ errors.length = objects.length;
+
+ if (total === 0) {
+ return ParsePromise.as.apply(this, returnValue);
+ }
+
+ var promise = new ParsePromise();
+
+ var resolveOne = function () {
+ total--;
+ if (total <= 0) {
+ if (hadError) {
+ promise.reject(errors);
+ } else {
+ promise.resolve.apply(promise, returnValue);
+ }
+ }
+ };
+
+ var chain = function (object, index) {
+ if (ParsePromise.is(object)) {
+ object.then(function (result) {
+ results[index] = result;
+ resolveOne();
+ }, function (error) {
+ errors[index] = error;
+ hadError = true;
+ resolveOne();
+ });
+ } else {
+ results[i] = object;
+ resolveOne();
+ }
+ };
+ for (var i = 0; i < objects.length; i++) {
+ chain(objects[i], i);
+ }
+
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is fulfilled when all of the promises in the
+ * iterable argument are resolved. If any promise in the list fails, then
+ * the returned promise will be immediately rejected with the reason that
+ * single promise rejected. If they all succeed, then the returned promise
+ * will succeed, with the results being the results of all the input
+ * promises. If the iterable provided is empty, the returned promise will
+ * be immediately resolved.
+ *
+ * For example:
+ * var p1 = Parse.Promise.as(1);
+ * var p2 = Parse.Promise.as(2);
+ * var p3 = Parse.Promise.as(3);
+ *
+ * Parse.Promise.all([p1, p2, p3]).then(function([r1, r2, r3]) {
+ * console.log(r1); // prints 1
+ * console.log(r2); // prints 2
+ * console.log(r3); // prints 3
+ * });
+ *
+ * @method all
+ * @param {Iterable} promises an iterable of promises to wait for.
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'all',
+ value: function (promises) {
+ var total = 0;
+ var objects = [];
+
+ var _iteratorNormalCompletion = true;
+ var _didIteratorError = false;
+ var _iteratorError = undefined;
+
+ try {
+ for (var _iterator = (0, _getIterator3.default)(promises), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+ var p = _step.value;
+
+ objects[total++] = p;
+ }
+ } catch (err) {
+ _didIteratorError = true;
+ _iteratorError = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion && _iterator.return) {
+ _iterator.return();
+ }
+ } finally {
+ if (_didIteratorError) {
+ throw _iteratorError;
+ }
+ }
+ }
+
+ if (total === 0) {
+ return ParsePromise.as([]);
+ }
+
+ var hadError = false;
+ var promise = new ParsePromise();
+ var resolved = 0;
+ var results = [];
+ objects.forEach(function (object, i) {
+ if (ParsePromise.is(object)) {
+ object.then(function (result) {
+ if (hadError) {
+ return false;
+ }
+ results[i] = result;
+ resolved++;
+ if (resolved >= total) {
+ promise.resolve(results);
+ }
+ }, function (error) {
+ // Reject immediately
+ promise.reject(error);
+ hadError = true;
+ });
+ } else {
+ results[i] = object;
+ resolved++;
+ if (!hadError && resolved >= total) {
+ promise.resolve(results);
+ }
+ }
+ });
+
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is immediately fulfilled when any of the
+ * promises in the iterable argument are resolved or rejected. If the
+ * first promise to complete is resolved, the returned promise will be
+ * resolved with the same value. Likewise, if the first promise to
+ * complete is rejected, the returned promise will be rejected with the
+ * same reason.
+ *
+ * @method race
+ * @param {Iterable} promises an iterable of promises to wait for.
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'race',
+ value: function (promises) {
+ var completed = false;
+ var promise = new ParsePromise();
+ var _iteratorNormalCompletion2 = true;
+ var _didIteratorError2 = false;
+ var _iteratorError2 = undefined;
+
+ try {
+ for (var _iterator2 = (0, _getIterator3.default)(promises), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+ var p = _step2.value;
+
+ if (ParsePromise.is(p)) {
+ p.then(function (result) {
+ if (completed) {
+ return;
+ }
+ completed = true;
+ promise.resolve(result);
+ }, function (error) {
+ if (completed) {
+ return;
+ }
+ completed = true;
+ promise.reject(error);
+ });
+ } else if (!completed) {
+ completed = true;
+ promise.resolve(p);
+ }
+ }
+ } catch (err) {
+ _didIteratorError2 = true;
+ _iteratorError2 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion2 && _iterator2.return) {
+ _iterator2.return();
+ }
+ } finally {
+ if (_didIteratorError2) {
+ throw _iteratorError2;
+ }
+ }
+ }
+
+ return promise;
+ }
+
+ /**
+ * Runs the given asyncFunction repeatedly, as long as the predicate
+ * function returns a truthy value. Stops repeating if asyncFunction returns
+ * a rejected promise.
+ * @method _continueWhile
+ * @param {Function} predicate should return false when ready to stop.
+ * @param {Function} asyncFunction should return a Promise.
+ * @static
+ */
+
+ }, {
+ key: '_continueWhile',
+ value: function (predicate, asyncFunction) {
+ if (predicate()) {
+ return asyncFunction().then(function () {
+ return ParsePromise._continueWhile(predicate, asyncFunction);
+ });
+ }
+ return ParsePromise.as();
+ }
+ }, {
+ key: 'isPromisesAPlusCompliant',
+ value: function () {
+ return _isPromisesAPlusCompliant;
+ }
+ }, {
+ key: 'enableAPlusCompliant',
+ value: function () {
+ _isPromisesAPlusCompliant = true;
+ }
+ }, {
+ key: 'disableAPlusCompliant',
+ value: function () {
+ _isPromisesAPlusCompliant = false;
+ }
+ }]);
+ return ParsePromise;
+}();
+
+exports.default = ParsePromise;
+}).call(this,_dereq_('_process'))
+},{"_process":168,"babel-runtime/core-js/get-iterator":43,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57,"babel-runtime/helpers/typeof":61}],21:[function(_dereq_,module,exports){
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _typeof2 = _dereq_('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = _dereq_('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _keys = _dereq_('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
+var _CoreManager = _dereq_('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _encode = _dereq_('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _ParseError = _dereq_('./ParseError');
+
+var _ParseError2 = _interopRequireDefault(_ParseError);
+
+var _ParseGeoPoint = _dereq_('./ParseGeoPoint');
+
+var _ParseGeoPoint2 = _interopRequireDefault(_ParseGeoPoint);
+
+var _ParseObject = _dereq_('./ParseObject');
+
+var _ParseObject2 = _interopRequireDefault(_ParseObject);
+
+var _ParsePromise = _dereq_('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Converts a string into a regex that matches it.
+ * Surrounding with \Q .. \E does this, we just need to escape any \E's in
+ * the text separately.
+ */
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function quote(s) {
+ return '\\Q' + s.replace('\\E', '\\E\\\\E\\Q') + '\\E';
+}
+
+/**
+ * Handles pre-populating the result data of a query with select fields,
+ * making sure that the data object contains keys for all objects that have
+ * been requested with a select, so that our cached state updates correctly.
+ */
+function handleSelectResult(data, select) {
+ var serverDataMask = {};
+
+ select.forEach(function (field) {
+ var hasSubObjectSelect = field.indexOf(".") !== -1;
+ if (!hasSubObjectSelect && !data.hasOwnProperty(field)) {
+ // this field was selected, but is missing from the retrieved data
+ data[field] = undefined;
+ } else if (hasSubObjectSelect) {
+ // this field references a sub-object,
+ // so we need to walk down the path components
+ var pathComponents = field.split(".");
+ var obj = data;
+ var serverMask = serverDataMask;
+
+ pathComponents.forEach(function (component, index, arr) {
+ // add keys if the expected data is missing
+ if (!obj[component]) {
+ obj[component] = index == arr.length - 1 ? undefined : {};
+ }
+ obj = obj[component];
+
+ //add this path component to the server mask so we can fill it in later if needed
+ if (index < arr.length - 1) {
+ if (!serverMask[component]) {
+ serverMask[component] = {};
+ }
+ }
+ });
+ }
+ });
+
+ if ((0, _keys2.default)(serverDataMask).length > 0) {
+ var copyMissingDataWithMask = function copyMissingDataWithMask(src, dest, mask, copyThisLevel) {
+ //copy missing elements at this level
+ if (copyThisLevel) {
+ for (var key in src) {
+ if (src.hasOwnProperty(key) && !dest.hasOwnProperty(key)) {
+ dest[key] = src[key];
+ }
+ }
+ }
+ for (var key in mask) {
+ //traverse into objects as needed
+ copyMissingDataWithMask(src[key], dest[key], mask[key], true);
+ }
+ };
+
+ // When selecting from sub-objects, we don't want to blow away the missing
+ // information that we may have retrieved before. We've already added any
+ // missing selected keys to sub-objects, but we still need to add in the
+ // data for any previously retrieved sub-objects that were not selected.
+
+ var serverData = _CoreManager2.default.getObjectStateController().getServerData({ id: data.objectId, className: data.className });
+
+ copyMissingDataWithMask(serverData, data, serverDataMask, false);
+ }
+}
+
+/**
+ * Creates a new parse Parse.Query for the given Parse.Object subclass.
+ * @class Parse.Query
+ * @constructor
+ * @param {} objectClass An instance of a subclass of Parse.Object, or a Parse className string.
+ *
+ * Parse.Query defines a query that is used to fetch Parse.Objects. The
+ * most common use case is finding all objects that match a query through the
+ * find method. For example, this sample code fetches all objects
+ * of class MyClass. It calls a different function depending on
+ * whether the fetch succeeded or not.
+ *
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.find({
+ * success: function(results) {
+ * // results is an array of Parse.Object.
+ * },
+ *
+ * error: function(error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ *
+ * A Parse.Query can also be used to retrieve a single object whose id is
+ * known, through the get method. For example, this sample code fetches an
+ * object of class MyClass and id myId. It calls a
+ * different function depending on whether the fetch succeeded or not.
+ *
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.get(myId, {
+ * success: function(object) {
+ * // object is an instance of Parse.Object.
+ * },
+ *
+ * error: function(object, error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ *
+ * A Parse.Query can also be used to count the number of objects that match
+ * the query without retrieving all of those objects. For example, this
+ * sample code counts the number of objects of the class MyClass
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.count({
+ * success: function(number) {
+ * // There are number instances of MyClass.
+ * },
+ *
+ * error: function(error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ */
+
+var ParseQuery = function () {
+ function ParseQuery(objectClass) {
+ (0, _classCallCheck3.default)(this, ParseQuery);
+
+ if (typeof objectClass === 'string') {
+ if (objectClass === 'User' && _CoreManager2.default.get('PERFORM_USER_REWRITE')) {
+ this.className = '_User';
+ } else {
+ this.className = objectClass;
+ }
+ } else if (objectClass instanceof _ParseObject2.default) {
+ this.className = objectClass.className;
+ } else if (typeof objectClass === 'function') {
+ if (typeof objectClass.className === 'string') {
+ this.className = objectClass.className;
+ } else {
+ var obj = new objectClass();
+ this.className = obj.className;
+ }
+ } else {
+ throw new TypeError('A ParseQuery must be constructed with a ParseObject or class name.');
+ }
+
+ this._where = {};
+ this._include = [];
+ this._limit = -1; // negative limit is not sent in the server request
+ this._skip = 0;
+ this._extraOptions = {};
+ }
+
+ /**
+ * Adds constraint that at least one of the passed in queries matches.
+ * @method _orQuery
+ * @param {Array} queries
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+
+ (0, _createClass3.default)(ParseQuery, [{
+ key: '_orQuery',
+ value: function (queries) {
+ var queryJSON = queries.map(function (q) {
+ return q.toJSON().where;
+ });
+
+ this._where.$or = queryJSON;
+ return this;
+ }
+
+ /**
+ * Helper for condition queries
+ */
+
+ }, {
+ key: '_addCondition',
+ value: function (key, condition, value) {
+ if (!this._where[key] || typeof this._where[key] === 'string') {
+ this._where[key] = {};
+ }
+ this._where[key][condition] = (0, _encode2.default)(value, false, true);
+ return this;
+ }
+
+ /**
+ * Converts string for regular expression at the beginning
+ */
+
+ }, {
+ key: '_regexStartWith',
+ value: function (string) {
+ return '^' + quote(string);
+ }
+
+ /**
+ * Returns a JSON representation of this query.
+ * @method toJSON
+ * @return {Object} The JSON representation of the query.
+ */
+
+ }, {
+ key: 'toJSON',
+ value: function () {
+ var params = {
+ where: this._where
+ };
+
+ if (this._include.length) {
+ params.include = this._include.join(',');
+ }
+ if (this._select) {
+ params.keys = this._select.join(',');
+ }
+ if (this._limit >= 0) {
+ params.limit = this._limit;
+ }
+ if (this._skip > 0) {
+ params.skip = this._skip;
+ }
+ if (this._order) {
+ params.order = this._order.join(',');
+ }
+ for (var key in this._extraOptions) {
+ params[key] = this._extraOptions[key];
+ }
+
+ return params;
+ }
+
+ /**
+ * Constructs a Parse.Object whose id is already known by fetching data from
+ * the server. Either options.success or options.error is called when the
+ * find completes.
+ *
+ * @method get
+ * @param {String} objectId The id of the object to be fetched.
+ * @param {Object} options A Backbone-style options object.
+ * Valid options are:var compoundQuery = Parse.Query.or(query1, query2, query3);+ * + * will create a compoundQuery that is an or of the query1, query2, and + * query3. + * @method or + * @param {...Parse.Query} var_args The list of queries to OR. + * @static + * @return {Parse.Query} The query that is the OR of the passed in queries. + */ + + }], [{ + key: 'or', + value: function () { + var className = null; + + for (var _len7 = arguments.length, queries = Array(_len7), _key7 = 0; _key7 < _len7; _key7++) { + queries[_key7] = arguments[_key7]; + } + + queries.forEach(function (q) { + if (!className) { + className = q.className; + } + + if (className !== q.className) { + throw new Error('All queries must be for the same class.'); + } + }); + + var query = new ParseQuery(className); + query._orQuery(queries); + return query; + } + }]); + return ParseQuery; +}(); + +exports.default = ParseQuery; + +var DefaultController = { + find: function (className, params, options) { + var RESTController = _CoreManager2.default.getRESTController(); + + return RESTController.request('GET', 'classes/' + className, params, options); + } +}; + +_CoreManager2.default.setQueryController(DefaultController); +},{"./CoreManager":3,"./ParseError":13,"./ParseGeoPoint":15,"./ParseObject":18,"./ParsePromise":20,"./encode":36,"babel-runtime/core-js/object/keys":51,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57,"babel-runtime/helpers/typeof":61}],22:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = _dereq_('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _ParseOp = _dereq_('./ParseOp'); + +var _ParseObject = _dereq_('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseQuery = _dereq_('./ParseQuery'); + +var _ParseQuery2 = _interopRequireDefault(_ParseQuery); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Creates a new Relation for the given parent object and key. This + * constructor should rarely be used directly, but rather created by + * Parse.Object.relation. + * @class Parse.Relation + * @constructor + * @param {Parse.Object} parent The parent of this relation. + * @param {String} key The key for this relation on the parent. + * + *
+ * A class that is used to access all of the children of a many-to-many + * relationship. Each instance of Parse.Relation is associated with a + * particular parent object and key. + *
+ */ +var ParseRelation = function () { + function ParseRelation(parent, key) { + (0, _classCallCheck3.default)(this, ParseRelation); + + this.parent = parent; + this.key = key; + this.targetClassName = null; + } + + /** + * Makes sure that this relation has the right parent and key. + */ + + (0, _createClass3.default)(ParseRelation, [{ + key: '_ensureParentAndKey', + value: function (parent, key) { + this.key = this.key || key; + if (this.key !== key) { + throw new Error('Internal Error. Relation retrieved from two different keys.'); + } + if (this.parent) { + if (this.parent.className !== parent.className) { + throw new Error('Internal Error. Relation retrieved from two different Objects.'); + } + if (this.parent.id) { + if (this.parent.id !== parent.id) { + throw new Error('Internal Error. Relation retrieved from two different Objects.'); + } + } else if (parent.id) { + this.parent = parent; + } + } else { + this.parent = parent; + } + } + + /** + * Adds a Parse.Object or an array of Parse.Objects to the relation. + * @method add + * @param {} objects The item or items to add. + */ + + }, { + key: 'add', + value: function (objects) { + if (!Array.isArray(objects)) { + objects = [objects]; + } + + var change = new _ParseOp.RelationOp(objects, []); + var parent = this.parent; + if (!parent) { + throw new Error('Cannot add to a Relation without a parent'); + } + parent.set(this.key, change); + this.targetClassName = change._targetClassName; + return parent; + } + + /** + * Removes a Parse.Object or an array of Parse.Objects from this relation. + * @method remove + * @param {} objects The item or items to remove. + */ + + }, { + key: 'remove', + value: function (objects) { + if (!Array.isArray(objects)) { + objects = [objects]; + } + + var change = new _ParseOp.RelationOp([], objects); + if (!this.parent) { + throw new Error('Cannot remove from a Relation without a parent'); + } + this.parent.set(this.key, change); + this.targetClassName = change._targetClassName; + } + + /** + * Returns a JSON version of the object suitable for saving to disk. + * @method toJSON + * @return {Object} + */ + + }, { + key: 'toJSON', + value: function () { + return { + __type: 'Relation', + className: this.targetClassName + }; + } + + /** + * Returns a Parse.Query that is limited to objects in this + * relation. + * @method query + * @return {Parse.Query} + */ + + }, { + key: 'query', + value: function () { + var query; + var parent = this.parent; + if (!parent) { + throw new Error('Cannot construct a query for a Relation without a parent'); + } + if (!this.targetClassName) { + query = new _ParseQuery2.default(parent.className); + query._extraOptions.redirectClassNameForKey = this.key; + } else { + query = new _ParseQuery2.default(this.targetClassName); + } + query._addCondition('$relatedTo', 'object', { + __type: 'Pointer', + className: parent.className, + objectId: parent.id + }); + query._addCondition('$relatedTo', 'key', this.key); + + return query; + } + }]); + return ParseRelation; +}(); /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +exports.default = ParseRelation; +},{"./ParseObject":18,"./ParseOp":19,"./ParseQuery":21,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57}],23:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _getPrototypeOf = _dereq_('babel-runtime/core-js/object/get-prototype-of'); + +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = _dereq_('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _possibleConstructorReturn2 = _dereq_('babel-runtime/helpers/possibleConstructorReturn'); + +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +var _get2 = _dereq_('babel-runtime/helpers/get'); + +var _get3 = _interopRequireDefault(_get2); + +var _inherits2 = _dereq_('babel-runtime/helpers/inherits'); + +var _inherits3 = _interopRequireDefault(_inherits2); + +var _ParseACL = _dereq_('./ParseACL'); + +var _ParseACL2 = _interopRequireDefault(_ParseACL); + +var _ParseError = _dereq_('./ParseError'); + +var _ParseError2 = _interopRequireDefault(_ParseError); + +var _ParseObject2 = _dereq_('./ParseObject'); + +var _ParseObject3 = _interopRequireDefault(_ParseObject2); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Represents a Role on the Parse server. Roles represent groupings of + * Users for the purposes of granting permissions (e.g. specifying an ACL + * for an Object). Roles are specified by their sets of child users and + * child roles, all of which are granted any permissions that the parent + * role has. + * + *Roles must have a name (which cannot be changed after creation of the + * role), and must specify an ACL.
+ * @class Parse.Role + * @constructor + * @param {String} name The name of the Role to create. + * @param {Parse.ACL} acl The ACL for this role. Roles must have an ACL. + * A Parse.Role is a local representation of a role persisted to the Parse + * cloud. + */ +var ParseRole = function (_ParseObject) { + (0, _inherits3.default)(ParseRole, _ParseObject); + + function ParseRole(name, acl) { + (0, _classCallCheck3.default)(this, ParseRole); + + var _this = (0, _possibleConstructorReturn3.default)(this, (ParseRole.__proto__ || (0, _getPrototypeOf2.default)(ParseRole)).call(this, '_Role')); + + if (typeof name === 'string' && acl instanceof _ParseACL2.default) { + _this.setName(name); + _this.setACL(acl); + } + return _this; + } + + /** + * Gets the name of the role. You can alternatively call role.get("name") + * + * @method getName + * @return {String} the name of the role. + */ + + (0, _createClass3.default)(ParseRole, [{ + key: 'getName', + value: function () { + var name = this.get('name'); + if (name == null || typeof name === 'string') { + return name; + } + return ''; + } + + /** + * Sets the name for a role. This value must be set before the role has + * been saved to the server, and cannot be set once the role has been + * saved. + * + *+ * A role's name can only contain alphanumeric characters, _, -, and + * spaces. + *
+ * + *This is equivalent to calling role.set("name", name)
+ * + * @method setName + * @param {String} name The name of the role. + * @param {Object} options Standard options object with success and error + * callbacks. + */ + + }, { + key: 'setName', + value: function (name, options) { + return this.set('name', name, options); + } + + /** + * Gets the Parse.Relation for the Parse.Users that are direct + * children of this role. These users are granted any privileges that this + * role has been granted (e.g. read or write access through ACLs). You can + * add or remove users from the role through this relation. + * + *This is equivalent to calling role.relation("users")
+ * + * @method getUsers + * @return {Parse.Relation} the relation for the users belonging to this + * role. + */ + + }, { + key: 'getUsers', + value: function () { + return this.relation('users'); + } + + /** + * Gets the Parse.Relation for the Parse.Roles that are direct + * children of this role. These roles' users are granted any privileges that + * this role has been granted (e.g. read or write access through ACLs). You + * can add or remove child roles from this role through this relation. + * + *This is equivalent to calling role.relation("roles")
+ * + * @method getRoles + * @return {Parse.Relation} the relation for the roles belonging to this + * role. + */ + + }, { + key: 'getRoles', + value: function () { + return this.relation('roles'); + } + }, { + key: 'validate', + value: function (attrs, options) { + var isInvalid = (0, _get3.default)(ParseRole.prototype.__proto__ || (0, _getPrototypeOf2.default)(ParseRole.prototype), 'validate', this).call(this, attrs, options); + if (isInvalid) { + return isInvalid; + } + + if ('name' in attrs && attrs.name !== this.getName()) { + var newName = attrs.name; + if (this.id && this.id !== attrs.objectId) { + // Check to see if the objectId being set matches this.id + // This happens during a fetch -- the id is set before calling fetch + // Let the name be set in this case + return new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'A role\'s name can only be set before it has been saved.'); + } + if (typeof newName !== 'string') { + return new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'A role\'s name must be a String.'); + } + if (!/^[0-9a-zA-Z\-_ ]+$/.test(newName)) { + return new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'A role\'s name can be only contain alphanumeric characters, _, ' + '-, and spaces.'); + } + } + return false; + } + }]); + return ParseRole; +}(_ParseObject3.default); /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +exports.default = ParseRole; + +_ParseObject3.default.registerSubclass('_Role', ParseRole); +},{"./ParseACL":11,"./ParseError":13,"./ParseObject":18,"babel-runtime/core-js/object/get-prototype-of":50,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57,"babel-runtime/helpers/get":58,"babel-runtime/helpers/inherits":59,"babel-runtime/helpers/possibleConstructorReturn":60}],24:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = _dereq_('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +var _getPrototypeOf = _dereq_('babel-runtime/core-js/object/get-prototype-of'); + +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = _dereq_('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _possibleConstructorReturn2 = _dereq_('babel-runtime/helpers/possibleConstructorReturn'); + +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +var _inherits2 = _dereq_('babel-runtime/helpers/inherits'); + +var _inherits3 = _interopRequireDefault(_inherits2); + +var _CoreManager = _dereq_('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +var _isRevocableSession = _dereq_('./isRevocableSession'); + +var _isRevocableSession2 = _interopRequireDefault(_isRevocableSession); + +var _ParseObject2 = _dereq_('./ParseObject'); + +var _ParseObject3 = _interopRequireDefault(_ParseObject2); + +var _ParsePromise = _dereq_('./ParsePromise'); + +var _ParsePromise2 = _interopRequireDefault(_ParsePromise); + +var _ParseUser = _dereq_('./ParseUser'); + +var _ParseUser2 = _interopRequireDefault(_ParseUser); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * @class Parse.Session + * @constructor + * + *A Parse.Session object is a local representation of a revocable session. + * This class is a subclass of a Parse.Object, and retains the same + * functionality of a Parse.Object.
+ */ +var ParseSession = function (_ParseObject) { + (0, _inherits3.default)(ParseSession, _ParseObject); + + function ParseSession(attributes) { + (0, _classCallCheck3.default)(this, ParseSession); + + var _this = (0, _possibleConstructorReturn3.default)(this, (ParseSession.__proto__ || (0, _getPrototypeOf2.default)(ParseSession)).call(this, '_Session')); + + if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') { + if (!_this.set(attributes || {})) { + throw new Error('Can\'t create an invalid Session'); + } + } + return _this; + } + + /** + * Returns the session token string. + * @method getSessionToken + * @return {String} + */ + + (0, _createClass3.default)(ParseSession, [{ + key: 'getSessionToken', + value: function () { + var token = this.get('sessionToken'); + if (typeof token === 'string') { + return token; + } + return ''; + } + }], [{ + key: 'readOnlyAttributes', + value: function () { + return ['createdWith', 'expiresAt', 'installationId', 'restricted', 'sessionToken', 'user']; + } + + /** + * Retrieves the Session object for the currently logged in session. + * @method current + * @static + * @return {Parse.Promise} A promise that is resolved with the Parse.Session + * object after it has been fetched. If there is no current user, the + * promise will be rejected. + */ + + }, { + key: 'current', + value: function (options) { + options = options || {}; + var controller = _CoreManager2.default.getSessionController(); + + var sessionOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + sessionOptions.useMasterKey = options.useMasterKey; + } + return _ParseUser2.default.currentAsync().then(function (user) { + if (!user) { + return _ParsePromise2.default.error('There is no current user.'); + } + user.getSessionToken(); + + sessionOptions.sessionToken = user.getSessionToken(); + return controller.getSession(sessionOptions); + }); + } + + /** + * Determines whether the current session token is revocable. + * This method is useful for migrating Express.js or Node.js web apps to + * use revocable sessions. If you are migrating an app that uses the Parse + * SDK in the browser only, please use Parse.User.enableRevocableSession() + * instead, so that sessions can be automatically upgraded. + * @method isCurrentSessionRevocable + * @static + * @return {Boolean} + */ + + }, { + key: 'isCurrentSessionRevocable', + value: function () { + var currentUser = _ParseUser2.default.current(); + if (currentUser) { + return (0, _isRevocableSession2.default)(currentUser.getSessionToken() || ''); + } + return false; + } + }]); + return ParseSession; +}(_ParseObject3.default); /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +exports.default = ParseSession; + +_ParseObject3.default.registerSubclass('_Session', ParseSession); + +var DefaultController = { + getSession: function (options) { + var RESTController = _CoreManager2.default.getRESTController(); + var session = new ParseSession(); + + return RESTController.request('GET', 'sessions/me', {}, options).then(function (sessionData) { + session._finishFetch(sessionData); + session._setExisted(true); + return session; + }); + } +}; + +_CoreManager2.default.setSessionController(DefaultController); +},{"./CoreManager":3,"./ParseObject":18,"./ParsePromise":20,"./ParseUser":25,"./isRevocableSession":39,"babel-runtime/core-js/object/get-prototype-of":50,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57,"babel-runtime/helpers/inherits":59,"babel-runtime/helpers/possibleConstructorReturn":60,"babel-runtime/helpers/typeof":61}],25:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _stringify = _dereq_('babel-runtime/core-js/json/stringify'); + +var _stringify2 = _interopRequireDefault(_stringify); + +var _defineProperty = _dereq_('babel-runtime/core-js/object/define-property'); + +var _defineProperty2 = _interopRequireDefault(_defineProperty); + +var _typeof2 = _dereq_('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +var _getPrototypeOf = _dereq_('babel-runtime/core-js/object/get-prototype-of'); + +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +var _classCallCheck2 = _dereq_('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = _dereq_('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _possibleConstructorReturn2 = _dereq_('babel-runtime/helpers/possibleConstructorReturn'); + +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +var _get2 = _dereq_('babel-runtime/helpers/get'); + +var _get3 = _interopRequireDefault(_get2); + +var _inherits2 = _dereq_('babel-runtime/helpers/inherits'); + +var _inherits3 = _interopRequireDefault(_inherits2); + +var _CoreManager = _dereq_('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +var _isRevocableSession = _dereq_('./isRevocableSession'); + +var _isRevocableSession2 = _interopRequireDefault(_isRevocableSession); + +var _ParseError = _dereq_('./ParseError'); + +var _ParseError2 = _interopRequireDefault(_ParseError); + +var _ParseObject2 = _dereq_('./ParseObject'); + +var _ParseObject3 = _interopRequireDefault(_ParseObject2); + +var _ParsePromise = _dereq_('./ParsePromise'); + +var _ParsePromise2 = _interopRequireDefault(_ParsePromise); + +var _ParseSession = _dereq_('./ParseSession'); + +var _ParseSession2 = _interopRequireDefault(_ParseSession); + +var _Storage = _dereq_('./Storage'); + +var _Storage2 = _interopRequireDefault(_Storage); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +var CURRENT_USER_KEY = 'currentUser'; /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +var canUseCurrentUser = !_CoreManager2.default.get('IS_NODE'); +var currentUserCacheMatchesDisk = false; +var currentUserCache = null; + +var authProviders = {}; + +/** + * @class Parse.User + * @constructor + * + *A Parse.User object is a local representation of a user persisted to the + * Parse cloud. This class is a subclass of a Parse.Object, and retains the + * same functionality of a Parse.Object, but also extends it with various + * user specific methods, like authentication, signing up, and validation of + * uniqueness.
+ */ + +var ParseUser = function (_ParseObject) { + (0, _inherits3.default)(ParseUser, _ParseObject); + + function ParseUser(attributes) { + (0, _classCallCheck3.default)(this, ParseUser); + + var _this = (0, _possibleConstructorReturn3.default)(this, (ParseUser.__proto__ || (0, _getPrototypeOf2.default)(ParseUser)).call(this, '_User')); + + if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') { + if (!_this.set(attributes || {})) { + throw new Error('Can\'t create an invalid Parse User'); + } + } + return _this; + } + + /** + * Request a revocable session token to replace the older style of token. + * @method _upgradeToRevocableSession + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is resolved when the replacement + * token has been fetched. + */ + + (0, _createClass3.default)(ParseUser, [{ + key: '_upgradeToRevocableSession', + value: function (options) { + options = options || {}; + + var upgradeOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + upgradeOptions.useMasterKey = options.useMasterKey; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.upgradeToRevocableSession(this, upgradeOptions)._thenRunCallbacks(options); + } + + /** + * Unlike in the Android/iOS SDKs, logInWith is unnecessary, since you can + * call linkWith on the user (even if it doesn't exist yet on the server). + * @method _linkWith + */ + + }, { + key: '_linkWith', + value: function (provider, options) { + var _this2 = this; + + var authType; + if (typeof provider === 'string') { + authType = provider; + provider = authProviders[provider]; + } else { + authType = provider.getAuthType(); + } + if (options && options.hasOwnProperty('authData')) { + var authData = this.get('authData') || {}; + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + throw new Error('Invalid type: authData field should be an object'); + } + authData[authType] = options.authData; + + var controller = _CoreManager2.default.getUserController(); + return controller.linkWith(this, authData)._thenRunCallbacks(options, this); + } else { + var promise = new _ParsePromise2.default(); + provider.authenticate({ + success: function (provider, result) { + var opts = {}; + opts.authData = result; + if (options.success) { + opts.success = options.success; + } + if (options.error) { + opts.error = options.error; + } + _this2._linkWith(provider, opts).then(function () { + promise.resolve(_this2); + }, function (error) { + promise.reject(error); + }); + }, + error: function (provider, _error) { + if (typeof options.error === 'function') { + options.error(_this2, _error); + } + promise.reject(_error); + } + }); + return promise; + } + } + + /** + * Synchronizes auth data for a provider (e.g. puts the access token in the + * right place to be used by the Facebook SDK). + * @method _synchronizeAuthData + */ + + }, { + key: '_synchronizeAuthData', + value: function (provider) { + if (!this.isCurrent() || !provider) { + return; + } + var authType; + if (typeof provider === 'string') { + authType = provider; + provider = authProviders[authType]; + } else { + authType = provider.getAuthType(); + } + var authData = this.get('authData'); + if (!provider || !authData || (typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return; + } + var success = provider.restoreAuthentication(authData[authType]); + if (!success) { + this._unlinkFrom(provider); + } + } + + /** + * Synchronizes authData for all providers. + * @method _synchronizeAllAuthData + */ + + }, { + key: '_synchronizeAllAuthData', + value: function () { + var authData = this.get('authData'); + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return; + } + + for (var key in authData) { + this._synchronizeAuthData(key); + } + } + + /** + * Removes null values from authData (which exist temporarily for + * unlinking) + * @method _cleanupAuthData + */ + + }, { + key: '_cleanupAuthData', + value: function () { + if (!this.isCurrent()) { + return; + } + var authData = this.get('authData'); + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return; + } + + for (var key in authData) { + if (!authData[key]) { + delete authData[key]; + } + } + } + + /** + * Unlinks a user from a service. + * @method _unlinkFrom + */ + + }, { + key: '_unlinkFrom', + value: function (provider, options) { + var _this3 = this; + + if (typeof provider === 'string') { + provider = authProviders[provider]; + } else { + provider.getAuthType(); + } + return this._linkWith(provider, { authData: null }).then(function () { + _this3._synchronizeAuthData(provider); + return _ParsePromise2.default.as(_this3); + })._thenRunCallbacks(options); + } + + /** + * Checks whether a user is linked to a service. + * @method _isLinked + */ + + }, { + key: '_isLinked', + value: function (provider) { + var authType; + if (typeof provider === 'string') { + authType = provider; + } else { + authType = provider.getAuthType(); + } + var authData = this.get('authData') || {}; + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return false; + } + return !!authData[authType]; + } + + /** + * Deauthenticates all providers. + * @method _logOutWithAll + */ + + }, { + key: '_logOutWithAll', + value: function () { + var authData = this.get('authData'); + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return; + } + + for (var key in authData) { + this._logOutWith(key); + } + } + + /** + * Deauthenticates a single provider (e.g. removing access tokens from the + * Facebook SDK). + * @method _logOutWith + */ + + }, { + key: '_logOutWith', + value: function (provider) { + if (!this.isCurrent()) { + return; + } + if (typeof provider === 'string') { + provider = authProviders[provider]; + } + if (provider && provider.deauthenticate) { + provider.deauthenticate(); + } + } + + /** + * Class instance method used to maintain specific keys when a fetch occurs. + * Used to ensure that the session token is not lost. + */ + + }, { + key: '_preserveFieldsOnFetch', + value: function () { + return { + sessionToken: this.get('sessionToken') + }; + } + + /** + * Returns true ifcurrent would return this user.
+ * @method isCurrent
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'isCurrent',
+ value: function () {
+ var current = ParseUser.current();
+ return !!current && current.id === this.id;
+ }
+
+ /**
+ * Returns get("username").
+ * @method getUsername
+ * @return {String}
+ */
+
+ }, {
+ key: 'getUsername',
+ value: function () {
+ var username = this.get('username');
+ if (username == null || typeof username === 'string') {
+ return username;
+ }
+ return '';
+ }
+
+ /**
+ * Calls set("username", username, options) and returns the result.
+ * @method setUsername
+ * @param {String} username
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'setUsername',
+ value: function (username) {
+ // Strip anonymity, even we do not support anonymous user in js SDK, we may
+ // encounter anonymous user created by android/iOS in cloud code.
+ var authData = this.get('authData');
+ if (authData && (typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) === 'object' && authData.hasOwnProperty('anonymous')) {
+ // We need to set anonymous to null instead of deleting it in order to remove it from Parse.
+ authData.anonymous = null;
+ }
+ this.set('username', username);
+ }
+
+ /**
+ * Calls set("password", password, options) and returns the result.
+ * @method setPassword
+ * @param {String} password
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'setPassword',
+ value: function (password) {
+ this.set('password', password);
+ }
+
+ /**
+ * Returns get("email").
+ * @method getEmail
+ * @return {String}
+ */
+
+ }, {
+ key: 'getEmail',
+ value: function () {
+ var email = this.get('email');
+ if (email == null || typeof email === 'string') {
+ return email;
+ }
+ return '';
+ }
+
+ /**
+ * Calls set("email", email, options) and returns the result.
+ * @method setEmail
+ * @param {String} email
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'setEmail',
+ value: function (email) {
+ this.set('email', email);
+ }
+
+ /**
+ * Returns the session token for this user, if the user has been logged in,
+ * or if it is the result of a query with the master key. Otherwise, returns
+ * undefined.
+ * @method getSessionToken
+ * @return {String} the session token, or undefined
+ */
+
+ }, {
+ key: 'getSessionToken',
+ value: function () {
+ var token = this.get('sessionToken');
+ if (token == null || typeof token === 'string') {
+ return token;
+ }
+ return '';
+ }
+
+ /**
+ * Checks whether this user is the current user and has been authenticated.
+ * @method authenticated
+ * @return (Boolean) whether this user is the current user and is logged in.
+ */
+
+ }, {
+ key: 'authenticated',
+ value: function () {
+ var current = ParseUser.current();
+ return !!this.get('sessionToken') && !!current && current.id === this.id;
+ }
+
+ /**
+ * Signs up a new user. You should call this instead of save for
+ * new Parse.Users. This will create a new Parse.User on the server, and
+ * also persist the session on disk so that you can access the user using
+ * current.
+ *
+ * A username and password must be set before calling signUp.
+ * + *Calls options.success or options.error on completion.
+ * + * @method signUp + * @param {Object} attrs Extra fields to set on the new user, or null. + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is fulfilled when the signup + * finishes. + */ + + }, { + key: 'signUp', + value: function (attrs, options) { + options = options || {}; + + var signupOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + signupOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('installationId')) { + signupOptions.installationId = options.installationId; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.signUp(this, attrs, signupOptions)._thenRunCallbacks(options, this); + } + + /** + * Logs in a Parse.User. On success, this saves the session to disk, + * so you can retrieve the currently logged in user using + *current.
+ *
+ * A username and password must be set before calling logIn.
+ * + *Calls options.success or options.error on completion.
+ * + * @method logIn + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login is complete. + */ + + }, { + key: 'logIn', + value: function (options) { + options = options || {}; + + var loginOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + loginOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('installationId')) { + loginOptions.installationId = options.installationId; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.logIn(this, loginOptions)._thenRunCallbacks(options, this); + } + + /** + * Wrap the default save behavior with functionality to save to local + * storage if this is current user. + */ + + }, { + key: 'save', + value: function () { + var _this4 = this; + + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return (0, _get3.default)(ParseUser.prototype.__proto__ || (0, _getPrototypeOf2.default)(ParseUser.prototype), 'save', this).apply(this, args).then(function () { + if (_this4.isCurrent()) { + return _CoreManager2.default.getUserController().updateUserOnDisk(_this4); + } + return _this4; + }); + } + + /** + * Wrap the default destroy behavior with functionality that logs out + * the current user when it is destroyed + */ + + }, { + key: 'destroy', + value: function () { + var _this5 = this; + + for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + return (0, _get3.default)(ParseUser.prototype.__proto__ || (0, _getPrototypeOf2.default)(ParseUser.prototype), 'destroy', this).apply(this, args).then(function () { + if (_this5.isCurrent()) { + return _CoreManager2.default.getUserController().removeUserFromDisk(); + } + return _this5; + }); + } + + /** + * Wrap the default fetch behavior with functionality to save to local + * storage if this is current user. + */ + + }, { + key: 'fetch', + value: function () { + var _this6 = this; + + for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + args[_key3] = arguments[_key3]; + } + + return (0, _get3.default)(ParseUser.prototype.__proto__ || (0, _getPrototypeOf2.default)(ParseUser.prototype), 'fetch', this).apply(this, args).then(function () { + if (_this6.isCurrent()) { + return _CoreManager2.default.getUserController().updateUserOnDisk(_this6); + } + return _this6; + }); + } + }], [{ + key: 'readOnlyAttributes', + value: function () { + return ['sessionToken']; + } + + /** + * Adds functionality to the existing Parse.User class + * @method extend + * @param {Object} protoProps A set of properties to add to the prototype + * @param {Object} classProps A set of static properties to add to the class + * @static + * @return {Class} The newly extended Parse.User class + */ + + }, { + key: 'extend', + value: function (protoProps, classProps) { + if (protoProps) { + for (var prop in protoProps) { + if (prop !== 'className') { + (0, _defineProperty2.default)(ParseUser.prototype, prop, { + value: protoProps[prop], + enumerable: false, + writable: true, + configurable: true + }); + } + } + } + + if (classProps) { + for (var prop in classProps) { + if (prop !== 'className') { + (0, _defineProperty2.default)(ParseUser, prop, { + value: classProps[prop], + enumerable: false, + writable: true, + configurable: true + }); + } + } + } + + return ParseUser; + } + + /** + * Retrieves the currently logged in ParseUser with a valid session, + * either from memory or localStorage, if necessary. + * @method current + * @static + * @return {Parse.Object} The currently logged in Parse.User. + */ + + }, { + key: 'current', + value: function () { + if (!canUseCurrentUser) { + return null; + } + var controller = _CoreManager2.default.getUserController(); + return controller.currentUser(); + } + + /** + * Retrieves the currently logged in ParseUser from asynchronous Storage. + * @method currentAsync + * @static + * @return {Parse.Promise} A Promise that is resolved with the currently + * logged in Parse User + */ + + }, { + key: 'currentAsync', + value: function () { + if (!canUseCurrentUser) { + return _ParsePromise2.default.as(null); + } + var controller = _CoreManager2.default.getUserController(); + return controller.currentUserAsync(); + } + + /** + * Signs up a new user with a username (or email) and password. + * This will create a new Parse.User on the server, and also persist the + * session in localStorage so that you can access the user using + * {@link #current}. + * + *Calls options.success or options.error on completion.
+ * + * @method signUp + * @param {String} username The username (or email) to sign up with. + * @param {String} password The password to sign up with. + * @param {Object} attrs Extra fields to set on the new user. + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the signup completes. + */ + + }, { + key: 'signUp', + value: function (username, password, attrs, options) { + attrs = attrs || {}; + attrs.username = username; + attrs.password = password; + var user = new ParseUser(attrs); + return user.signUp({}, options); + } + + /** + * Logs in a user with a username (or email) and password. On success, this + * saves the session to disk, so you can retrieve the currently logged in + * user usingcurrent.
+ *
+ * Calls options.success or options.error on completion.
+ * + * @method logIn + * @param {String} username The username (or email) to log in with. + * @param {String} password The password to log in with. + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login completes. + */ + + }, { + key: 'logIn', + value: function (username, password, options) { + if (typeof username !== 'string') { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Username must be a string.')); + } else if (typeof password !== 'string') { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Password must be a string.')); + } + var user = new ParseUser(); + user._finishFetch({ username: username, password: password }); + return user.logIn(options); + } + + /** + * Logs in a user with a session token. On success, this saves the session + * to disk, so you can retrieve the currently logged in user using + *current.
+ *
+ * Calls options.success or options.error on completion.
+ * + * @method become + * @param {String} sessionToken The sessionToken to log in with. + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login completes. + */ + + }, { + key: 'become', + value: function (sessionToken, options) { + if (!canUseCurrentUser) { + throw new Error('It is not memory-safe to become a user in a server environment'); + } + options = options || {}; + + var becomeOptions = { + sessionToken: sessionToken + }; + if (options.hasOwnProperty('useMasterKey')) { + becomeOptions.useMasterKey = options.useMasterKey; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.become(becomeOptions)._thenRunCallbacks(options); + } + }, { + key: 'logInWith', + value: function (provider, options) { + return ParseUser._logInWith(provider, options); + } + + /** + * Logs out the currently logged in user session. This will remove the + * session from disk, log out of linked services, and future calls to + *current will return null.
+ * @method logOut
+ * @static
+ * @return {Parse.Promise} A promise that is resolved when the session is
+ * destroyed on the server.
+ */
+
+ }, {
+ key: 'logOut',
+ value: function () {
+ if (!canUseCurrentUser) {
+ throw new Error('There is no current user user on a node.js server environment.');
+ }
+
+ var controller = _CoreManager2.default.getUserController();
+ return controller.logOut();
+ }
+
+ /**
+ * Requests a password reset email to be sent to the specified email address
+ * associated with the user account. This email allows the user to securely
+ * reset their password on the Parse site.
+ *
+ * Calls options.success or options.error on completion.
+ * + * @method requestPasswordReset + * @param {String} email The email address associated with the user that + * forgot their password. + * @param {Object} options A Backbone-style options object. + * @static + */ + + }, { + key: 'requestPasswordReset', + value: function (email, options) { + options = options || {}; + + var requestOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + requestOptions.useMasterKey = options.useMasterKey; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.requestPasswordReset(email, requestOptions)._thenRunCallbacks(options); + } + + /** + * Allow someone to define a custom User class without className + * being rewritten to _User. The default behavior is to rewrite + * User to _User for legacy reasons. This allows developers to + * override that behavior. + * + * @method allowCustomUserClass + * @param {Boolean} isAllowed Whether or not to allow custom User class + * @static + */ + + }, { + key: 'allowCustomUserClass', + value: function (isAllowed) { + _CoreManager2.default.set('PERFORM_USER_REWRITE', !isAllowed); + } + + /** + * Allows a legacy application to start using revocable sessions. If the + * current session token is not revocable, a request will be made for a new, + * revocable session. + * It is not necessary to call this method from cloud code unless you are + * handling user signup or login from the server side. In a cloud code call, + * this function will not attempt to upgrade the current token. + * @method enableRevocableSession + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is resolved when the process has + * completed. If a replacement session token is requested, the promise + * will be resolved after a new token has been fetched. + */ + + }, { + key: 'enableRevocableSession', + value: function (options) { + options = options || {}; + _CoreManager2.default.set('FORCE_REVOCABLE_SESSION', true); + if (canUseCurrentUser) { + var current = ParseUser.current(); + if (current) { + return current._upgradeToRevocableSession(options); + } + } + return _ParsePromise2.default.as()._thenRunCallbacks(options); + } + + /** + * Enables the use of become or the current user in a server + * environment. These features are disabled by default, since they depend on + * global objects that are not memory-safe for most servers. + * @method enableUnsafeCurrentUser + * @static + */ + + }, { + key: 'enableUnsafeCurrentUser', + value: function () { + canUseCurrentUser = true; + } + + /** + * Disables the use of become or the current user in any environment. + * These features are disabled on servers by default, since they depend on + * global objects that are not memory-safe for most servers. + * @method disableUnsafeCurrentUser + * @static + */ + + }, { + key: 'disableUnsafeCurrentUser', + value: function () { + canUseCurrentUser = false; + } + }, { + key: '_registerAuthenticationProvider', + value: function (provider) { + authProviders[provider.getAuthType()] = provider; + // Synchronize the current user with the auth provider. + ParseUser.currentAsync().then(function (current) { + if (current) { + current._synchronizeAuthData(provider.getAuthType()); + } + }); + } + }, { + key: '_logInWith', + value: function (provider, options) { + var user = new ParseUser(); + return user._linkWith(provider, options); + } + }, { + key: '_clearCache', + value: function () { + currentUserCache = null; + currentUserCacheMatchesDisk = false; + } + }, { + key: '_setCurrentUserCache', + value: function (user) { + currentUserCache = user; + } + }]); + return ParseUser; +}(_ParseObject3.default); + +exports.default = ParseUser; + +_ParseObject3.default.registerSubclass('_User', ParseUser); + +var DefaultController = { + updateUserOnDisk: function (user) { + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + var json = user.toJSON(); + json.className = '_User'; + return _Storage2.default.setItemAsync(path, (0, _stringify2.default)(json)).then(function () { + return user; + }); + }, + removeUserFromDisk: function () { + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + currentUserCacheMatchesDisk = true; + currentUserCache = null; + return _Storage2.default.removeItemAsync(path); + }, + setCurrentUser: function (user) { + currentUserCache = user; + user._cleanupAuthData(); + user._synchronizeAllAuthData(); + return DefaultController.updateUserOnDisk(user); + }, + currentUser: function () { + if (currentUserCache) { + return currentUserCache; + } + if (currentUserCacheMatchesDisk) { + return null; + } + if (_Storage2.default.async()) { + throw new Error('Cannot call currentUser() when using a platform with an async ' + 'storage system. Call currentUserAsync() instead.'); + } + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + var userData = _Storage2.default.getItem(path); + currentUserCacheMatchesDisk = true; + if (!userData) { + currentUserCache = null; + return null; + } + userData = JSON.parse(userData); + if (!userData.className) { + userData.className = '_User'; + } + if (userData._id) { + if (userData.objectId !== userData._id) { + userData.objectId = userData._id; + } + delete userData._id; + } + if (userData._sessionToken) { + userData.sessionToken = userData._sessionToken; + delete userData._sessionToken; + } + var current = _ParseObject3.default.fromJSON(userData); + currentUserCache = current; + current._synchronizeAllAuthData(); + return current; + }, + currentUserAsync: function () { + if (currentUserCache) { + return _ParsePromise2.default.as(currentUserCache); + } + if (currentUserCacheMatchesDisk) { + return _ParsePromise2.default.as(null); + } + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + return _Storage2.default.getItemAsync(path).then(function (userData) { + currentUserCacheMatchesDisk = true; + if (!userData) { + currentUserCache = null; + return _ParsePromise2.default.as(null); + } + userData = JSON.parse(userData); + if (!userData.className) { + userData.className = '_User'; + } + if (userData._id) { + if (userData.objectId !== userData._id) { + userData.objectId = userData._id; + } + delete userData._id; + } + if (userData._sessionToken) { + userData.sessionToken = userData._sessionToken; + delete userData._sessionToken; + } + var current = _ParseObject3.default.fromJSON(userData); + currentUserCache = current; + current._synchronizeAllAuthData(); + return _ParsePromise2.default.as(current); + }); + }, + signUp: function (user, attrs, options) { + var username = attrs && attrs.username || user.get('username'); + var password = attrs && attrs.password || user.get('password'); + + if (!username || !username.length) { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Cannot sign up user with an empty name.')); + } + if (!password || !password.length) { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Cannot sign up user with an empty password.')); + } + + return user.save(attrs, options).then(function () { + // Clear the password field + user._finishFetch({ password: undefined }); + + if (canUseCurrentUser) { + return DefaultController.setCurrentUser(user); + } + return user; + }); + }, + logIn: function (user, options) { + var RESTController = _CoreManager2.default.getRESTController(); + var stateController = _CoreManager2.default.getObjectStateController(); + var auth = { + username: user.get('username'), + password: user.get('password') + }; + return RESTController.request('GET', 'login', auth, options).then(function (response, status) { + user._migrateId(response.objectId); + user._setExisted(true); + stateController.setPendingOp(user._getStateIdentifier(), 'username', undefined); + stateController.setPendingOp(user._getStateIdentifier(), 'password', undefined); + response.password = undefined; + user._finishFetch(response); + if (!canUseCurrentUser) { + // We can't set the current user, so just return the one we logged in + return _ParsePromise2.default.as(user); + } + return DefaultController.setCurrentUser(user); + }); + }, + become: function (options) { + var user = new ParseUser(); + var RESTController = _CoreManager2.default.getRESTController(); + return RESTController.request('GET', 'users/me', {}, options).then(function (response, status) { + user._finishFetch(response); + user._setExisted(true); + return DefaultController.setCurrentUser(user); + }); + }, + logOut: function () { + return DefaultController.currentUserAsync().then(function (currentUser) { + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + var promise = _Storage2.default.removeItemAsync(path); + var RESTController = _CoreManager2.default.getRESTController(); + if (currentUser !== null) { + var currentSession = currentUser.getSessionToken(); + if (currentSession && (0, _isRevocableSession2.default)(currentSession)) { + promise = promise.then(function () { + return RESTController.request('POST', 'logout', {}, { sessionToken: currentSession }); + }); + } + currentUser._logOutWithAll(); + currentUser._finishFetch({ sessionToken: undefined }); + } + currentUserCacheMatchesDisk = true; + currentUserCache = null; + + return promise; + }); + }, + requestPasswordReset: function (email, options) { + var RESTController = _CoreManager2.default.getRESTController(); + return RESTController.request('POST', 'requestPasswordReset', { email: email }, options); + }, + upgradeToRevocableSession: function (user, options) { + var token = user.getSessionToken(); + if (!token) { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.SESSION_MISSING, 'Cannot upgrade a user with no session token')); + } + + options.sessionToken = token; + + var RESTController = _CoreManager2.default.getRESTController(); + return RESTController.request('POST', 'upgradeToRevocableSession', {}, options).then(function (result) { + var session = new _ParseSession2.default(); + session._finishFetch(result); + user._finishFetch({ sessionToken: session.getSessionToken() }); + if (user.isCurrent()) { + return DefaultController.setCurrentUser(user); + } + return _ParsePromise2.default.as(user); + }); + }, + linkWith: function (user, authData) { + return user.save({ authData: authData }).then(function () { + if (canUseCurrentUser) { + return DefaultController.setCurrentUser(user); + } + return user; + }); + } +}; + +_CoreManager2.default.setUserController(DefaultController); +},{"./CoreManager":3,"./ParseError":13,"./ParseObject":18,"./ParsePromise":20,"./ParseSession":24,"./Storage":29,"./isRevocableSession":39,"babel-runtime/core-js/json/stringify":44,"babel-runtime/core-js/object/define-property":47,"babel-runtime/core-js/object/get-prototype-of":50,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57,"babel-runtime/helpers/get":58,"babel-runtime/helpers/inherits":59,"babel-runtime/helpers/possibleConstructorReturn":60,"babel-runtime/helpers/typeof":61}],26:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = _dereq_('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +exports.send = send; + +var _CoreManager = _dereq_('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +var _ParseQuery = _dereq_('./ParseQuery'); + +var _ParseQuery2 = _interopRequireDefault(_ParseQuery); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Contains functions to deal with Push in Parse. + * @class Parse.Push + * @static + */ + +/** + * Sends a push notification. + * @method send + * @param {Object} data - The data of the push notification. Valid fields + * are: + *
+ * var dimensions = {
+ * gender: 'm',
+ * source: 'web',
+ * dayType: 'weekend'
+ * };
+ * Parse.Analytics.track('signup', dimensions);
+ *
+ *
+ * There is a default limit of 8 dimensions per event tracked.
+ *
+ * @method track
+ * @param {String} name The name of the custom event to report to Parse as
+ * having happened.
+ * @param {Object} dimensions The dictionary of information by which to
+ * segment this event.
+ * @param {Object} options A Backbone-style callback object.
+ * @return {Parse.Promise} A promise that is resolved when the round-trip
+ * to the server completes.
+ */
+function track(name, dimensions, options) {
+ name = name || '';
+ name = name.replace(/^\s*/, '');
+ name = name.replace(/\s*$/, '');
+ if (name.length === 0) {
+ throw new TypeError('A name for the custom event must be provided');
+ }
+
+ for (var key in dimensions) {
+ if (typeof key !== 'string' || typeof dimensions[key] !== 'string') {
+ throw new TypeError('track() dimensions expects keys and values of type "string".');
+ }
+ }
+
+ options = options || {};
+ return _CoreManager2.default.getAnalyticsController().track(name, dimensions)._thenRunCallbacks(options);
+} /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+var DefaultController = {
+ track: function (name, dimensions) {
+ var RESTController = _CoreManager2.default.getRESTController();
+ return RESTController.request('POST', 'events/' + name, { dimensions: dimensions });
+ }
+};
+
+_CoreManager2.default.setAnalyticsController(DefaultController);
\ No newline at end of file
diff --git a/lib/browser/Cloud.js b/lib/browser/Cloud.js
new file mode 100644
index 000000000..72c333750
--- /dev/null
+++ b/lib/browser/Cloud.js
@@ -0,0 +1,109 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.run = run;
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _decode = require('./decode');
+
+var _decode2 = _interopRequireDefault(_decode);
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _ParseError = require('./ParseError');
+
+var _ParseError2 = _interopRequireDefault(_ParseError);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Contains functions for calling and declaring
+ * cloud functions.
+ * + * Some functions are only available from Cloud Code. + *
+ * + * @class Parse.Cloud + * @static + */ + +/** + * Makes a call to a cloud function. + * @method run + * @param {String} name The function name. + * @param {Object} data The parameters to send to the cloud function. + * @param {Object} options A Backbone-style options object + * options.success, if set, should be a function to handle a successful + * call to a cloud function. options.error should be a function that + * handles an error running the cloud function. Both functions are + * optional. Both functions take a single argument. + * @return {Parse.Promise} A promise that will be resolved with the result + * of the function. + */ +function run(name, data, options) { + options = options || {}; + + if (typeof name !== 'string' || name.length === 0) { + throw new TypeError('Cloud function name must be a string.'); + } + + var requestOptions = {}; + if (options.useMasterKey) { + requestOptions.useMasterKey = options.useMasterKey; + } + if (options.sessionToken) { + requestOptions.sessionToken = options.sessionToken; + } + + return _CoreManager2.default.getCloudController().run(name, data, requestOptions)._thenRunCallbacks(options); +} /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +var DefaultController = { + run: function (name, data, options) { + var RESTController = _CoreManager2.default.getRESTController(); + + var payload = (0, _encode2.default)(data, true); + + var requestOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + requestOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + requestOptions.sessionToken = options.sessionToken; + } + + var request = RESTController.request('POST', 'functions/' + name, payload, requestOptions); + + return request.then(function (res) { + var decoded = (0, _decode2.default)(res); + if (decoded && decoded.hasOwnProperty('result')) { + return _ParsePromise2.default.as(decoded.result); + } + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.INVALID_JSON, 'The server returned an invalid response.')); + })._thenRunCallbacks(options); + } +}; + +_CoreManager2.default.setCloudController(DefaultController); \ No newline at end of file diff --git a/lib/browser/CoreManager.js b/lib/browser/CoreManager.js new file mode 100644 index 000000000..20d66a5f1 --- /dev/null +++ b/lib/browser/CoreManager.js @@ -0,0 +1,161 @@ +'use strict'; + +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +var config = { + // Defaults + IS_NODE: typeof process !== 'undefined' && !!process.versions && !!process.versions.node && !process.versions.electron, + REQUEST_ATTEMPT_LIMIT: 5, + SERVER_URL: 'https://api.parse.com/1', + LIVEQUERY_SERVER_URL: null, + VERSION: 'js' + '1.9.2', + APPLICATION_ID: null, + JAVASCRIPT_KEY: null, + MASTER_KEY: null, + USE_MASTER_KEY: false, + PERFORM_USER_REWRITE: true, + FORCE_REVOCABLE_SESSION: false +}; + +function requireMethods(name, methods, controller) { + methods.forEach(function (func) { + if (typeof controller[func] !== 'function') { + throw new Error(name + ' must implement ' + func + '()'); + } + }); +} + +module.exports = { + get: function (key) { + if (config.hasOwnProperty(key)) { + return config[key]; + } + throw new Error('Configuration key not found: ' + key); + }, + + set: function (key, value) { + config[key] = value; + }, + + /* Specialized Controller Setters/Getters */ + + setAnalyticsController: function (controller) { + requireMethods('AnalyticsController', ['track'], controller); + config['AnalyticsController'] = controller; + }, + getAnalyticsController: function () { + return config['AnalyticsController']; + }, + setCloudController: function (controller) { + requireMethods('CloudController', ['run'], controller); + config['CloudController'] = controller; + }, + getCloudController: function () { + return config['CloudController']; + }, + setConfigController: function (controller) { + requireMethods('ConfigController', ['current', 'get'], controller); + config['ConfigController'] = controller; + }, + getConfigController: function () { + return config['ConfigController']; + }, + setFileController: function (controller) { + requireMethods('FileController', ['saveFile', 'saveBase64'], controller); + config['FileController'] = controller; + }, + getFileController: function () { + return config['FileController']; + }, + setInstallationController: function (controller) { + requireMethods('InstallationController', ['currentInstallationId'], controller); + config['InstallationController'] = controller; + }, + getInstallationController: function () { + return config['InstallationController']; + }, + setObjectController: function (controller) { + requireMethods('ObjectController', ['save', 'fetch', 'destroy'], controller); + config['ObjectController'] = controller; + }, + getObjectController: function () { + return config['ObjectController']; + }, + setObjectStateController: function (controller) { + requireMethods('ObjectStateController', ['getState', 'initializeState', 'removeState', 'getServerData', 'setServerData', 'getPendingOps', 'setPendingOp', 'pushPendingState', 'popPendingState', 'mergeFirstPendingState', 'getObjectCache', 'estimateAttribute', 'estimateAttributes', 'commitServerChanges', 'enqueueTask', 'clearAllState'], controller); + + config['ObjectStateController'] = controller; + }, + getObjectStateController: function () { + return config['ObjectStateController']; + }, + setPushController: function (controller) { + requireMethods('PushController', ['send'], controller); + config['PushController'] = controller; + }, + getPushController: function () { + return config['PushController']; + }, + setQueryController: function (controller) { + requireMethods('QueryController', ['find'], controller); + config['QueryController'] = controller; + }, + getQueryController: function () { + return config['QueryController']; + }, + setRESTController: function (controller) { + requireMethods('RESTController', ['request', 'ajax'], controller); + config['RESTController'] = controller; + }, + getRESTController: function () { + return config['RESTController']; + }, + setSessionController: function (controller) { + requireMethods('SessionController', ['getSession'], controller); + config['SessionController'] = controller; + }, + getSessionController: function () { + return config['SessionController']; + }, + setStorageController: function (controller) { + if (controller.async) { + requireMethods('An async StorageController', ['getItemAsync', 'setItemAsync', 'removeItemAsync'], controller); + } else { + requireMethods('A synchronous StorageController', ['getItem', 'setItem', 'removeItem'], controller); + } + config['StorageController'] = controller; + }, + getStorageController: function () { + return config['StorageController']; + }, + setUserController: function (controller) { + requireMethods('UserController', ['setCurrentUser', 'currentUser', 'currentUserAsync', 'signUp', 'logIn', 'become', 'logOut', 'requestPasswordReset', 'upgradeToRevocableSession', 'linkWith'], controller); + config['UserController'] = controller; + }, + getUserController: function () { + return config['UserController']; + }, + setLiveQueryController: function (controller) { + requireMethods('LiveQueryController', ['subscribe', 'unsubscribe', 'open', 'close'], controller); + config['LiveQueryController'] = controller; + }, + getLiveQueryController: function () { + return config['LiveQueryController']; + }, + setHooksController: function (controller) { + requireMethods('HooksController', ['create', 'get', 'update', 'remove'], controller); + config['HooksController'] = controller; + }, + getHooksController: function () { + return config['HooksController']; + } +}; \ No newline at end of file diff --git a/lib/browser/EventEmitter.js b/lib/browser/EventEmitter.js new file mode 100644 index 000000000..e9414f0bf --- /dev/null +++ b/lib/browser/EventEmitter.js @@ -0,0 +1,15 @@ +'use strict'; + +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * This is a simple wrapper to unify EventEmitter implementations across platforms. + */ + +module.exports = require('events').EventEmitter; +var EventEmitter; \ No newline at end of file diff --git a/lib/browser/FacebookUtils.js b/lib/browser/FacebookUtils.js new file mode 100644 index 000000000..bb8b7ef51 --- /dev/null +++ b/lib/browser/FacebookUtils.js @@ -0,0 +1,243 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _parseDate = require('./parseDate'); + +var _parseDate2 = _interopRequireDefault(_parseDate); + +var _ParseUser = require('./ParseUser'); + +var _ParseUser2 = _interopRequireDefault(_ParseUser); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * -weak + */ + +var PUBLIC_KEY = "*"; + +var initialized = false; +var requestedPermissions; +var initOptions; +var provider = { + authenticate: function (options) { + var _this = this; + + if (typeof FB === 'undefined') { + options.error(this, 'Facebook SDK not found.'); + } + FB.login(function (response) { + if (response.authResponse) { + if (options.success) { + options.success(_this, { + id: response.authResponse.userID, + access_token: response.authResponse.accessToken, + expiration_date: new Date(response.authResponse.expiresIn * 1000 + new Date().getTime()).toJSON() + }); + } + } else { + if (options.error) { + options.error(_this, response); + } + } + }, { + scope: requestedPermissions + }); + }, + restoreAuthentication: function (authData) { + if (authData) { + var expiration = (0, _parseDate2.default)(authData.expiration_date); + var expiresIn = expiration ? (expiration.getTime() - new Date().getTime()) / 1000 : 0; + + var authResponse = { + userID: authData.id, + accessToken: authData.access_token, + expiresIn: expiresIn + }; + var newOptions = {}; + if (initOptions) { + for (var key in initOptions) { + newOptions[key] = initOptions[key]; + } + } + newOptions.authResponse = authResponse; + + // Suppress checks for login status from the browser. + newOptions.status = false; + + // If the user doesn't match the one known by the FB SDK, log out. + // Most of the time, the users will match -- it's only in cases where + // the FB SDK knows of a different user than the one being restored + // from a Parse User that logged in with username/password. + var existingResponse = FB.getAuthResponse(); + if (existingResponse && existingResponse.userID !== authResponse.userID) { + FB.logout(); + } + + FB.init(newOptions); + } + return true; + }, + getAuthType: function () { + return 'facebook'; + }, + deauthenticate: function () { + this.restoreAuthentication(null); + } +}; + +/** + * Provides a set of utilities for using Parse with Facebook. + * @class Parse.FacebookUtils + * @static + */ +var FacebookUtils = { + /** + * Initializes Parse Facebook integration. Call this function after you + * have loaded the Facebook Javascript SDK with the same parameters + * as you would pass to
+ *
+ * FB.init(). Parse.FacebookUtils will invoke FB.init() for you
+ * with these arguments.
+ *
+ * @method init
+ * @param {Object} options Facebook options argument as described here:
+ *
+ * FB.init(). The status flag will be coerced to 'false' because it
+ * interferes with Parse Facebook integration. Call FB.getLoginStatus()
+ * explicitly if this behavior is required by your application.
+ */
+ init: function (options) {
+ if (typeof FB === 'undefined') {
+ throw new Error('The Facebook JavaScript SDK must be loaded before calling init.');
+ }
+ initOptions = {};
+ if (options) {
+ for (var key in options) {
+ initOptions[key] = options[key];
+ }
+ }
+ if (initOptions.status && typeof console !== 'undefined') {
+ var warn = console.warn || console.log || function () {};
+ warn.call(console, 'The "status" flag passed into' + ' FB.init, when set to true, can interfere with Parse Facebook' + ' integration, so it has been suppressed. Please call' + ' FB.getLoginStatus() explicitly if you require this behavior.');
+ }
+ initOptions.status = false;
+ FB.init(initOptions);
+ _ParseUser2.default._registerAuthenticationProvider(provider);
+ initialized = true;
+ },
+
+ /**
+ * Gets whether the user has their account linked to Facebook.
+ *
+ * @method isLinked
+ * @param {Parse.User} user User to check for a facebook link.
+ * The user must be logged in on this device.
+ * @return {Boolean} true if the user has their account
+ * linked to Facebook.
+ */
+ isLinked: function (user) {
+ return user._isLinked('facebook');
+ },
+
+ /**
+ * Logs in a user using Facebook. This method delegates to the Facebook
+ * SDK to authenticate the user, and then automatically logs in (or
+ * creates, in the case where it is a new user) a Parse.User.
+ *
+ * @method logIn
+ * @param {String, Object} permissions The permissions required for Facebook
+ * log in. This is a comma-separated string of permissions.
+ * Alternatively, supply a Facebook authData object as described in our
+ * REST API docs if you want to handle getting facebook auth tokens
+ * yourself.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ logIn: function (permissions, options) {
+ if (!permissions || typeof permissions === 'string') {
+ if (!initialized) {
+ throw new Error('You must initialize FacebookUtils before calling logIn.');
+ }
+ requestedPermissions = permissions;
+ return _ParseUser2.default._logInWith('facebook', options);
+ } else {
+ var newOptions = {};
+ if (options) {
+ for (var key in options) {
+ newOptions[key] = options[key];
+ }
+ }
+ newOptions.authData = permissions;
+ return _ParseUser2.default._logInWith('facebook', newOptions);
+ }
+ },
+
+ /**
+ * Links Facebook to an existing PFUser. This method delegates to the
+ * Facebook SDK to authenticate the user, and then automatically links
+ * the account to the Parse.User.
+ *
+ * @method link
+ * @param {Parse.User} user User to link to Facebook. This must be the
+ * current user.
+ * @param {String, Object} permissions The permissions required for Facebook
+ * log in. This is a comma-separated string of permissions.
+ * Alternatively, supply a Facebook authData object as described in our
+ * REST API docs if you want to handle getting facebook auth tokens
+ * yourself.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ link: function (user, permissions, options) {
+ if (!permissions || typeof permissions === 'string') {
+ if (!initialized) {
+ throw new Error('You must initialize FacebookUtils before calling link.');
+ }
+ requestedPermissions = permissions;
+ return user._linkWith('facebook', options);
+ } else {
+ var newOptions = {};
+ if (options) {
+ for (var key in options) {
+ newOptions[key] = options[key];
+ }
+ }
+ newOptions.authData = permissions;
+ return user._linkWith('facebook', newOptions);
+ }
+ },
+
+ /**
+ * Unlinks the Parse.User from a Facebook account.
+ *
+ * @method unlink
+ * @param {Parse.User} user User to unlink from Facebook. This must be the
+ * current user.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ unlink: function (user, options) {
+ if (!initialized) {
+ throw new Error('You must initialize FacebookUtils before calling unlink.');
+ }
+ return user._unlinkFrom('facebook', options);
+ }
+};
+
+exports.default = FacebookUtils;
\ No newline at end of file
diff --git a/lib/browser/InstallationController.js b/lib/browser/InstallationController.js
new file mode 100644
index 000000000..7b01e3cad
--- /dev/null
+++ b/lib/browser/InstallationController.js
@@ -0,0 +1,64 @@
+'use strict';
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+var _Storage = require('./Storage');
+
+var _Storage2 = _interopRequireDefault(_Storage);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+var iidCache = null; /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function hexOctet() {
+ return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
+}
+
+function generateId() {
+ return hexOctet() + hexOctet() + '-' + hexOctet() + '-' + hexOctet() + '-' + hexOctet() + '-' + hexOctet() + hexOctet() + hexOctet();
+}
+
+var InstallationController = {
+ currentInstallationId: function () {
+ if (typeof iidCache === 'string') {
+ return _ParsePromise2.default.as(iidCache);
+ }
+ var path = _Storage2.default.generatePath('installationId');
+ return _Storage2.default.getItemAsync(path).then(function (iid) {
+ if (!iid) {
+ iid = generateId();
+ return _Storage2.default.setItemAsync(path, iid).then(function () {
+ iidCache = iid;
+ return iid;
+ });
+ }
+ iidCache = iid;
+ return iid;
+ });
+ },
+ _clearCache: function () {
+ iidCache = null;
+ },
+ _setInstallationIdCache: function (iid) {
+ iidCache = iid;
+ }
+};
+
+module.exports = InstallationController;
\ No newline at end of file
diff --git a/lib/browser/LiveQueryClient.js b/lib/browser/LiveQueryClient.js
new file mode 100644
index 000000000..bc9603a62
--- /dev/null
+++ b/lib/browser/LiveQueryClient.js
@@ -0,0 +1,595 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _getIterator2 = require('babel-runtime/core-js/get-iterator');
+
+var _getIterator3 = _interopRequireDefault(_getIterator2);
+
+var _stringify = require('babel-runtime/core-js/json/stringify');
+
+var _stringify2 = _interopRequireDefault(_stringify);
+
+var _map = require('babel-runtime/core-js/map');
+
+var _map2 = _interopRequireDefault(_map);
+
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _EventEmitter2 = require('./EventEmitter');
+
+var _EventEmitter3 = _interopRequireDefault(_EventEmitter2);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+var _ParseObject = require('./ParseObject');
+
+var _ParseObject2 = _interopRequireDefault(_ParseObject);
+
+var _LiveQuerySubscription = require('./LiveQuerySubscription');
+
+var _LiveQuerySubscription2 = _interopRequireDefault(_LiveQuerySubscription);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+// The LiveQuery client inner state
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+var CLIENT_STATE = {
+ INITIALIZED: 'initialized',
+ CONNECTING: 'connecting',
+ CONNECTED: 'connected',
+ CLOSED: 'closed',
+ RECONNECTING: 'reconnecting',
+ DISCONNECTED: 'disconnected'
+};
+
+// The event type the LiveQuery client should sent to server
+var OP_TYPES = {
+ CONNECT: 'connect',
+ SUBSCRIBE: 'subscribe',
+ UNSUBSCRIBE: 'unsubscribe',
+ ERROR: 'error'
+};
+
+// The event we get back from LiveQuery server
+var OP_EVENTS = {
+ CONNECTED: 'connected',
+ SUBSCRIBED: 'subscribed',
+ UNSUBSCRIBED: 'unsubscribed',
+ ERROR: 'error',
+ CREATE: 'create',
+ UPDATE: 'update',
+ ENTER: 'enter',
+ LEAVE: 'leave',
+ DELETE: 'delete'
+};
+
+// The event the LiveQuery client should emit
+var CLIENT_EMMITER_TYPES = {
+ CLOSE: 'close',
+ ERROR: 'error',
+ OPEN: 'open'
+};
+
+// The event the LiveQuery subscription should emit
+var SUBSCRIPTION_EMMITER_TYPES = {
+ OPEN: 'open',
+ CLOSE: 'close',
+ ERROR: 'error',
+ CREATE: 'create',
+ UPDATE: 'update',
+ ENTER: 'enter',
+ LEAVE: 'leave',
+ DELETE: 'delete'
+};
+
+var generateInterval = function (k) {
+ return Math.random() * Math.min(30, Math.pow(2, k) - 1) * 1000;
+};
+
+/**
+ * Creates a new LiveQueryClient.
+ * Extends events.EventEmitter
+ * cloud functions.
+ *
+ * A wrapper of a standard WebSocket client. We add several useful methods to
+ * help you connect/disconnect to LiveQueryServer, subscribe/unsubscribe a ParseQuery easily.
+ *
+ * javascriptKey and masterKey are used for verifying the LiveQueryClient when it tries
+ * to connect to the LiveQuery server
+ *
+ * @class Parse.LiveQueryClient
+ * @constructor
+ * @param {Object} options
+ * @param {string} options.applicationId - applicationId of your Parse app
+ * @param {string} options.serverURL - the URL of your LiveQuery server
+ * @param {string} options.javascriptKey (optional)
+ * @param {string} options.masterKey (optional) Your Parse Master Key. (Node.js only!)
+ * @param {string} options.sessionToken (optional)
+ *
+ *
+ * We expose three events to help you monitor the status of the LiveQueryClient.
+ *
+ *
+ * let Parse = require('parse/node');
+ * let LiveQueryClient = Parse.LiveQueryClient;
+ * let client = new LiveQueryClient({
+ * applicationId: '',
+ * serverURL: '',
+ * javascriptKey: '',
+ * masterKey: ''
+ * });
+ *
+ *
+ * Open - When we establish the WebSocket connection to the LiveQuery server, you'll get this event.
+ *
+ * client.on('open', () => {
+ *
+ * });
+ *
+ * Close - When we lose the WebSocket connection to the LiveQuery server, you'll get this event.
+ *
+ * client.on('close', () => {
+ *
+ * });
+ *
+ * Error - When some network error or LiveQuery server error happens, you'll get this event.
+ *
+ * client.on('error', (error) => {
+ *
+ * });
+ *
+ *
+ */
+
+var LiveQueryClient = function (_EventEmitter) {
+ (0, _inherits3.default)(LiveQueryClient, _EventEmitter);
+
+ function LiveQueryClient(_ref) {
+ var applicationId = _ref.applicationId,
+ serverURL = _ref.serverURL,
+ javascriptKey = _ref.javascriptKey,
+ masterKey = _ref.masterKey,
+ sessionToken = _ref.sessionToken;
+ (0, _classCallCheck3.default)(this, LiveQueryClient);
+
+ var _this = (0, _possibleConstructorReturn3.default)(this, (LiveQueryClient.__proto__ || (0, _getPrototypeOf2.default)(LiveQueryClient)).call(this));
+
+ if (!serverURL || serverURL.indexOf('ws') !== 0) {
+ throw new Error('You need to set a proper Parse LiveQuery server url before using LiveQueryClient');
+ }
+
+ _this.reconnectHandle = null;
+ _this.attempts = 1;;
+ _this.id = 0;
+ _this.requestId = 1;
+ _this.serverURL = serverURL;
+ _this.applicationId = applicationId;
+ _this.javascriptKey = javascriptKey;
+ _this.masterKey = masterKey;
+ _this.sessionToken = sessionToken;
+ _this.connectPromise = new _ParsePromise2.default();
+ _this.subscriptions = new _map2.default();
+ _this.state = CLIENT_STATE.INITIALIZED;
+ return _this;
+ }
+
+ (0, _createClass3.default)(LiveQueryClient, [{
+ key: 'shouldOpen',
+ value: function () {
+ return this.state === CLIENT_STATE.INITIALIZED || this.state === CLIENT_STATE.DISCONNECTED;
+ }
+
+ /**
+ * Subscribes to a ParseQuery
+ *
+ * If you provide the sessionToken, when the LiveQuery server gets ParseObject's
+ * updates from parse server, it'll try to check whether the sessionToken fulfills
+ * the ParseObject's ACL. The LiveQuery server will only send updates to clients whose
+ * sessionToken is fit for the ParseObject's ACL. You can check the LiveQuery protocol
+ * here for more details. The subscription you get is the same subscription you get
+ * from our Standard API.
+ *
+ * @method subscribe
+ * @param {Object} query - the ParseQuery you want to subscribe to
+ * @param {string} sessionToken (optional)
+ * @return {Object} subscription
+ */
+
+ }, {
+ key: 'subscribe',
+ value: function (query, sessionToken) {
+ var _this2 = this;
+
+ if (!query) {
+ return;
+ }
+ var where = query.toJSON().where;
+ var className = query.className;
+ var subscribeRequest = {
+ op: OP_TYPES.SUBSCRIBE,
+ requestId: this.requestId,
+ query: {
+ className: className,
+ where: where
+ }
+ };
+
+ if (sessionToken) {
+ subscribeRequest.sessionToken = sessionToken;
+ }
+
+ var subscription = new _LiveQuerySubscription2.default(this.requestId, query, sessionToken);
+ this.subscriptions.set(this.requestId, subscription);
+ this.requestId += 1;
+ this.connectPromise.then(function () {
+ _this2.socket.send((0, _stringify2.default)(subscribeRequest));
+ });
+
+ // adding listener so process does not crash
+ // best practice is for developer to register their own listener
+ subscription.on('error', function () {});
+
+ return subscription;
+ }
+
+ /**
+ * After calling unsubscribe you'll stop receiving events from the subscription object.
+ *
+ * @method unsubscribe
+ * @param {Object} subscription - subscription you would like to unsubscribe from.
+ */
+
+ }, {
+ key: 'unsubscribe',
+ value: function (subscription) {
+ var _this3 = this;
+
+ if (!subscription) {
+ return;
+ }
+
+ this.subscriptions.delete(subscription.id);
+ var unsubscribeRequest = {
+ op: OP_TYPES.UNSUBSCRIBE,
+ requestId: subscription.id
+ };
+ this.connectPromise.then(function () {
+ _this3.socket.send((0, _stringify2.default)(unsubscribeRequest));
+ });
+ }
+
+ /**
+ * After open is called, the LiveQueryClient will try to send a connect request
+ * to the LiveQuery server.
+ *
+ * @method open
+ */
+
+ }, {
+ key: 'open',
+ value: function () {
+ var _this4 = this;
+
+ var WebSocketImplementation = this._getWebSocketImplementation();
+ if (!WebSocketImplementation) {
+ this.emit(CLIENT_EMMITER_TYPES.ERROR, 'Can not find WebSocket implementation');
+ return;
+ }
+
+ if (this.state !== CLIENT_STATE.RECONNECTING) {
+ this.state = CLIENT_STATE.CONNECTING;
+ }
+
+ // Get WebSocket implementation
+ this.socket = new WebSocketImplementation(this.serverURL);
+
+ // Bind WebSocket callbacks
+ this.socket.onopen = function () {
+ _this4._handleWebSocketOpen();
+ };
+
+ this.socket.onmessage = function (event) {
+ _this4._handleWebSocketMessage(event);
+ };
+
+ this.socket.onclose = function () {
+ _this4._handleWebSocketClose();
+ };
+
+ this.socket.onerror = function (error) {
+ _this4._handleWebSocketError(error);
+ };
+ }
+ }, {
+ key: 'resubscribe',
+ value: function () {
+ var _this5 = this;
+
+ this.subscriptions.forEach(function (subscription, requestId) {
+ var query = subscription.query;
+ var where = query.toJSON().where;
+ var className = query.className;
+ var sessionToken = subscription.sessionToken;
+ var subscribeRequest = {
+ op: OP_TYPES.SUBSCRIBE,
+ requestId: requestId,
+ query: {
+ className: className,
+ where: where
+ }
+ };
+
+ if (sessionToken) {
+ subscribeRequest.sessionToken = sessionToken;
+ }
+
+ _this5.connectPromise.then(function () {
+ _this5.socket.send((0, _stringify2.default)(subscribeRequest));
+ });
+ });
+ }
+
+ /**
+ * This method will close the WebSocket connection to this LiveQueryClient,
+ * cancel the auto reconnect and unsubscribe all subscriptions based on it.
+ *
+ * @method close
+ */
+
+ }, {
+ key: 'close',
+ value: function () {
+ if (this.state === CLIENT_STATE.INITIALIZED || this.state === CLIENT_STATE.DISCONNECTED) {
+ return;
+ }
+ this.state = CLIENT_STATE.DISCONNECTED;
+ this.socket.close();
+ // Notify each subscription about the close
+ var _iteratorNormalCompletion = true;
+ var _didIteratorError = false;
+ var _iteratorError = undefined;
+
+ try {
+ for (var _iterator = (0, _getIterator3.default)(this.subscriptions.values()), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+ var subscription = _step.value;
+
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.CLOSE);
+ }
+ } catch (err) {
+ _didIteratorError = true;
+ _iteratorError = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion && _iterator.return) {
+ _iterator.return();
+ }
+ } finally {
+ if (_didIteratorError) {
+ throw _iteratorError;
+ }
+ }
+ }
+
+ this._handleReset();
+ this.emit(CLIENT_EMMITER_TYPES.CLOSE);
+ }
+ }, {
+ key: '_getWebSocketImplementation',
+ value: function () {
+ return typeof WebSocket === 'function' || (typeof WebSocket === 'undefined' ? 'undefined' : (0, _typeof3.default)(WebSocket)) === 'object' ? WebSocket : null;
+ }
+
+ // ensure we start with valid state if connect is called again after close
+
+ }, {
+ key: '_handleReset',
+ value: function () {
+ this.attempts = 1;;
+ this.id = 0;
+ this.requestId = 1;
+ this.connectPromise = new _ParsePromise2.default();
+ this.subscriptions = new _map2.default();
+ }
+ }, {
+ key: '_handleWebSocketOpen',
+ value: function () {
+ this.attempts = 1;
+ var connectRequest = {
+ op: OP_TYPES.CONNECT,
+ applicationId: this.applicationId,
+ javascriptKey: this.javascriptKey,
+ masterKey: this.masterKey,
+ sessionToken: this.sessionToken
+ };
+ this.socket.send((0, _stringify2.default)(connectRequest));
+ }
+ }, {
+ key: '_handleWebSocketMessage',
+ value: function (event) {
+ var data = event.data;
+ if (typeof data === 'string') {
+ data = JSON.parse(data);
+ }
+ var subscription = null;
+ if (data.requestId) {
+ subscription = this.subscriptions.get(data.requestId);
+ }
+ switch (data.op) {
+ case OP_EVENTS.CONNECTED:
+ if (this.state === CLIENT_STATE.RECONNECTING) {
+ this.resubscribe();
+ }
+ this.emit(CLIENT_EMMITER_TYPES.OPEN);
+ this.id = data.clientId;
+ this.connectPromise.resolve();
+ this.state = CLIENT_STATE.CONNECTED;
+ break;
+ case OP_EVENTS.SUBSCRIBED:
+ if (subscription) {
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.OPEN);
+ }
+ break;
+ case OP_EVENTS.ERROR:
+ if (data.requestId) {
+ if (subscription) {
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.ERROR, data.error);
+ }
+ } else {
+ this.emit(CLIENT_EMMITER_TYPES.ERROR, data.error);
+ }
+ break;
+ case OP_EVENTS.UNSUBSCRIBED:
+ // We have already deleted subscription in unsubscribe(), do nothing here
+ break;
+ default:
+ // create, update, enter, leave, delete cases
+ var className = data.object.className;
+ // Delete the extrea __type and className fields during transfer to full JSON
+ delete data.object.__type;
+ delete data.object.className;
+ var parseObject = new _ParseObject2.default(className);
+ parseObject._finishFetch(data.object);
+ if (!subscription) {
+ break;
+ }
+ subscription.emit(data.op, parseObject);
+ }
+ }
+ }, {
+ key: '_handleWebSocketClose',
+ value: function () {
+ if (this.state === CLIENT_STATE.DISCONNECTED) {
+ return;
+ }
+ this.state = CLIENT_STATE.CLOSED;
+ this.emit(CLIENT_EMMITER_TYPES.CLOSE);
+ // Notify each subscription about the close
+ var _iteratorNormalCompletion2 = true;
+ var _didIteratorError2 = false;
+ var _iteratorError2 = undefined;
+
+ try {
+ for (var _iterator2 = (0, _getIterator3.default)(this.subscriptions.values()), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+ var subscription = _step2.value;
+
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.CLOSE);
+ }
+ } catch (err) {
+ _didIteratorError2 = true;
+ _iteratorError2 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion2 && _iterator2.return) {
+ _iterator2.return();
+ }
+ } finally {
+ if (_didIteratorError2) {
+ throw _iteratorError2;
+ }
+ }
+ }
+
+ this._handleReconnect();
+ }
+ }, {
+ key: '_handleWebSocketError',
+ value: function (error) {
+ this.emit(CLIENT_EMMITER_TYPES.ERROR, error);
+ var _iteratorNormalCompletion3 = true;
+ var _didIteratorError3 = false;
+ var _iteratorError3 = undefined;
+
+ try {
+ for (var _iterator3 = (0, _getIterator3.default)(this.subscriptions.values()), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
+ var subscription = _step3.value;
+
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.ERROR);
+ }
+ } catch (err) {
+ _didIteratorError3 = true;
+ _iteratorError3 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion3 && _iterator3.return) {
+ _iterator3.return();
+ }
+ } finally {
+ if (_didIteratorError3) {
+ throw _iteratorError3;
+ }
+ }
+ }
+
+ this._handleReconnect();
+ }
+ }, {
+ key: '_handleReconnect',
+ value: function () {
+ var _this6 = this;
+
+ // if closed or currently reconnecting we stop attempting to reconnect
+ if (this.state === CLIENT_STATE.DISCONNECTED) {
+ return;
+ }
+
+ this.state = CLIENT_STATE.RECONNECTING;
+ var time = generateInterval(this.attempts);
+
+ // handle case when both close/error occur at frequent rates we ensure we do not reconnect unnecessarily.
+ // we're unable to distinguish different between close/error when we're unable to reconnect therefore
+ // we try to reonnect in both cases
+ // server side ws and browser WebSocket behave differently in when close/error get triggered
+
+ if (this.reconnectHandle) {
+ clearTimeout(this.reconnectHandle);
+ }
+
+ this.reconnectHandle = setTimeout(function () {
+ _this6.attempts++;
+ _this6.connectPromise = new _ParsePromise2.default();
+ _this6.open();
+ }.bind(this), time);
+ }
+ }]);
+ return LiveQueryClient;
+}(_EventEmitter3.default);
+
+exports.default = LiveQueryClient;
\ No newline at end of file
diff --git a/lib/browser/LiveQuerySubscription.js b/lib/browser/LiveQuerySubscription.js
new file mode 100644
index 000000000..4078b65d5
--- /dev/null
+++ b/lib/browser/LiveQuerySubscription.js
@@ -0,0 +1,162 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _EventEmitter2 = require('./EventEmitter');
+
+var _EventEmitter3 = _interopRequireDefault(_EventEmitter2);
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Creates a new LiveQuery Subscription.
+ * Extends events.EventEmitter
+ * cloud functions.
+ *
+ * @constructor
+ * @param {string} id - subscription id
+ * @param {string} query - query to subscribe to
+ * @param {string} sessionToken - optional session token
+ *
+ * Open Event - When you call query.subscribe(), we send a subscribe request to + * the LiveQuery server, when we get the confirmation from the LiveQuery server, + * this event will be emitted. When the client loses WebSocket connection to the + * LiveQuery server, we will try to auto reconnect the LiveQuery server. If we + * reconnect the LiveQuery server and successfully resubscribe the ParseQuery, + * you'll also get this event. + * + *
+ * subscription.on('open', () => {
+ *
+ * });
+ *
+ * Create Event - When a new ParseObject is created and it fulfills the ParseQuery you subscribe, + * you'll get this event. The object is the ParseObject which is created. + * + *
+ * subscription.on('create', (object) => {
+ *
+ * });
+ *
+ * Update Event - When an existing ParseObject which fulfills the ParseQuery you subscribe + * is updated (The ParseObject fulfills the ParseQuery before and after changes), + * you'll get this event. The object is the ParseObject which is updated. + * Its content is the latest value of the ParseObject. + * + *
+ * subscription.on('update', (object) => {
+ *
+ * });
+ *
+ * Enter Event - When an existing ParseObject's old value doesn't fulfill the ParseQuery + * but its new value fulfills the ParseQuery, you'll get this event. The object is the + * ParseObject which enters the ParseQuery. Its content is the latest value of the ParseObject. + * + *
+ * subscription.on('enter', (object) => {
+ *
+ * });
+ *
+ *
+ * Update Event - When an existing ParseObject's old value fulfills the ParseQuery but its new value + * doesn't fulfill the ParseQuery, you'll get this event. The object is the ParseObject + * which leaves the ParseQuery. Its content is the latest value of the ParseObject. + * + *
+ * subscription.on('leave', (object) => {
+ *
+ * });
+ *
+ *
+ * Delete Event - When an existing ParseObject which fulfills the ParseQuery is deleted, you'll + * get this event. The object is the ParseObject which is deleted. + * + *
+ * subscription.on('delete', (object) => {
+ *
+ * });
+ *
+ *
+ * Close Event - When the client loses the WebSocket connection to the LiveQuery + * server and we stop receiving events, you'll get this event. + * + *
+ * subscription.on('close', () => {
+ *
+ * });
+ *
+ *
+ */
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+var Subscription = function (_EventEmitter) {
+ (0, _inherits3.default)(Subscription, _EventEmitter);
+
+ function Subscription(id, query, sessionToken) {
+ (0, _classCallCheck3.default)(this, Subscription);
+
+ var _this2 = (0, _possibleConstructorReturn3.default)(this, (Subscription.__proto__ || (0, _getPrototypeOf2.default)(Subscription)).call(this));
+
+ _this2.id = id;
+ _this2.query = query;
+ _this2.sessionToken = sessionToken;
+ return _this2;
+ }
+
+ /**
+ * @method unsubscribe
+ */
+
+ (0, _createClass3.default)(Subscription, [{
+ key: 'unsubscribe',
+ value: function () {
+ var _this3 = this;
+
+ var _this = this;
+ _CoreManager2.default.getLiveQueryController().getDefaultLiveQueryClient().then(function (liveQueryClient) {
+ liveQueryClient.unsubscribe(_this);
+ _this.emit('close');
+ _this3.resolve();
+ });
+ }
+ }]);
+ return Subscription;
+}(_EventEmitter3.default);
+
+exports.default = Subscription;
\ No newline at end of file
diff --git a/lib/browser/ObjectStateMutations.js b/lib/browser/ObjectStateMutations.js
new file mode 100644
index 000000000..b476a0e2f
--- /dev/null
+++ b/lib/browser/ObjectStateMutations.js
@@ -0,0 +1,165 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _stringify = require('babel-runtime/core-js/json/stringify');
+
+var _stringify2 = _interopRequireDefault(_stringify);
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+exports.defaultState = defaultState;
+exports.setServerData = setServerData;
+exports.setPendingOp = setPendingOp;
+exports.pushPendingState = pushPendingState;
+exports.popPendingState = popPendingState;
+exports.mergeFirstPendingState = mergeFirstPendingState;
+exports.estimateAttribute = estimateAttribute;
+exports.estimateAttributes = estimateAttributes;
+exports.commitServerChanges = commitServerChanges;
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _ParseFile = require('./ParseFile');
+
+var _ParseFile2 = _interopRequireDefault(_ParseFile);
+
+var _ParseObject = require('./ParseObject');
+
+var _ParseObject2 = _interopRequireDefault(_ParseObject);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+var _ParseRelation = require('./ParseRelation');
+
+var _ParseRelation2 = _interopRequireDefault(_ParseRelation);
+
+var _TaskQueue = require('./TaskQueue');
+
+var _TaskQueue2 = _interopRequireDefault(_TaskQueue);
+
+var _ParseOp = require('./ParseOp');
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+function defaultState() {
+ return {
+ serverData: {},
+ pendingOps: [{}],
+ objectCache: {},
+ tasks: new _TaskQueue2.default(),
+ existed: false
+ };
+} /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function setServerData(serverData, attributes) {
+ for (var _attr in attributes) {
+ if (typeof attributes[_attr] !== 'undefined') {
+ serverData[_attr] = attributes[_attr];
+ } else {
+ delete serverData[_attr];
+ }
+ }
+}
+
+function setPendingOp(pendingOps, attr, op) {
+ var last = pendingOps.length - 1;
+ if (op) {
+ pendingOps[last][attr] = op;
+ } else {
+ delete pendingOps[last][attr];
+ }
+}
+
+function pushPendingState(pendingOps) {
+ pendingOps.push({});
+}
+
+function popPendingState(pendingOps) {
+ var first = pendingOps.shift();
+ if (!pendingOps.length) {
+ pendingOps[0] = {};
+ }
+ return first;
+}
+
+function mergeFirstPendingState(pendingOps) {
+ var first = popPendingState(pendingOps);
+ var next = pendingOps[0];
+ for (var _attr2 in first) {
+ if (next[_attr2] && first[_attr2]) {
+ var merged = next[_attr2].mergeWith(first[_attr2]);
+ if (merged) {
+ next[_attr2] = merged;
+ }
+ } else {
+ next[_attr2] = first[_attr2];
+ }
+ }
+}
+
+function estimateAttribute(serverData, pendingOps, className, id, attr) {
+ var value = serverData[attr];
+ for (var i = 0; i < pendingOps.length; i++) {
+ if (pendingOps[i][attr]) {
+ if (pendingOps[i][attr] instanceof _ParseOp.RelationOp) {
+ if (id) {
+ value = pendingOps[i][attr].applyTo(value, { className: className, id: id }, attr);
+ }
+ } else {
+ value = pendingOps[i][attr].applyTo(value);
+ }
+ }
+ }
+ return value;
+}
+
+function estimateAttributes(serverData, pendingOps, className, id) {
+ var data = {};
+ var attr = void 0;
+ for (attr in serverData) {
+ data[attr] = serverData[attr];
+ }
+ for (var i = 0; i < pendingOps.length; i++) {
+ for (attr in pendingOps[i]) {
+ if (pendingOps[i][attr] instanceof _ParseOp.RelationOp) {
+ if (id) {
+ data[attr] = pendingOps[i][attr].applyTo(data[attr], { className: className, id: id }, attr);
+ }
+ } else {
+ data[attr] = pendingOps[i][attr].applyTo(data[attr]);
+ }
+ }
+ }
+ return data;
+}
+
+function commitServerChanges(serverData, objectCache, changes) {
+ for (var _attr3 in changes) {
+ var val = changes[_attr3];
+ serverData[_attr3] = val;
+ if (val && (typeof val === 'undefined' ? 'undefined' : (0, _typeof3.default)(val)) === 'object' && !(val instanceof _ParseObject2.default) && !(val instanceof _ParseFile2.default) && !(val instanceof _ParseRelation2.default)) {
+ var json = (0, _encode2.default)(val, false, true);
+ objectCache[_attr3] = (0, _stringify2.default)(json);
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/browser/Parse.js b/lib/browser/Parse.js
new file mode 100644
index 000000000..2499dc786
--- /dev/null
+++ b/lib/browser/Parse.js
@@ -0,0 +1,186 @@
+'use strict';
+
+var _decode = require('./decode');
+
+var _decode2 = _interopRequireDefault(_decode);
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _InstallationController = require('./InstallationController');
+
+var _InstallationController2 = _interopRequireDefault(_InstallationController);
+
+var _ParseOp = require('./ParseOp');
+
+var ParseOp = _interopRequireWildcard(_ParseOp);
+
+var _RESTController = require('./RESTController');
+
+var _RESTController2 = _interopRequireDefault(_RESTController);
+
+function _interopRequireWildcard(obj) {
+ if (obj && obj.__esModule) {
+ return obj;
+ } else {
+ var newObj = {};if (obj != null) {
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];
+ }
+ }newObj.default = obj;return newObj;
+ }
+}
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Contains all Parse API classes and functions.
+ * @class Parse
+ * @static
+ */
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+var Parse = {
+ /**
+ * Call this method first to set up your authentication tokens for Parse.
+ * You can get your keys from the Data Browser on parse.com.
+ * @method initialize
+ * @param {String} applicationId Your Parse Application ID.
+ * @param {String} javaScriptKey (optional) Your Parse JavaScript Key (Not needed for parse-server)
+ * @param {String} masterKey (optional) Your Parse Master Key. (Node.js only!)
+ * @static
+ */
+ initialize: function (applicationId, javaScriptKey) {
+ if ('browser' === 'browser' && _CoreManager2.default.get('IS_NODE')) {
+ console.log('It looks like you\'re using the browser version of the SDK in a ' + 'node.js environment. You should require(\'parse/node\') instead.');
+ }
+ Parse._initialize(applicationId, javaScriptKey);
+ },
+ _initialize: function (applicationId, javaScriptKey, masterKey) {
+ _CoreManager2.default.set('APPLICATION_ID', applicationId);
+ _CoreManager2.default.set('JAVASCRIPT_KEY', javaScriptKey);
+ _CoreManager2.default.set('MASTER_KEY', masterKey);
+ _CoreManager2.default.set('USE_MASTER_KEY', false);
+ }
+};
+
+/** These legacy setters may eventually be deprecated **/
+Object.defineProperty(Parse, 'applicationId', {
+ get: function () {
+ return _CoreManager2.default.get('APPLICATION_ID');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('APPLICATION_ID', value);
+ }
+});
+Object.defineProperty(Parse, 'javaScriptKey', {
+ get: function () {
+ return _CoreManager2.default.get('JAVASCRIPT_KEY');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('JAVASCRIPT_KEY', value);
+ }
+});
+Object.defineProperty(Parse, 'masterKey', {
+ get: function () {
+ return _CoreManager2.default.get('MASTER_KEY');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('MASTER_KEY', value);
+ }
+});
+Object.defineProperty(Parse, 'serverURL', {
+ get: function () {
+ return _CoreManager2.default.get('SERVER_URL');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('SERVER_URL', value);
+ }
+});
+Object.defineProperty(Parse, 'liveQueryServerURL', {
+ get: function () {
+ return _CoreManager2.default.get('LIVEQUERY_SERVER_URL');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('LIVEQUERY_SERVER_URL', value);
+ }
+});
+/** End setters **/
+
+Parse.ACL = require('./ParseACL').default;
+Parse.Analytics = require('./Analytics');
+Parse.Cloud = require('./Cloud');
+Parse.CoreManager = require('./CoreManager');
+Parse.Config = require('./ParseConfig').default;
+Parse.Error = require('./ParseError').default;
+Parse.FacebookUtils = require('./FacebookUtils').default;
+Parse.File = require('./ParseFile').default;
+Parse.GeoPoint = require('./ParseGeoPoint').default;
+Parse.Installation = require('./ParseInstallation').default;
+Parse.Object = require('./ParseObject').default;
+Parse.Op = {
+ Set: ParseOp.SetOp,
+ Unset: ParseOp.UnsetOp,
+ Increment: ParseOp.IncrementOp,
+ Add: ParseOp.AddOp,
+ Remove: ParseOp.RemoveOp,
+ AddUnique: ParseOp.AddUniqueOp,
+ Relation: ParseOp.RelationOp
+};
+Parse.Promise = require('./ParsePromise').default;
+Parse.Push = require('./Push');
+Parse.Query = require('./ParseQuery').default;
+Parse.Relation = require('./ParseRelation').default;
+Parse.Role = require('./ParseRole').default;
+Parse.Session = require('./ParseSession').default;
+Parse.Storage = require('./Storage');
+Parse.User = require('./ParseUser').default;
+Parse.LiveQuery = require('./ParseLiveQuery').default;
+Parse.LiveQueryClient = require('./LiveQueryClient').default;
+
+Parse._request = function () {
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ return _CoreManager2.default.getRESTController().request.apply(null, args);
+};
+Parse._ajax = function () {
+ for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+
+ return _CoreManager2.default.getRESTController().ajax.apply(null, args);
+};
+// We attempt to match the signatures of the legacy versions of these methods
+Parse._decode = function (_, value) {
+ return (0, _decode2.default)(value);
+};
+Parse._encode = function (value, _, disallowObjects) {
+ return (0, _encode2.default)(value, disallowObjects);
+};
+Parse._getInstallationId = function () {
+ return _CoreManager2.default.getInstallationController().currentInstallationId();
+};
+
+_CoreManager2.default.setInstallationController(_InstallationController2.default);
+_CoreManager2.default.setRESTController(_RESTController2.default);
+
+// For legacy requires, of the form `var Parse = require('parse').Parse`
+Parse.Parse = Parse;
+
+module.exports = Parse;
\ No newline at end of file
diff --git a/lib/browser/ParseACL.js b/lib/browser/ParseACL.js
new file mode 100644
index 000000000..2aa232c18
--- /dev/null
+++ b/lib/browser/ParseACL.js
@@ -0,0 +1,406 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _keys = require('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _ParseRole = require('./ParseRole');
+
+var _ParseRole2 = _interopRequireDefault(_ParseRole);
+
+var _ParseUser = require('./ParseUser');
+
+var _ParseUser2 = _interopRequireDefault(_ParseUser);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+var PUBLIC_KEY = '*';
+
+/**
+ * Creates a new ACL.
+ * If no argument is given, the ACL has no permissions for anyone.
+ * If the argument is a Parse.User, the ACL will have read and write
+ * permission for only that user.
+ * If the argument is any other JSON object, that object will be interpretted
+ * as a serialized ACL created with toJSON().
+ * @class Parse.ACL
+ * @constructor
+ *
+ * An ACL, or Access Control List can be added to any
+ * Parse.Object to restrict access to only a subset of users
+ * of your application.
Parse.Error.
+ * @param {String} message A detailed description of the error.
+ */
+var ParseError = function ParseError(code, message) {
+ (0, _classCallCheck3.default)(this, ParseError);
+
+ this.code = code;
+ this.message = message;
+};
+
+/**
+ * Error code indicating some error other than those enumerated here.
+ * @property OTHER_CAUSE
+ * @static
+ * @final
+ */
+
+exports.default = ParseError;
+ParseError.OTHER_CAUSE = -1;
+
+/**
+ * Error code indicating that something has gone wrong with the server.
+ * If you get this error code, it is Parse's fault. Contact us at
+ * https://parse.com/help
+ * @property INTERNAL_SERVER_ERROR
+ * @static
+ * @final
+ */
+ParseError.INTERNAL_SERVER_ERROR = 1;
+
+/**
+ * Error code indicating the connection to the Parse servers failed.
+ * @property CONNECTION_FAILED
+ * @static
+ * @final
+ */
+ParseError.CONNECTION_FAILED = 100;
+
+/**
+ * Error code indicating the specified object doesn't exist.
+ * @property OBJECT_NOT_FOUND
+ * @static
+ * @final
+ */
+ParseError.OBJECT_NOT_FOUND = 101;
+
+/**
+ * Error code indicating you tried to query with a datatype that doesn't
+ * support it, like exact matching an array or object.
+ * @property INVALID_QUERY
+ * @static
+ * @final
+ */
+ParseError.INVALID_QUERY = 102;
+
+/**
+ * Error code indicating a missing or invalid classname. Classnames are
+ * case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the
+ * only valid characters.
+ * @property INVALID_CLASS_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_CLASS_NAME = 103;
+
+/**
+ * Error code indicating an unspecified object id.
+ * @property MISSING_OBJECT_ID
+ * @static
+ * @final
+ */
+ParseError.MISSING_OBJECT_ID = 104;
+
+/**
+ * Error code indicating an invalid key name. Keys are case-sensitive. They
+ * must start with a letter, and a-zA-Z0-9_ are the only valid characters.
+ * @property INVALID_KEY_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_KEY_NAME = 105;
+
+/**
+ * Error code indicating a malformed pointer. You should not see this unless
+ * you have been mucking about changing internal Parse code.
+ * @property INVALID_POINTER
+ * @static
+ * @final
+ */
+ParseError.INVALID_POINTER = 106;
+
+/**
+ * Error code indicating that badly formed JSON was received upstream. This
+ * either indicates you have done something unusual with modifying how
+ * things encode to JSON, or the network is failing badly.
+ * @property INVALID_JSON
+ * @static
+ * @final
+ */
+ParseError.INVALID_JSON = 107;
+
+/**
+ * Error code indicating that the feature you tried to access is only
+ * available internally for testing purposes.
+ * @property COMMAND_UNAVAILABLE
+ * @static
+ * @final
+ */
+ParseError.COMMAND_UNAVAILABLE = 108;
+
+/**
+ * You must call Parse.initialize before using the Parse library.
+ * @property NOT_INITIALIZED
+ * @static
+ * @final
+ */
+ParseError.NOT_INITIALIZED = 109;
+
+/**
+ * Error code indicating that a field was set to an inconsistent type.
+ * @property INCORRECT_TYPE
+ * @static
+ * @final
+ */
+ParseError.INCORRECT_TYPE = 111;
+
+/**
+ * Error code indicating an invalid channel name. A channel name is either
+ * an empty string (the broadcast channel) or contains only a-zA-Z0-9_
+ * characters and starts with a letter.
+ * @property INVALID_CHANNEL_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_CHANNEL_NAME = 112;
+
+/**
+ * Error code indicating that push is misconfigured.
+ * @property PUSH_MISCONFIGURED
+ * @static
+ * @final
+ */
+ParseError.PUSH_MISCONFIGURED = 115;
+
+/**
+ * Error code indicating that the object is too large.
+ * @property OBJECT_TOO_LARGE
+ * @static
+ * @final
+ */
+ParseError.OBJECT_TOO_LARGE = 116;
+
+/**
+ * Error code indicating that the operation isn't allowed for clients.
+ * @property OPERATION_FORBIDDEN
+ * @static
+ * @final
+ */
+ParseError.OPERATION_FORBIDDEN = 119;
+
+/**
+ * Error code indicating the result was not found in the cache.
+ * @property CACHE_MISS
+ * @static
+ * @final
+ */
+ParseError.CACHE_MISS = 120;
+
+/**
+ * Error code indicating that an invalid key was used in a nested
+ * JSONObject.
+ * @property INVALID_NESTED_KEY
+ * @static
+ * @final
+ */
+ParseError.INVALID_NESTED_KEY = 121;
+
+/**
+ * Error code indicating that an invalid filename was used for ParseFile.
+ * A valid file name contains only a-zA-Z0-9_. characters and is between 1
+ * and 128 characters.
+ * @property INVALID_FILE_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_FILE_NAME = 122;
+
+/**
+ * Error code indicating an invalid ACL was provided.
+ * @property INVALID_ACL
+ * @static
+ * @final
+ */
+ParseError.INVALID_ACL = 123;
+
+/**
+ * Error code indicating that the request timed out on the server. Typically
+ * this indicates that the request is too expensive to run.
+ * @property TIMEOUT
+ * @static
+ * @final
+ */
+ParseError.TIMEOUT = 124;
+
+/**
+ * Error code indicating that the email address was invalid.
+ * @property INVALID_EMAIL_ADDRESS
+ * @static
+ * @final
+ */
+ParseError.INVALID_EMAIL_ADDRESS = 125;
+
+/**
+ * Error code indicating a missing content type.
+ * @property MISSING_CONTENT_TYPE
+ * @static
+ * @final
+ */
+ParseError.MISSING_CONTENT_TYPE = 126;
+
+/**
+ * Error code indicating a missing content length.
+ * @property MISSING_CONTENT_LENGTH
+ * @static
+ * @final
+ */
+ParseError.MISSING_CONTENT_LENGTH = 127;
+
+/**
+ * Error code indicating an invalid content length.
+ * @property INVALID_CONTENT_LENGTH
+ * @static
+ * @final
+ */
+ParseError.INVALID_CONTENT_LENGTH = 128;
+
+/**
+ * Error code indicating a file that was too large.
+ * @property FILE_TOO_LARGE
+ * @static
+ * @final
+ */
+ParseError.FILE_TOO_LARGE = 129;
+
+/**
+ * Error code indicating an error saving a file.
+ * @property FILE_SAVE_ERROR
+ * @static
+ * @final
+ */
+ParseError.FILE_SAVE_ERROR = 130;
+
+/**
+ * Error code indicating that a unique field was given a value that is
+ * already taken.
+ * @property DUPLICATE_VALUE
+ * @static
+ * @final
+ */
+ParseError.DUPLICATE_VALUE = 137;
+
+/**
+ * Error code indicating that a role's name is invalid.
+ * @property INVALID_ROLE_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_ROLE_NAME = 139;
+
+/**
+ * Error code indicating that an application quota was exceeded. Upgrade to
+ * resolve.
+ * @property EXCEEDED_QUOTA
+ * @static
+ * @final
+ */
+ParseError.EXCEEDED_QUOTA = 140;
+
+/**
+ * Error code indicating that a Cloud Code script failed.
+ * @property SCRIPT_FAILED
+ * @static
+ * @final
+ */
+ParseError.SCRIPT_FAILED = 141;
+
+/**
+ * Error code indicating that a Cloud Code validation failed.
+ * @property VALIDATION_ERROR
+ * @static
+ * @final
+ */
+ParseError.VALIDATION_ERROR = 142;
+
+/**
+ * Error code indicating that invalid image data was provided.
+ * @property INVALID_IMAGE_DATA
+ * @static
+ * @final
+ */
+ParseError.INVALID_IMAGE_DATA = 143;
+
+/**
+ * Error code indicating an unsaved file.
+ * @property UNSAVED_FILE_ERROR
+ * @static
+ * @final
+ */
+ParseError.UNSAVED_FILE_ERROR = 151;
+
+/**
+ * Error code indicating an invalid push time.
+ * @property INVALID_PUSH_TIME_ERROR
+ * @static
+ * @final
+ */
+ParseError.INVALID_PUSH_TIME_ERROR = 152;
+
+/**
+ * Error code indicating an error deleting a file.
+ * @property FILE_DELETE_ERROR
+ * @static
+ * @final
+ */
+ParseError.FILE_DELETE_ERROR = 153;
+
+/**
+ * Error code indicating that the application has exceeded its request
+ * limit.
+ * @property REQUEST_LIMIT_EXCEEDED
+ * @static
+ * @final
+ */
+ParseError.REQUEST_LIMIT_EXCEEDED = 155;
+
+/**
+ * Error code indicating an invalid event name.
+ * @property INVALID_EVENT_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_EVENT_NAME = 160;
+
+/**
+ * Error code indicating that the username is missing or empty.
+ * @property USERNAME_MISSING
+ * @static
+ * @final
+ */
+ParseError.USERNAME_MISSING = 200;
+
+/**
+ * Error code indicating that the password is missing or empty.
+ * @property PASSWORD_MISSING
+ * @static
+ * @final
+ */
+ParseError.PASSWORD_MISSING = 201;
+
+/**
+ * Error code indicating that the username has already been taken.
+ * @property USERNAME_TAKEN
+ * @static
+ * @final
+ */
+ParseError.USERNAME_TAKEN = 202;
+
+/**
+ * Error code indicating that the email has already been taken.
+ * @property EMAIL_TAKEN
+ * @static
+ * @final
+ */
+ParseError.EMAIL_TAKEN = 203;
+
+/**
+ * Error code indicating that the email is missing, but must be specified.
+ * @property EMAIL_MISSING
+ * @static
+ * @final
+ */
+ParseError.EMAIL_MISSING = 204;
+
+/**
+ * Error code indicating that a user with the specified email was not found.
+ * @property EMAIL_NOT_FOUND
+ * @static
+ * @final
+ */
+ParseError.EMAIL_NOT_FOUND = 205;
+
+/**
+ * Error code indicating that a user object without a valid session could
+ * not be altered.
+ * @property SESSION_MISSING
+ * @static
+ * @final
+ */
+ParseError.SESSION_MISSING = 206;
+
+/**
+ * Error code indicating that a user can only be created through signup.
+ * @property MUST_CREATE_USER_THROUGH_SIGNUP
+ * @static
+ * @final
+ */
+ParseError.MUST_CREATE_USER_THROUGH_SIGNUP = 207;
+
+/**
+ * Error code indicating that an an account being linked is already linked
+ * to another user.
+ * @property ACCOUNT_ALREADY_LINKED
+ * @static
+ * @final
+ */
+ParseError.ACCOUNT_ALREADY_LINKED = 208;
+
+/**
+ * Error code indicating that the current session token is invalid.
+ * @property INVALID_SESSION_TOKEN
+ * @static
+ * @final
+ */
+ParseError.INVALID_SESSION_TOKEN = 209;
+
+/**
+ * Error code indicating that a user cannot be linked to an account because
+ * that account's id could not be found.
+ * @property LINKED_ID_MISSING
+ * @static
+ * @final
+ */
+ParseError.LINKED_ID_MISSING = 250;
+
+/**
+ * Error code indicating that a user with a linked (e.g. Facebook) account
+ * has an invalid session.
+ * @property INVALID_LINKED_SESSION
+ * @static
+ * @final
+ */
+ParseError.INVALID_LINKED_SESSION = 251;
+
+/**
+ * Error code indicating that a service being linked (e.g. Facebook or
+ * Twitter) is unsupported.
+ * @property UNSUPPORTED_SERVICE
+ * @static
+ * @final
+ */
+ParseError.UNSUPPORTED_SERVICE = 252;
+
+/**
+ * Error code indicating that there were multiple errors. Aggregate errors
+ * have an "errors" property, which is an array of error objects with more
+ * detail about each error that occurred.
+ * @property AGGREGATE_ERROR
+ * @static
+ * @final
+ */
+ParseError.AGGREGATE_ERROR = 600;
+
+/**
+ * Error code indicating the client was unable to read an input file.
+ * @property FILE_READ_ERROR
+ * @static
+ * @final
+ */
+ParseError.FILE_READ_ERROR = 601;
+
+/**
+ * Error code indicating a real error code is unavailable because
+ * we had to use an XDomainRequest object to allow CORS requests in
+ * Internet Explorer, which strips the body from HTTP responses that have
+ * a non-2XX status code.
+ * @property X_DOMAIN_REQUEST
+ * @static
+ * @final
+ */
+ParseError.X_DOMAIN_REQUEST = 602;
\ No newline at end of file
diff --git a/lib/browser/ParseFile.js b/lib/browser/ParseFile.js
new file mode 100644
index 000000000..48efdb9d8
--- /dev/null
+++ b/lib/browser/ParseFile.js
@@ -0,0 +1,291 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+var dataUriRegexp = /^data:([a-zA-Z]*\/[a-zA-Z+.-]*);(charset=[a-zA-Z0-9\-\/\s]*,)?base64,/;
+
+function b64Digit(number) {
+ if (number < 26) {
+ return String.fromCharCode(65 + number);
+ }
+ if (number < 52) {
+ return String.fromCharCode(97 + (number - 26));
+ }
+ if (number < 62) {
+ return String.fromCharCode(48 + (number - 52));
+ }
+ if (number === 62) {
+ return '+';
+ }
+ if (number === 63) {
+ return '/';
+ }
+ throw new TypeError('Tried to encode large digit ' + number + ' in base64.');
+}
+
+/**
+ * A Parse.File is a local representation of a file that is saved to the Parse
+ * cloud.
+ * @class Parse.File
+ * @constructor
+ * @param name {String} The file's name. This will be prefixed by a unique
+ * value once the file has finished saving. The file name must begin with
+ * an alphanumeric character, and consist of alphanumeric characters,
+ * periods, spaces, underscores, or dashes.
+ * @param data {Array} The data for the file, as either:
+ * 1. an Array of byte value Numbers, or
+ * 2. an Object like { base64: "..." } with a base64-encoded String.
+ * 3. a File object selected with a file upload control. (3) only works
+ * in Firefox 3.6+, Safari 6.0.2+, Chrome 7+, and IE 10+.
+ * For example:
+ * var fileUploadControl = $("#profilePhotoFileUpload")[0];
+ * if (fileUploadControl.files.length > 0) {
+ * var file = fileUploadControl.files[0];
+ * var name = "photo.jpg";
+ * var parseFile = new Parse.File(name, file);
+ * parseFile.save().then(function() {
+ * // The file has been saved to Parse.
+ * }, function(error) {
+ * // The file either could not be read, or could not be saved to Parse.
+ * });
+ * }
+ * @param type {String} Optional Content-Type header to use for the file. If
+ * this is omitted, the content type will be inferred from the name's
+ * extension.
+ */
+
+var ParseFile = function () {
+ function ParseFile(name, data, type) {
+ (0, _classCallCheck3.default)(this, ParseFile);
+
+ var specifiedType = type || '';
+
+ this._name = name;
+
+ if (data !== undefined) {
+ if (Array.isArray(data)) {
+ this._source = {
+ format: 'base64',
+ base64: ParseFile.encodeBase64(data),
+ type: specifiedType
+ };
+ } else if (typeof File !== 'undefined' && data instanceof File) {
+ this._source = {
+ format: 'file',
+ file: data,
+ type: specifiedType
+ };
+ } else if (data && typeof data.base64 === 'string') {
+ var _base = data.base64;
+ var commaIndex = _base.indexOf(',');
+
+ if (commaIndex !== -1) {
+ var matches = dataUriRegexp.exec(_base.slice(0, commaIndex + 1));
+ // if data URI with type and charset, there will be 4 matches.
+ this._source = {
+ format: 'base64',
+ base64: _base.slice(commaIndex + 1),
+ type: matches[1]
+ };
+ } else {
+ this._source = {
+ format: 'base64',
+ base64: _base,
+ type: specifiedType
+ };
+ }
+ } else {
+ throw new TypeError('Cannot create a Parse.File with that data.');
+ }
+ }
+ }
+
+ /**
+ * Gets the name of the file. Before save is called, this is the filename
+ * given by the user. After save is called, that name gets prefixed with a
+ * unique identifier.
+ * @method name
+ * @return {String}
+ */
+
+ (0, _createClass3.default)(ParseFile, [{
+ key: 'name',
+ value: function () {
+ return this._name;
+ }
+
+ /**
+ * Gets the url of the file. It is only available after you save the file or
+ * after you get the file from a Parse.Object.
+ * @method url
+ * @param {Object} options An object to specify url options
+ * @return {String}
+ */
+
+ }, {
+ key: 'url',
+ value: function (options) {
+ options = options || {};
+ if (!this._url) {
+ return;
+ }
+ if (options.forceSecure) {
+ return this._url.replace(/^http:\/\//i, 'https://');
+ } else {
+ return this._url;
+ }
+ }
+
+ /**
+ * Saves the file to the Parse cloud.
+ * @method save
+ * @param {Object} options A Backbone-style options object.
+ * @return {Parse.Promise} Promise that is resolved when the save finishes.
+ */
+
+ }, {
+ key: 'save',
+ value: function (options) {
+ var _this = this;
+
+ options = options || {};
+ var controller = _CoreManager2.default.getFileController();
+ if (!this._previousSave) {
+ if (this._source.format === 'file') {
+ this._previousSave = controller.saveFile(this._name, this._source).then(function (res) {
+ _this._name = res.name;
+ _this._url = res.url;
+ return _this;
+ });
+ } else {
+ this._previousSave = controller.saveBase64(this._name, this._source).then(function (res) {
+ _this._name = res.name;
+ _this._url = res.url;
+ return _this;
+ });
+ }
+ }
+ if (this._previousSave) {
+ return this._previousSave._thenRunCallbacks(options);
+ }
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return {
+ __type: 'File',
+ name: this._name,
+ url: this._url
+ };
+ }
+ }, {
+ key: 'equals',
+ value: function (other) {
+ if (this === other) {
+ return true;
+ }
+ // Unsaved Files are never equal, since they will be saved to different URLs
+ return other instanceof ParseFile && this.name() === other.name() && this.url() === other.url() && typeof this.url() !== 'undefined';
+ }
+ }], [{
+ key: 'fromJSON',
+ value: function (obj) {
+ if (obj.__type !== 'File') {
+ throw new TypeError('JSON object does not represent a ParseFile');
+ }
+ var file = new ParseFile(obj.name);
+ file._url = obj.url;
+ return file;
+ }
+ }, {
+ key: 'encodeBase64',
+ value: function (bytes) {
+ var chunks = [];
+ chunks.length = Math.ceil(bytes.length / 3);
+ for (var i = 0; i < chunks.length; i++) {
+ var b1 = bytes[i * 3];
+ var b2 = bytes[i * 3 + 1] || 0;
+ var b3 = bytes[i * 3 + 2] || 0;
+
+ var has2 = i * 3 + 1 < bytes.length;
+ var has3 = i * 3 + 2 < bytes.length;
+
+ chunks[i] = [b64Digit(b1 >> 2 & 0x3F), b64Digit(b1 << 4 & 0x30 | b2 >> 4 & 0x0F), has2 ? b64Digit(b2 << 2 & 0x3C | b3 >> 6 & 0x03) : '=', has3 ? b64Digit(b3 & 0x3F) : '='].join('');
+ }
+
+ return chunks.join('');
+ }
+ }]);
+ return ParseFile;
+}();
+
+exports.default = ParseFile;
+
+var DefaultController = {
+ saveFile: function (name, source) {
+ if (source.format !== 'file') {
+ throw new Error('saveFile can only be used with File-type sources.');
+ }
+ // To directly upload a File, we use a REST-style AJAX request
+ var headers = {
+ 'X-Parse-Application-ID': _CoreManager2.default.get('APPLICATION_ID'),
+ 'X-Parse-JavaScript-Key': _CoreManager2.default.get('JAVASCRIPT_KEY'),
+ 'Content-Type': source.type || (source.file ? source.file.type : null)
+ };
+ var url = _CoreManager2.default.get('SERVER_URL');
+ if (url[url.length - 1] !== '/') {
+ url += '/';
+ }
+ url += 'files/' + name;
+ return _CoreManager2.default.getRESTController().ajax('POST', url, source.file, headers);
+ },
+
+ saveBase64: function (name, source) {
+ if (source.format !== 'base64') {
+ throw new Error('saveBase64 can only be used with Base64-type sources.');
+ }
+ var data = {
+ base64: source.base64
+ };
+ if (source.type) {
+ data._ContentType = source.type;
+ }
+
+ return _CoreManager2.default.getRESTController().request('POST', 'files/' + name, data);
+ }
+};
+
+_CoreManager2.default.setFileController(DefaultController);
\ No newline at end of file
diff --git a/lib/browser/ParseGeoPoint.js b/lib/browser/ParseGeoPoint.js
new file mode 100644
index 000000000..98691f85b
--- /dev/null
+++ b/lib/browser/ParseGeoPoint.js
@@ -0,0 +1,235 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Creates a new GeoPoint with any of the following forms:
+ * new GeoPoint(otherGeoPoint)
+ * new GeoPoint(30, 30)
+ * new GeoPoint([30, 30])
+ * new GeoPoint({latitude: 30, longitude: 30})
+ * new GeoPoint() // defaults to (0, 0)
+ *
+ * @class Parse.GeoPoint
+ * @constructor
+ *
+ * Represents a latitude / longitude point that may be associated + * with a key in a ParseObject or used as a reference point for geo queries. + * This allows proximity-based queries on the key.
+ * + *Only one key in a class may contain a GeoPoint.
+ * + *Example:
+ * var point = new Parse.GeoPoint(30.0, -20.0);
+ * var object = new Parse.Object("PlaceObject");
+ * object.set("location", point);
+ * object.save();
+ */
+var ParseGeoPoint = function () {
+ function ParseGeoPoint(arg1, arg2) {
+ (0, _classCallCheck3.default)(this, ParseGeoPoint);
+
+ if (Array.isArray(arg1)) {
+ ParseGeoPoint._validate(arg1[0], arg1[1]);
+ this._latitude = arg1[0];
+ this._longitude = arg1[1];
+ } else if ((typeof arg1 === 'undefined' ? 'undefined' : (0, _typeof3.default)(arg1)) === 'object') {
+ ParseGeoPoint._validate(arg1.latitude, arg1.longitude);
+ this._latitude = arg1.latitude;
+ this._longitude = arg1.longitude;
+ } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
+ ParseGeoPoint._validate(arg1, arg2);
+ this._latitude = arg1;
+ this._longitude = arg2;
+ } else {
+ this._latitude = 0;
+ this._longitude = 0;
+ }
+ }
+
+ /**
+ * North-south portion of the coordinate, in range [-90, 90].
+ * Throws an exception if set out of range in a modern browser.
+ * @property latitude
+ * @type Number
+ */
+
+ (0, _createClass3.default)(ParseGeoPoint, [{
+ key: 'toJSON',
+
+ /**
+ * Returns a JSON representation of the GeoPoint, suitable for Parse.
+ * @method toJSON
+ * @return {Object}
+ */
+ value: function () {
+ ParseGeoPoint._validate(this._latitude, this._longitude);
+ return {
+ __type: 'GeoPoint',
+ latitude: this._latitude,
+ longitude: this._longitude
+ };
+ }
+ }, {
+ key: 'equals',
+ value: function (other) {
+ return other instanceof ParseGeoPoint && this.latitude === other.latitude && this.longitude === other.longitude;
+ }
+
+ /**
+ * Returns the distance from this GeoPoint to another in radians.
+ * @method radiansTo
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+
+ }, {
+ key: 'radiansTo',
+ value: function (point) {
+ var d2r = Math.PI / 180.0;
+ var lat1rad = this.latitude * d2r;
+ var long1rad = this.longitude * d2r;
+ var lat2rad = point.latitude * d2r;
+ var long2rad = point.longitude * d2r;
+
+ var sinDeltaLatDiv2 = Math.sin((lat1rad - lat2rad) / 2);
+ var sinDeltaLongDiv2 = Math.sin((long1rad - long2rad) / 2);
+ // Square of half the straight line chord distance between both points.
+ var a = sinDeltaLatDiv2 * sinDeltaLatDiv2 + Math.cos(lat1rad) * Math.cos(lat2rad) * sinDeltaLongDiv2 * sinDeltaLongDiv2;
+ a = Math.min(1.0, a);
+ return 2 * Math.asin(Math.sqrt(a));
+ }
+
+ /**
+ * Returns the distance from this GeoPoint to another in kilometers.
+ * @method kilometersTo
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+
+ }, {
+ key: 'kilometersTo',
+ value: function (point) {
+ return this.radiansTo(point) * 6371.0;
+ }
+
+ /**
+ * Returns the distance from this GeoPoint to another in miles.
+ * @method milesTo
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+
+ }, {
+ key: 'milesTo',
+ value: function (point) {
+ return this.radiansTo(point) * 3958.8;
+ }
+
+ /**
+ * Throws an exception if the given lat-long is out of bounds.
+ */
+
+ }, {
+ key: 'latitude',
+ get: function () {
+ return this._latitude;
+ },
+ set: function (val) {
+ ParseGeoPoint._validate(val, this.longitude);
+ this._latitude = val;
+ }
+
+ /**
+ * East-west portion of the coordinate, in range [-180, 180].
+ * Throws if set out of range in a modern browser.
+ * @property longitude
+ * @type Number
+ */
+
+ }, {
+ key: 'longitude',
+ get: function () {
+ return this._longitude;
+ },
+ set: function (val) {
+ ParseGeoPoint._validate(this.latitude, val);
+ this._longitude = val;
+ }
+ }], [{
+ key: '_validate',
+ value: function (latitude, longitude) {
+ if (latitude !== latitude || longitude !== longitude) {
+ throw new TypeError('GeoPoint latitude and longitude must be valid numbers');
+ }
+ if (latitude < -90.0) {
+ throw new TypeError('GeoPoint latitude out of bounds: ' + latitude + ' < -90.0.');
+ }
+ if (latitude > 90.0) {
+ throw new TypeError('GeoPoint latitude out of bounds: ' + latitude + ' > 90.0.');
+ }
+ if (longitude < -180.0) {
+ throw new TypeError('GeoPoint longitude out of bounds: ' + longitude + ' < -180.0.');
+ }
+ if (longitude > 180.0) {
+ throw new TypeError('GeoPoint longitude out of bounds: ' + longitude + ' > 180.0.');
+ }
+ }
+
+ /**
+ * Creates a GeoPoint with the user's current location, if available.
+ * Calls options.success with a new GeoPoint instance or calls options.error.
+ * @method current
+ * @param {Object} options An object with success and error callbacks.
+ * @static
+ */
+
+ }, {
+ key: 'current',
+ value: function (options) {
+ var promise = new _ParsePromise2.default();
+ navigator.geolocation.getCurrentPosition(function (location) {
+ promise.resolve(new ParseGeoPoint(location.coords.latitude, location.coords.longitude));
+ }, function (error) {
+ promise.reject(error);
+ });
+
+ return promise._thenRunCallbacks(options);
+ }
+ }]);
+ return ParseGeoPoint;
+}(); /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+exports.default = ParseGeoPoint;
\ No newline at end of file
diff --git a/lib/browser/ParseHooks.js b/lib/browser/ParseHooks.js
new file mode 100644
index 000000000..a2057e899
--- /dev/null
+++ b/lib/browser/ParseHooks.js
@@ -0,0 +1,162 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _promise = require('babel-runtime/core-js/promise');
+
+var _promise2 = _interopRequireDefault(_promise);
+
+exports.getFunctions = getFunctions;
+exports.getTriggers = getTriggers;
+exports.getFunction = getFunction;
+exports.getTrigger = getTrigger;
+exports.createFunction = createFunction;
+exports.createTrigger = createTrigger;
+exports.create = create;
+exports.updateFunction = updateFunction;
+exports.updateTrigger = updateTrigger;
+exports.update = update;
+exports.removeFunction = removeFunction;
+exports.removeTrigger = removeTrigger;
+exports.remove = remove;
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _decode = require('./decode');
+
+var _decode2 = _interopRequireDefault(_decode);
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _ParseError = require('./ParseError');
+
+var _ParseError2 = _interopRequireDefault(_ParseError);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+function getFunctions() {
+ return _CoreManager2.default.getHooksController().get("functions");
+}
+
+function getTriggers() {
+ return _CoreManager2.default.getHooksController().get("triggers");
+}
+
+function getFunction(name) {
+ return _CoreManager2.default.getHooksController().get("functions", name);
+}
+
+function getTrigger(className, triggerName) {
+ return _CoreManager2.default.getHooksController().get("triggers", className, triggerName);
+}
+
+function createFunction(functionName, url) {
+ return create({ functionName: functionName, url: url });
+}
+
+function createTrigger(className, triggerName, url) {
+ return create({ className: className, triggerName: triggerName, url: url });
+}
+
+function create(hook) {
+ return _CoreManager2.default.getHooksController().create(hook);
+}
+
+function updateFunction(functionName, url) {
+ return update({ functionName: functionName, url: url });
+}
+
+function updateTrigger(className, triggerName, url) {
+ return update({ className: className, triggerName: triggerName, url: url });
+}
+
+function update(hook) {
+ return _CoreManager2.default.getHooksController().update(hook);
+}
+
+function removeFunction(functionName) {
+ return remove({ functionName: functionName });
+}
+
+function removeTrigger(className, triggerName) {
+ return remove({ className: className, triggerName: triggerName });
+}
+
+function remove(hook) {
+ return _CoreManager2.default.getHooksController().remove(hook);
+}
+
+var DefaultController = {
+ get: function (type, functionName, triggerName) {
+ var url = "/hooks/" + type;
+ if (functionName) {
+ url += "/" + functionName;
+ if (triggerName) {
+ url += "/" + triggerName;
+ }
+ }
+ return this.sendRequest("GET", url);
+ },
+ create: function (hook) {
+ var url;
+ if (hook.functionName && hook.url) {
+ url = "/hooks/functions";
+ } else if (hook.className && hook.triggerName && hook.url) {
+ url = "/hooks/triggers";
+ } else {
+ return _promise2.default.reject({ error: 'invalid hook declaration', code: 143 });
+ }
+ return this.sendRequest("POST", url, hook);
+ },
+ remove: function (hook) {
+ var url;
+ if (hook.functionName) {
+ url = "/hooks/functions/" + hook.functionName;
+ delete hook.functionName;
+ } else if (hook.className && hook.triggerName) {
+ url = "/hooks/triggers/" + hook.className + "/" + hook.triggerName;
+ delete hook.className;
+ delete hook.triggerName;
+ } else {
+ return _promise2.default.reject({ error: 'invalid hook declaration', code: 143 });
+ }
+ return this.sendRequest("PUT", url, { "__op": "Delete" });
+ },
+ update: function (hook) {
+ var url;
+ if (hook.functionName && hook.url) {
+ url = "/hooks/functions/" + hook.functionName;
+ delete hook.functionName;
+ } else if (hook.className && hook.triggerName && hook.url) {
+ url = "/hooks/triggers/" + hook.className + "/" + hook.triggerName;
+ delete hook.className;
+ delete hook.triggerName;
+ } else {
+ return _promise2.default.reject({ error: 'invalid hook declaration', code: 143 });
+ }
+ return this.sendRequest('PUT', url, hook);
+ },
+ sendRequest: function (method, url, body) {
+ return _CoreManager2.default.getRESTController().request(method, url, body, { useMasterKey: true }).then(function (res) {
+ var decoded = (0, _decode2.default)(res);
+ if (decoded) {
+ return _ParsePromise2.default.as(decoded);
+ }
+ return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.INVALID_JSON, 'The server returned an invalid response.'));
+ });
+ }
+};
+
+_CoreManager2.default.setHooksController(DefaultController);
\ No newline at end of file
diff --git a/lib/browser/ParseInstallation.js b/lib/browser/ParseInstallation.js
new file mode 100644
index 000000000..02d7be1d8
--- /dev/null
+++ b/lib/browser/ParseInstallation.js
@@ -0,0 +1,65 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _ParseObject2 = require('./ParseObject');
+
+var _ParseObject3 = _interopRequireDefault(_ParseObject2);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+var Installation = function (_ParseObject) {
+ (0, _inherits3.default)(Installation, _ParseObject);
+
+ function Installation(attributes) {
+ (0, _classCallCheck3.default)(this, Installation);
+
+ var _this = (0, _possibleConstructorReturn3.default)(this, (Installation.__proto__ || (0, _getPrototypeOf2.default)(Installation)).call(this, '_Installation'));
+
+ if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') {
+ if (!_this.set(attributes || {})) {
+ throw new Error('Can\'t create an invalid Session');
+ }
+ }
+ return _this;
+ }
+
+ return Installation;
+}(_ParseObject3.default); /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+exports.default = Installation;
+
+_ParseObject3.default.registerSubclass('_Installation', Installation);
\ No newline at end of file
diff --git a/lib/browser/ParseLiveQuery.js b/lib/browser/ParseLiveQuery.js
new file mode 100644
index 000000000..a3e5fe31d
--- /dev/null
+++ b/lib/browser/ParseLiveQuery.js
@@ -0,0 +1,241 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _EventEmitter = require('./EventEmitter');
+
+var _EventEmitter2 = _interopRequireDefault(_EventEmitter);
+
+var _LiveQueryClient = require('./LiveQueryClient');
+
+var _LiveQueryClient2 = _interopRequireDefault(_LiveQueryClient);
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function open() {
+ var LiveQueryController = _CoreManager2.default.getLiveQueryController();
+ LiveQueryController.open();
+}
+
+function close() {
+ var LiveQueryController = _CoreManager2.default.getLiveQueryController();
+ LiveQueryController.close();
+}
+
+/**
+ *
+ * We expose three events to help you monitor the status of the WebSocket connection:
+ *
+ * Open - When we establish the WebSocket connection to the LiveQuery server, you'll get this event. + * + *
+ * Parse.LiveQuery.on('open', () => {
+ *
+ * });
+ *
+ * Close - When we lose the WebSocket connection to the LiveQuery server, you'll get this event. + * + *
+ * Parse.LiveQuery.on('close', () => {
+ *
+ * });
+ *
+ * Error - When some network error or LiveQuery server error happens, you'll get this event. + * + *
+ * Parse.LiveQuery.on('error', (error) => {
+ *
+ * });
+ *
+ * @class Parse.LiveQuery
+ * @static
+ *
+ */
+var LiveQuery = new _EventEmitter2.default();
+
+/**
+ * After open is called, the LiveQuery will try to send a connect request
+ * to the LiveQuery server.
+ *
+ * @method open
+ */
+LiveQuery.open = open;
+
+/**
+ * When you're done using LiveQuery, you can call Parse.LiveQuery.close().
+ * This function will close the WebSocket connection to the LiveQuery server,
+ * cancel the auto reconnect, and unsubscribe all subscriptions based on it.
+ * If you call query.subscribe() after this, we'll create a new WebSocket
+ * connection to the LiveQuery server.
+ *
+ * @method close
+ */
+
+LiveQuery.close = close;
+// Register a default onError callback to make sure we do not crash on error
+LiveQuery.on('error', function () {});
+
+exports.default = LiveQuery;
+
+function getSessionToken() {
+ var controller = _CoreManager2.default.getUserController();
+ return controller.currentUserAsync().then(function (currentUser) {
+ return currentUser ? currentUser.getSessionToken() : undefined;
+ });
+}
+
+function getLiveQueryClient() {
+ return _CoreManager2.default.getLiveQueryController().getDefaultLiveQueryClient();
+}
+
+var defaultLiveQueryClient = void 0;
+var DefaultLiveQueryController = {
+ setDefaultLiveQueryClient: function (liveQueryClient) {
+ defaultLiveQueryClient = liveQueryClient;
+ },
+ getDefaultLiveQueryClient: function () {
+ if (defaultLiveQueryClient) {
+ return _ParsePromise2.default.as(defaultLiveQueryClient);
+ }
+
+ return getSessionToken().then(function (sessionToken) {
+ var liveQueryServerURL = _CoreManager2.default.get('LIVEQUERY_SERVER_URL');
+
+ if (liveQueryServerURL && liveQueryServerURL.indexOf('ws') !== 0) {
+ throw new Error('You need to set a proper Parse LiveQuery server url before using LiveQueryClient');
+ }
+
+ // If we can not find Parse.liveQueryServerURL, we try to extract it from Parse.serverURL
+ if (!liveQueryServerURL) {
+ var tempServerURL = _CoreManager2.default.get('SERVER_URL');
+ var protocol = 'ws://';
+ // If Parse is being served over SSL/HTTPS, ensure LiveQuery Server uses 'wss://' prefix
+ if (tempServerURL.indexOf('https') === 0) {
+ protocol = 'wss://';
+ }
+ var host = tempServerURL.replace(/^https?:\/\//, '');
+ liveQueryServerURL = protocol + host;
+ _CoreManager2.default.set('LIVEQUERY_SERVER_URL', liveQueryServerURL);
+ }
+
+ var applicationId = _CoreManager2.default.get('APPLICATION_ID');
+ var javascriptKey = _CoreManager2.default.get('JAVASCRIPT_KEY');
+ var masterKey = _CoreManager2.default.get('MASTER_KEY');
+ // Get currentUser sessionToken if possible
+ defaultLiveQueryClient = new _LiveQueryClient2.default({
+ applicationId: applicationId,
+ serverURL: liveQueryServerURL,
+ javascriptKey: javascriptKey,
+ masterKey: masterKey,
+ sessionToken: sessionToken
+ });
+ // Register a default onError callback to make sure we do not crash on error
+ // Cannot create these events on a nested way because of EventEmiiter from React Native
+ defaultLiveQueryClient.on('error', function (error) {
+ LiveQuery.emit('error', error);
+ });
+ defaultLiveQueryClient.on('open', function () {
+ LiveQuery.emit('open');
+ });
+ defaultLiveQueryClient.on('close', function () {
+ LiveQuery.emit('close');
+ });
+
+ return defaultLiveQueryClient;
+ });
+ },
+ open: function () {
+ var _this = this;
+
+ getLiveQueryClient().then(function (liveQueryClient) {
+ _this.resolve(liveQueryClient.open());
+ });
+ },
+ close: function () {
+ var _this2 = this;
+
+ getLiveQueryClient().then(function (liveQueryClient) {
+ _this2.resolve(liveQueryClient.close());
+ });
+ },
+ subscribe: function (query) {
+ var _this3 = this;
+
+ var subscriptionWrap = new _EventEmitter2.default();
+
+ getLiveQueryClient().then(function (liveQueryClient) {
+ if (liveQueryClient.shouldOpen()) {
+ liveQueryClient.open();
+ }
+ var promiseSessionToken = getSessionToken();
+ // new event emitter
+ return promiseSessionToken.then(function (sessionToken) {
+
+ var subscription = liveQueryClient.subscribe(query, sessionToken);
+ // enter, leave create, etc
+
+ subscriptionWrap.id = subscription.id;
+ subscriptionWrap.query = subscription.query;
+ subscriptionWrap.sessionToken = subscription.sessionToken;
+ subscriptionWrap.unsubscribe = subscription.unsubscribe;
+ // Cannot create these events on a nested way because of EventEmiiter from React Native
+ subscription.on('open', function () {
+ subscriptionWrap.emit('open');
+ });
+ subscription.on('create', function (object) {
+ subscriptionWrap.emit('create', object);
+ });
+ subscription.on('update', function (object) {
+ subscriptionWrap.emit('update', object);
+ });
+ subscription.on('enter', function (object) {
+ subscriptionWrap.emit('enter', object);
+ });
+ subscription.on('leave', function (object) {
+ subscriptionWrap.emit('leave', object);
+ });
+ subscription.on('delete', function (object) {
+ subscriptionWrap.emit('delete', object);
+ });
+
+ _this3.resolve();
+ });
+ });
+ return subscriptionWrap;
+ },
+ unsubscribe: function (subscription) {
+ var _this4 = this;
+
+ getLiveQueryClient().then(function (liveQueryClient) {
+ _this4.resolve(liveQueryClient.unsubscribe(subscription));
+ });
+ },
+ _clearCachedDefaultClient: function () {
+ defaultLiveQueryClient = null;
+ }
+};
+
+_CoreManager2.default.setLiveQueryController(DefaultLiveQueryController);
\ No newline at end of file
diff --git a/lib/browser/ParseObject.js b/lib/browser/ParseObject.js
new file mode 100644
index 000000000..f08795e52
--- /dev/null
+++ b/lib/browser/ParseObject.js
@@ -0,0 +1,2001 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _defineProperty = require('babel-runtime/core-js/object/define-property');
+
+var _defineProperty2 = _interopRequireDefault(_defineProperty);
+
+var _create = require('babel-runtime/core-js/object/create');
+
+var _create2 = _interopRequireDefault(_create);
+
+var _freeze = require('babel-runtime/core-js/object/freeze');
+
+var _freeze2 = _interopRequireDefault(_freeze);
+
+var _stringify = require('babel-runtime/core-js/json/stringify');
+
+var _stringify2 = _interopRequireDefault(_stringify);
+
+var _keys = require('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _canBeSerialized = require('./canBeSerialized');
+
+var _canBeSerialized2 = _interopRequireDefault(_canBeSerialized);
+
+var _decode = require('./decode');
+
+var _decode2 = _interopRequireDefault(_decode);
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _equals = require('./equals');
+
+var _equals2 = _interopRequireDefault(_equals);
+
+var _escape2 = require('./escape');
+
+var _escape3 = _interopRequireDefault(_escape2);
+
+var _ParseACL = require('./ParseACL');
+
+var _ParseACL2 = _interopRequireDefault(_ParseACL);
+
+var _parseDate = require('./parseDate');
+
+var _parseDate2 = _interopRequireDefault(_parseDate);
+
+var _ParseError = require('./ParseError');
+
+var _ParseError2 = _interopRequireDefault(_ParseError);
+
+var _ParseFile = require('./ParseFile');
+
+var _ParseFile2 = _interopRequireDefault(_ParseFile);
+
+var _ParseOp = require('./ParseOp');
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+var _ParseQuery = require('./ParseQuery');
+
+var _ParseQuery2 = _interopRequireDefault(_ParseQuery);
+
+var _ParseRelation = require('./ParseRelation');
+
+var _ParseRelation2 = _interopRequireDefault(_ParseRelation);
+
+var _SingleInstanceStateController = require('./SingleInstanceStateController');
+
+var SingleInstanceStateController = _interopRequireWildcard(_SingleInstanceStateController);
+
+var _unique = require('./unique');
+
+var _unique2 = _interopRequireDefault(_unique);
+
+var _UniqueInstanceStateController = require('./UniqueInstanceStateController');
+
+var UniqueInstanceStateController = _interopRequireWildcard(_UniqueInstanceStateController);
+
+var _unsavedChildren = require('./unsavedChildren');
+
+var _unsavedChildren2 = _interopRequireDefault(_unsavedChildren);
+
+function _interopRequireWildcard(obj) {
+ if (obj && obj.__esModule) {
+ return obj;
+ } else {
+ var newObj = {};if (obj != null) {
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];
+ }
+ }newObj.default = obj;return newObj;
+ }
+}
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+// Mapping of class names to constructors, so we can populate objects from the
+// server with appropriate subclasses of ParseObject
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+var classMap = {};
+
+// Global counter for generating unique local Ids
+var localCount = 0;
+// Global counter for generating unique Ids for non-single-instance objects
+var objectCount = 0;
+// On web clients, objects are single-instance: any two objects with the same Id
+// will have the same attributes. However, this may be dangerous default
+// behavior in a server scenario
+var singleInstance = !_CoreManager2.default.get('IS_NODE');
+if (singleInstance) {
+ _CoreManager2.default.setObjectStateController(SingleInstanceStateController);
+} else {
+ _CoreManager2.default.setObjectStateController(UniqueInstanceStateController);
+}
+
+function getServerUrlPath() {
+ var serverUrl = _CoreManager2.default.get('SERVER_URL');
+ if (serverUrl[serverUrl.length - 1] !== '/') {
+ serverUrl += '/';
+ }
+ var url = serverUrl.replace(/https?:\/\//, '');
+ return url.substr(url.indexOf('/'));
+}
+
+/**
+ * Creates a new model with defined attributes.
+ *
+ * You won't normally call this method directly. It is recommended that
+ * you use a subclass of Parse.Object instead, created by calling
+ * extend.
However, if you don't want to use a subclass, or aren't sure which + * subclass is appropriate, you can use this form:
+ * var object = new Parse.Object("ClassName");
+ *
+ * That is basically equivalent to:
+ * var MyClass = Parse.Object.extend("ClassName");
+ * var object = new MyClass();
+ *
+ *
+ * @class Parse.Object
+ * @constructor
+ * @param {String} className The class name for the object
+ * @param {Object} attributes The initial set of data to store in the object.
+ * @param {Object} options The options for this object instance.
+ */
+
+var ParseObject = function () {
+ /**
+ * The ID of this object, unique within its class.
+ * @property id
+ * @type String
+ */
+ function ParseObject(className, attributes, options) {
+ (0, _classCallCheck3.default)(this, ParseObject);
+
+ // Enable legacy initializers
+ if (typeof this.initialize === 'function') {
+ this.initialize.apply(this, arguments);
+ }
+
+ var toSet = null;
+ this._objCount = objectCount++;
+ if (typeof className === 'string') {
+ this.className = className;
+ if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') {
+ toSet = attributes;
+ }
+ } else if (className && (typeof className === 'undefined' ? 'undefined' : (0, _typeof3.default)(className)) === 'object') {
+ this.className = className.className;
+ toSet = {};
+ for (var attr in className) {
+ if (attr !== 'className') {
+ toSet[attr] = className[attr];
+ }
+ }
+ if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') {
+ options = attributes;
+ }
+ }
+ if (toSet && !this.set(toSet, options)) {
+ throw new Error('Can\'t create an invalid Parse Object');
+ }
+ }
+
+ /** Prototype getters / setters **/
+
+ (0, _createClass3.default)(ParseObject, [{
+ key: '_getId',
+
+ /** Private methods **/
+
+ /**
+ * Returns a local or server Id used uniquely identify this object
+ */
+ value: function () {
+ if (typeof this.id === 'string') {
+ return this.id;
+ }
+ if (typeof this._localId === 'string') {
+ return this._localId;
+ }
+ var localId = 'local' + String(localCount++);
+ this._localId = localId;
+ return localId;
+ }
+
+ /**
+ * Returns a unique identifier used to pull data from the State Controller.
+ */
+
+ }, {
+ key: '_getStateIdentifier',
+ value: function () {
+ if (singleInstance) {
+ var _id = this.id;
+ if (!_id) {
+ _id = this._getId();
+ }
+ return {
+ id: _id,
+ className: this.className
+ };
+ } else {
+ return this;
+ }
+ }
+ }, {
+ key: '_getServerData',
+ value: function () {
+ var stateController = _CoreManager2.default.getObjectStateController();
+ return stateController.getServerData(this._getStateIdentifier());
+ }
+ }, {
+ key: '_clearServerData',
+ value: function () {
+ var serverData = this._getServerData();
+ var unset = {};
+ for (var attr in serverData) {
+ unset[attr] = undefined;
+ }
+ var stateController = _CoreManager2.default.getObjectStateController();
+ stateController.setServerData(this._getStateIdentifier(), unset);
+ }
+ }, {
+ key: '_getPendingOps',
+ value: function () {
+ var stateController = _CoreManager2.default.getObjectStateController();
+ return stateController.getPendingOps(this._getStateIdentifier());
+ }
+ }, {
+ key: '_clearPendingOps',
+ value: function () {
+ var pending = this._getPendingOps();
+ var latest = pending[pending.length - 1];
+ var keys = (0, _keys2.default)(latest);
+ keys.forEach(function (key) {
+ delete latest[key];
+ });
+ }
+ }, {
+ key: '_getDirtyObjectAttributes',
+ value: function () {
+ var attributes = this.attributes;
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var objectCache = stateController.getObjectCache(this._getStateIdentifier());
+ var dirty = {};
+ for (var attr in attributes) {
+ var val = attributes[attr];
+ if (val && (typeof val === 'undefined' ? 'undefined' : (0, _typeof3.default)(val)) === 'object' && !(val instanceof ParseObject) && !(val instanceof _ParseFile2.default) && !(val instanceof _ParseRelation2.default)) {
+ // Due to the way browsers construct maps, the key order will not change
+ // unless the object is changed
+ try {
+ var json = (0, _encode2.default)(val, false, true);
+ var stringified = (0, _stringify2.default)(json);
+ if (objectCache[attr] !== stringified) {
+ dirty[attr] = val;
+ }
+ } catch (e) {
+ // Error occurred, possibly by a nested unsaved pointer in a mutable container
+ // No matter how it happened, it indicates a change in the attribute
+ dirty[attr] = val;
+ }
+ }
+ }
+ return dirty;
+ }
+ }, {
+ key: '_toFullJSON',
+ value: function (seen) {
+ var json = this.toJSON(seen);
+ json.__type = 'Object';
+ json.className = this.className;
+ return json;
+ }
+ }, {
+ key: '_getSaveJSON',
+ value: function () {
+ var pending = this._getPendingOps();
+ var dirtyObjects = this._getDirtyObjectAttributes();
+ var json = {};
+
+ for (var attr in dirtyObjects) {
+ json[attr] = new _ParseOp.SetOp(dirtyObjects[attr]).toJSON();
+ }
+ for (attr in pending[0]) {
+ json[attr] = pending[0][attr].toJSON();
+ }
+ return json;
+ }
+ }, {
+ key: '_getSaveParams',
+ value: function () {
+ var method = this.id ? 'PUT' : 'POST';
+ var body = this._getSaveJSON();
+ var path = 'classes/' + this.className;
+ if (this.id) {
+ path += '/' + this.id;
+ } else if (this.className === '_User') {
+ path = 'users';
+ }
+ return {
+ method: method,
+ body: body,
+ path: path
+ };
+ }
+ }, {
+ key: '_finishFetch',
+ value: function (serverData) {
+ if (!this.id && serverData.objectId) {
+ this.id = serverData.objectId;
+ }
+ var stateController = _CoreManager2.default.getObjectStateController();
+ stateController.initializeState(this._getStateIdentifier());
+ var decoded = {};
+ for (var attr in serverData) {
+ if (attr === 'ACL') {
+ decoded[attr] = new _ParseACL2.default(serverData[attr]);
+ } else if (attr !== 'objectId') {
+ decoded[attr] = (0, _decode2.default)(serverData[attr]);
+ if (decoded[attr] instanceof _ParseRelation2.default) {
+ decoded[attr]._ensureParentAndKey(this, attr);
+ }
+ }
+ }
+ if (decoded.createdAt && typeof decoded.createdAt === 'string') {
+ decoded.createdAt = (0, _parseDate2.default)(decoded.createdAt);
+ }
+ if (decoded.updatedAt && typeof decoded.updatedAt === 'string') {
+ decoded.updatedAt = (0, _parseDate2.default)(decoded.updatedAt);
+ }
+ if (!decoded.updatedAt && decoded.createdAt) {
+ decoded.updatedAt = decoded.createdAt;
+ }
+ stateController.commitServerChanges(this._getStateIdentifier(), decoded);
+ }
+ }, {
+ key: '_setExisted',
+ value: function (existed) {
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var state = stateController.getState(this._getStateIdentifier());
+ if (state) {
+ state.existed = existed;
+ }
+ }
+ }, {
+ key: '_migrateId',
+ value: function (serverId) {
+ if (this._localId && serverId) {
+ if (singleInstance) {
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var oldState = stateController.removeState(this._getStateIdentifier());
+ this.id = serverId;
+ delete this._localId;
+ if (oldState) {
+ stateController.initializeState(this._getStateIdentifier(), oldState);
+ }
+ } else {
+ this.id = serverId;
+ delete this._localId;
+ }
+ }
+ }
+ }, {
+ key: '_handleSaveResponse',
+ value: function (response, status) {
+ var changes = {};
+
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var pending = stateController.popPendingState(this._getStateIdentifier());
+ for (var attr in pending) {
+ if (pending[attr] instanceof _ParseOp.RelationOp) {
+ changes[attr] = pending[attr].applyTo(undefined, this, attr);
+ } else if (!(attr in response)) {
+ // Only SetOps and UnsetOps should not come back with results
+ changes[attr] = pending[attr].applyTo(undefined);
+ }
+ }
+ for (attr in response) {
+ if ((attr === 'createdAt' || attr === 'updatedAt') && typeof response[attr] === 'string') {
+ changes[attr] = (0, _parseDate2.default)(response[attr]);
+ } else if (attr === 'ACL') {
+ changes[attr] = new _ParseACL2.default(response[attr]);
+ } else if (attr !== 'objectId') {
+ changes[attr] = (0, _decode2.default)(response[attr]);
+ if (changes[attr] instanceof _ParseOp.UnsetOp) {
+ changes[attr] = undefined;
+ }
+ }
+ }
+ if (changes.createdAt && !changes.updatedAt) {
+ changes.updatedAt = changes.createdAt;
+ }
+
+ this._migrateId(response.objectId);
+
+ if (status !== 201) {
+ this._setExisted(true);
+ }
+
+ stateController.commitServerChanges(this._getStateIdentifier(), changes);
+ }
+ }, {
+ key: '_handleSaveError',
+ value: function () {
+ this._getPendingOps();
+
+ var stateController = _CoreManager2.default.getObjectStateController();
+ stateController.mergeFirstPendingState(this._getStateIdentifier());
+ }
+
+ /** Public methods **/
+
+ }, {
+ key: 'initialize',
+ value: function () {}
+ // NOOP
+
+
+ /**
+ * Returns a JSON version of the object suitable for saving to Parse.
+ * @method toJSON
+ * @return {Object}
+ */
+
+ }, {
+ key: 'toJSON',
+ value: function (seen) {
+ var seenEntry = this.id ? this.className + ':' + this.id : this;
+ var seen = seen || [seenEntry];
+ var json = {};
+ var attrs = this.attributes;
+ for (var attr in attrs) {
+ if ((attr === 'createdAt' || attr === 'updatedAt') && attrs[attr].toJSON) {
+ json[attr] = attrs[attr].toJSON();
+ } else {
+ json[attr] = (0, _encode2.default)(attrs[attr], false, false, seen);
+ }
+ }
+ var pending = this._getPendingOps();
+ for (var attr in pending[0]) {
+ json[attr] = pending[0][attr].toJSON();
+ }
+
+ if (this.id) {
+ json.objectId = this.id;
+ }
+ return json;
+ }
+
+ /**
+ * Determines whether this ParseObject is equal to another ParseObject
+ * @method equals
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'equals',
+ value: function (other) {
+ if (this === other) {
+ return true;
+ }
+ return other instanceof ParseObject && this.className === other.className && this.id === other.id && typeof this.id !== 'undefined';
+ }
+
+ /**
+ * Returns true if this object has been modified since its last
+ * save/refresh. If an attribute is specified, it returns true only if that
+ * particular attribute has been modified since the last save/refresh.
+ * @method dirty
+ * @param {String} attr An attribute name (optional).
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'dirty',
+ value: function (attr) {
+ if (!this.id) {
+ return true;
+ }
+ var pendingOps = this._getPendingOps();
+ var dirtyObjects = this._getDirtyObjectAttributes();
+ if (attr) {
+ if (dirtyObjects.hasOwnProperty(attr)) {
+ return true;
+ }
+ for (var i = 0; i < pendingOps.length; i++) {
+ if (pendingOps[i].hasOwnProperty(attr)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ if ((0, _keys2.default)(pendingOps[0]).length !== 0) {
+ return true;
+ }
+ if ((0, _keys2.default)(dirtyObjects).length !== 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns an array of keys that have been modified since last save/refresh
+ * @method dirtyKeys
+ * @return {Array of string}
+ */
+
+ }, {
+ key: 'dirtyKeys',
+ value: function () {
+ var pendingOps = this._getPendingOps();
+ var keys = {};
+ for (var i = 0; i < pendingOps.length; i++) {
+ for (var attr in pendingOps[i]) {
+ keys[attr] = true;
+ }
+ }
+ var dirtyObjects = this._getDirtyObjectAttributes();
+ for (var attr in dirtyObjects) {
+ keys[attr] = true;
+ }
+ return (0, _keys2.default)(keys);
+ }
+
+ /**
+ * Gets a Pointer referencing this Object.
+ * @method toPointer
+ * @return {Object}
+ */
+
+ }, {
+ key: 'toPointer',
+ value: function () {
+ if (!this.id) {
+ throw new Error('Cannot create a pointer to an unsaved ParseObject');
+ }
+ return {
+ __type: 'Pointer',
+ className: this.className,
+ objectId: this.id
+ };
+ }
+
+ /**
+ * Gets the value of an attribute.
+ * @method get
+ * @param {String} attr The string name of an attribute.
+ */
+
+ }, {
+ key: 'get',
+ value: function (attr) {
+ return this.attributes[attr];
+ }
+
+ /**
+ * Gets a relation on the given class for the attribute.
+ * @method relation
+ * @param String attr The attribute to get the relation for.
+ */
+
+ }, {
+ key: 'relation',
+ value: function (attr) {
+ var value = this.get(attr);
+ if (value) {
+ if (!(value instanceof _ParseRelation2.default)) {
+ throw new Error('Called relation() on non-relation field ' + attr);
+ }
+ value._ensureParentAndKey(this, attr);
+ return value;
+ }
+ return new _ParseRelation2.default(this, attr);
+ }
+
+ /**
+ * Gets the HTML-escaped value of an attribute.
+ * @method escape
+ * @param {String} attr The string name of an attribute.
+ */
+
+ }, {
+ key: 'escape',
+ value: function (attr) {
+ var val = this.attributes[attr];
+ if (val == null) {
+ return '';
+ }
+
+ if (typeof val !== 'string') {
+ if (typeof val.toString !== 'function') {
+ return '';
+ }
+ val = val.toString();
+ }
+ return (0, _escape3.default)(val);
+ }
+
+ /**
+ * Returns true if the attribute contains a value that is not
+ * null or undefined.
+ * @method has
+ * @param {String} attr The string name of the attribute.
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'has',
+ value: function (attr) {
+ var attributes = this.attributes;
+ if (attributes.hasOwnProperty(attr)) {
+ return attributes[attr] != null;
+ }
+ return false;
+ }
+
+ /**
+ * Sets a hash of model attributes on the object.
+ *
+ * You can call it with an object containing keys and values, or with one + * key and value. For example:
+ * gameTurn.set({
+ * player: player1,
+ * diceRoll: 2
+ * }, {
+ * error: function(gameTurnAgain, error) {
+ * // The set failed validation.
+ * }
+ * });
+ *
+ * game.set("currentPlayer", player2, {
+ * error: function(gameTurnAgain, error) {
+ * // The set failed validation.
+ * }
+ * });
+ *
+ * game.set("finished", true);
+ *
+ * @method set
+ * @param {String} key The key to set.
+ * @param {} value The value to give it.
+ * @param {Object} options A set of options for the set.
+ * The only supported option is error.
+ * @return {Boolean} true if the set succeeded.
+ */
+
+ }, {
+ key: 'set',
+ value: function (key, value, options) {
+ var changes = {};
+ var newOps = {};
+ if (key && (typeof key === 'undefined' ? 'undefined' : (0, _typeof3.default)(key)) === 'object') {
+ changes = key;
+ options = value;
+ } else if (typeof key === 'string') {
+ changes[key] = value;
+ } else {
+ return this;
+ }
+
+ options = options || {};
+ var readonly = [];
+ if (typeof this.constructor.readOnlyAttributes === 'function') {
+ readonly = readonly.concat(this.constructor.readOnlyAttributes());
+ }
+ for (var k in changes) {
+ if (k === 'createdAt' || k === 'updatedAt') {
+ // This property is read-only, but for legacy reasons we silently
+ // ignore it
+ continue;
+ }
+ if (readonly.indexOf(k) > -1) {
+ throw new Error('Cannot modify readonly attribute: ' + k);
+ }
+ if (options.unset) {
+ newOps[k] = new _ParseOp.UnsetOp();
+ } else if (changes[k] instanceof _ParseOp.Op) {
+ newOps[k] = changes[k];
+ } else if (changes[k] && (0, _typeof3.default)(changes[k]) === 'object' && typeof changes[k].__op === 'string') {
+ newOps[k] = (0, _ParseOp.opFromJSON)(changes[k]);
+ } else if (k === 'objectId' || k === 'id') {
+ if (typeof changes[k] === 'string') {
+ this.id = changes[k];
+ }
+ } else if (k === 'ACL' && (0, _typeof3.default)(changes[k]) === 'object' && !(changes[k] instanceof _ParseACL2.default)) {
+ newOps[k] = new _ParseOp.SetOp(new _ParseACL2.default(changes[k]));
+ } else {
+ newOps[k] = new _ParseOp.SetOp(changes[k]);
+ }
+ }
+
+ // Calculate new values
+ var currentAttributes = this.attributes;
+ var newValues = {};
+ for (var attr in newOps) {
+ if (newOps[attr] instanceof _ParseOp.RelationOp) {
+ newValues[attr] = newOps[attr].applyTo(currentAttributes[attr], this, attr);
+ } else if (!(newOps[attr] instanceof _ParseOp.UnsetOp)) {
+ newValues[attr] = newOps[attr].applyTo(currentAttributes[attr]);
+ }
+ }
+
+ // Validate changes
+ if (!options.ignoreValidation) {
+ var validation = this.validate(newValues);
+ if (validation) {
+ if (typeof options.error === 'function') {
+ options.error(this, validation);
+ }
+ return false;
+ }
+ }
+
+ // Consolidate Ops
+ var pendingOps = this._getPendingOps();
+ var last = pendingOps.length - 1;
+ var stateController = _CoreManager2.default.getObjectStateController();
+ for (var attr in newOps) {
+ var nextOp = newOps[attr].mergeWith(pendingOps[last][attr]);
+ stateController.setPendingOp(this._getStateIdentifier(), attr, nextOp);
+ }
+
+ return this;
+ }
+
+ /**
+ * Remove an attribute from the model. This is a noop if the attribute doesn't
+ * exist.
+ * @method unset
+ * @param {String} attr The string name of an attribute.
+ */
+
+ }, {
+ key: 'unset',
+ value: function (attr, options) {
+ options = options || {};
+ options.unset = true;
+ return this.set(attr, null, options);
+ }
+
+ /**
+ * Atomically increments the value of the given attribute the next time the
+ * object is saved. If no amount is specified, 1 is used by default.
+ *
+ * @method increment
+ * @param attr {String} The key.
+ * @param amount {Number} The amount to increment by (optional).
+ */
+
+ }, {
+ key: 'increment',
+ value: function (attr, amount) {
+ if (typeof amount === 'undefined') {
+ amount = 1;
+ }
+ if (typeof amount !== 'number') {
+ throw new Error('Cannot increment by a non-numeric amount.');
+ }
+ return this.set(attr, new _ParseOp.IncrementOp(amount));
+ }
+
+ /**
+ * Atomically add an object to the end of the array associated with a given
+ * key.
+ * @method add
+ * @param attr {String} The key.
+ * @param item {} The item to add.
+ */
+
+ }, {
+ key: 'add',
+ value: function (attr, item) {
+ return this.set(attr, new _ParseOp.AddOp([item]));
+ }
+
+ /**
+ * Atomically add an object to the array associated with a given key, only
+ * if it is not already present in the array. The position of the insert is
+ * not guaranteed.
+ *
+ * @method addUnique
+ * @param attr {String} The key.
+ * @param item {} The object to add.
+ */
+
+ }, {
+ key: 'addUnique',
+ value: function (attr, item) {
+ return this.set(attr, new _ParseOp.AddUniqueOp([item]));
+ }
+
+ /**
+ * Atomically remove all instances of an object from the array associated
+ * with a given key.
+ *
+ * @method remove
+ * @param attr {String} The key.
+ * @param item {} The object to remove.
+ */
+
+ }, {
+ key: 'remove',
+ value: function (attr, item) {
+ return this.set(attr, new _ParseOp.RemoveOp([item]));
+ }
+
+ /**
+ * Returns an instance of a subclass of Parse.Op describing what kind of
+ * modification has been performed on this field since the last time it was
+ * saved. For example, after calling object.increment("x"), calling
+ * object.op("x") would return an instance of Parse.Op.Increment.
+ *
+ * @method op
+ * @param attr {String} The key.
+ * @returns {Parse.Op} The operation, or undefined if none.
+ */
+
+ }, {
+ key: 'op',
+ value: function (attr) {
+ var pending = this._getPendingOps();
+ for (var i = pending.length; i--;) {
+ if (pending[i][attr]) {
+ return pending[i][attr];
+ }
+ }
+ }
+
+ /**
+ * Creates a new model with identical attributes to this one, similar to Backbone.Model's clone()
+ * @method clone
+ * @return {Parse.Object}
+ */
+
+ }, {
+ key: 'clone',
+ value: function () {
+ var clone = new this.constructor();
+ if (!clone.className) {
+ clone.className = this.className;
+ }
+ var attributes = this.attributes;
+ if (typeof this.constructor.readOnlyAttributes === 'function') {
+ var readonly = this.constructor.readOnlyAttributes() || [];
+ // Attributes are frozen, so we have to rebuild an object,
+ // rather than delete readonly keys
+ var copy = {};
+ for (var a in attributes) {
+ if (readonly.indexOf(a) < 0) {
+ copy[a] = attributes[a];
+ }
+ }
+ attributes = copy;
+ }
+ if (clone.set) {
+ clone.set(attributes);
+ }
+ return clone;
+ }
+
+ /**
+ * Creates a new instance of this object. Not to be confused with clone()
+ * @method newInstance
+ * @return {Parse.Object}
+ */
+
+ }, {
+ key: 'newInstance',
+ value: function () {
+ var clone = new this.constructor();
+ if (!clone.className) {
+ clone.className = this.className;
+ }
+ clone.id = this.id;
+ if (singleInstance) {
+ // Just return an object with the right id
+ return clone;
+ }
+
+ var stateController = _CoreManager2.default.getObjectStateController();
+ if (stateController) {
+ stateController.duplicateState(this._getStateIdentifier(), clone._getStateIdentifier());
+ }
+ return clone;
+ }
+
+ /**
+ * Returns true if this object has never been saved to Parse.
+ * @method isNew
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'isNew',
+ value: function () {
+ return !this.id;
+ }
+
+ /**
+ * Returns true if this object was created by the Parse server when the
+ * object might have already been there (e.g. in the case of a Facebook
+ * login)
+ * @method existed
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'existed',
+ value: function () {
+ if (!this.id) {
+ return false;
+ }
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var state = stateController.getState(this._getStateIdentifier());
+ if (state) {
+ return state.existed;
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the model is currently in a valid state.
+ * @method isValid
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'isValid',
+ value: function () {
+ return !this.validate(this.attributes);
+ }
+
+ /**
+ * You should not call this function directly unless you subclass
+ * Parse.Object, in which case you can override this method
+ * to provide additional validation on set and
+ * save. Your implementation should return
+ *
+ * @method validate
+ * @param {Object} attrs The current data to validate.
+ * @return {} False if the data is valid. An error object otherwise.
+ * @see Parse.Object#set
+ */
+
+ }, {
+ key: 'validate',
+ value: function (attrs) {
+ if (attrs.hasOwnProperty('ACL') && !(attrs.ACL instanceof _ParseACL2.default)) {
+ return new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'ACL must be a Parse ACL.');
+ }
+ for (var key in attrs) {
+ if (!/^[A-Za-z][0-9A-Za-z_]*$/.test(key)) {
+ return new _ParseError2.default(_ParseError2.default.INVALID_KEY_NAME);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the ACL for this object.
+ * @method getACL
+ * @returns {Parse.ACL} An instance of Parse.ACL.
+ * @see Parse.Object#get
+ */
+
+ }, {
+ key: 'getACL',
+ value: function () {
+ var acl = this.get('ACL');
+ if (acl instanceof _ParseACL2.default) {
+ return acl;
+ }
+ return null;
+ }
+
+ /**
+ * Sets the ACL to be used for this object.
+ * @method setACL
+ * @param {Parse.ACL} acl An instance of Parse.ACL.
+ * @param {Object} options Optional Backbone-like options object to be
+ * passed in to set.
+ * @return {Boolean} Whether the set passed validation.
+ * @see Parse.Object#set
+ */
+
+ }, {
+ key: 'setACL',
+ value: function (acl, options) {
+ return this.set('ACL', acl, options);
+ }
+
+ /**
+ * Clears any changes to this object made since the last call to save()
+ * @method revert
+ */
+
+ }, {
+ key: 'revert',
+ value: function () {
+ this._clearPendingOps();
+ }
+
+ /**
+ * Clears all attributes on a model
+ * @method clear
+ */
+
+ }, {
+ key: 'clear',
+ value: function () {
+ var attributes = this.attributes;
+ var erasable = {};
+ var readonly = ['createdAt', 'updatedAt'];
+ if (typeof this.constructor.readOnlyAttributes === 'function') {
+ readonly = readonly.concat(this.constructor.readOnlyAttributes());
+ }
+ for (var attr in attributes) {
+ if (readonly.indexOf(attr) < 0) {
+ erasable[attr] = true;
+ }
+ }
+ return this.set(erasable, { unset: true });
+ }
+
+ /**
+ * Fetch the model from the server. If the server's representation of the
+ * model differs from its current attributes, they will be overriden.
+ *
+ * @method fetch
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:+ * object.save();+ * or
+ * object.save(null, options);+ * or
+ * object.save(attrs, options);+ * or
+ * object.save(key, value, options);+ * + * For example,
+ * gameTurn.save({
+ * player: "Jake Cutter",
+ * diceRoll: 2
+ * }, {
+ * success: function(gameTurnAgain) {
+ * // The save was successful.
+ * },
+ * error: function(gameTurnAgain, error) {
+ * // The save failed. Error is an instance of Parse.Error.
+ * }
+ * });
+ * or with promises:
+ * gameTurn.save({
+ * player: "Jake Cutter",
+ * diceRoll: 2
+ * }).then(function(gameTurnAgain) {
+ * // The save was successful.
+ * }, function(error) {
+ * // The save failed. Error is an instance of Parse.Error.
+ * });
+ *
+ * @method save
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:
+ * Parse.Object.fetchAll([object1, object2, ...], {
+ * success: function(list) {
+ * // All the objects were fetched.
+ * },
+ * error: function(error) {
+ * // An error occurred while fetching one of the objects.
+ * },
+ * });
+ *
+ *
+ * @method fetchAll
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:
+ * Parse.Object.fetchAllIfNeeded([object1, ...], {
+ * success: function(list) {
+ * // Objects were fetched and updated.
+ * },
+ * error: function(error) {
+ * // An error occurred while fetching one of the objects.
+ * },
+ * });
+ *
+ *
+ * @method fetchAllIfNeeded
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:Unlike saveAll, if an error occurs while deleting an individual model, + * this method will continue trying to delete the rest of the models if + * possible, except in the case of a fatal error like a connection error. + * + *
In particular, the Parse.Error object returned in the case of error may + * be one of two types: + * + *
+ * Parse.Object.destroyAll([object1, object2, ...], {
+ * success: function() {
+ * // All the objects were deleted.
+ * },
+ * error: function(error) {
+ * // An error occurred while deleting one or more of the objects.
+ * // If this is an aggregate error, then we can inspect each error
+ * // object individually to determine the reason why a particular
+ * // object was not deleted.
+ * if (error.code === Parse.Error.AGGREGATE_ERROR) {
+ * for (var i = 0; i < error.errors.length; i++) {
+ * console.log("Couldn't delete " + error.errors[i].object.id +
+ * "due to " + error.errors[i].message);
+ * }
+ * } else {
+ * console.log("Delete aborted because of " + error.message);
+ * }
+ * },
+ * });
+ *
+ *
+ * @method destroyAll
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:
+ * Parse.Object.saveAll([object1, object2, ...], {
+ * success: function(list) {
+ * // All the objects were saved.
+ * },
+ * error: function(error) {
+ * // An error occurred while saving one of the objects.
+ * },
+ * });
+ *
+ *
+ * @method saveAll
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:A shortcut for:
+ * var Foo = Parse.Object.extend("Foo");
+ * var pointerToFoo = new Foo();
+ * pointerToFoo.id = "myObjectId";
+ *
+ *
+ * @method createWithoutData
+ * @param {String} id The ID of the object to create a reference to.
+ * @static
+ * @return {Parse.Object} A Parse.Object reference.
+ */
+
+ }, {
+ key: 'createWithoutData',
+ value: function (id) {
+ var obj = new this();
+ obj.id = id;
+ return obj;
+ }
+
+ /**
+ * Creates a new instance of a Parse Object from a JSON representation.
+ * @method fromJSON
+ * @param {Object} json The JSON map of the Object's data
+ * @param {boolean} override In single instance mode, all old server data
+ * is overwritten if this is set to true
+ * @static
+ * @return {Parse.Object} A Parse.Object reference
+ */
+
+ }, {
+ key: 'fromJSON',
+ value: function (json, override) {
+ if (!json.className) {
+ throw new Error('Cannot create an object without a className');
+ }
+ var constructor = classMap[json.className];
+ var o = constructor ? new constructor() : new ParseObject(json.className);
+ var otherAttributes = {};
+ for (var attr in json) {
+ if (attr !== 'className' && attr !== '__type') {
+ otherAttributes[attr] = json[attr];
+ }
+ }
+ if (override) {
+ // id needs to be set before clearServerData can work
+ if (otherAttributes.objectId) {
+ o.id = otherAttributes.objectId;
+ }
+ var preserved = null;
+ if (typeof o._preserveFieldsOnFetch === 'function') {
+ preserved = o._preserveFieldsOnFetch();
+ }
+ o._clearServerData();
+ if (preserved) {
+ o._finishFetch(preserved);
+ }
+ }
+ o._finishFetch(otherAttributes);
+ if (json.objectId) {
+ o._setExisted(true);
+ }
+ return o;
+ }
+
+ /**
+ * Registers a subclass of Parse.Object with a specific class name.
+ * When objects of that class are retrieved from a query, they will be
+ * instantiated with this subclass.
+ * This is only necessary when using ES6 subclassing.
+ * @method registerSubclass
+ * @param {String} className The class name of the subclass
+ * @param {Class} constructor The subclass
+ */
+
+ }, {
+ key: 'registerSubclass',
+ value: function (className, constructor) {
+ if (typeof className !== 'string') {
+ throw new TypeError('The first argument must be a valid class name.');
+ }
+ if (typeof constructor === 'undefined') {
+ throw new TypeError('You must supply a subclass constructor.');
+ }
+ if (typeof constructor !== 'function') {
+ throw new TypeError('You must register the subclass constructor. ' + 'Did you attempt to register an instance of the subclass?');
+ }
+ classMap[className] = constructor;
+ if (!constructor.className) {
+ constructor.className = className;
+ }
+ }
+
+ /**
+ * Creates a new subclass of Parse.Object for the given Parse class name.
+ *
+ * Every extension of a Parse class will inherit from the most recent + * previous extension of that class. When a Parse.Object is automatically + * created by parsing JSON, it will use the most recent extension of that + * class.
+ * + *You should call either:
+ * var MyClass = Parse.Object.extend("MyClass", {
+ * Instance methods,
+ * initialize: function(attrs, options) {
+ * this.someInstanceProperty = [],
+ * Other instance properties
+ * }
+ * }, {
+ * Class properties
+ * });
+ * or, for Backbone compatibility:
+ * var MyClass = Parse.Object.extend({
+ * className: "MyClass",
+ * Instance methods,
+ * initialize: function(attrs, options) {
+ * this.someInstanceProperty = [],
+ * Other instance properties
+ * }
+ * }, {
+ * Class properties
+ * });
+ *
+ * @method extend
+ * @param {String} className The name of the Parse class backing this model.
+ * @param {Object} protoProps Instance properties to add to instances of the
+ * class returned from this method.
+ * @param {Object} classProps Class properties to add the class returned from
+ * this method.
+ * @return {Class} A new subclass of Parse.Object.
+ */
+
+ }, {
+ key: 'extend',
+ value: function (className, protoProps, classProps) {
+ if (typeof className !== 'string') {
+ if (className && typeof className.className === 'string') {
+ return ParseObject.extend(className.className, className, protoProps);
+ } else {
+ throw new Error('Parse.Object.extend\'s first argument should be the className.');
+ }
+ }
+ var adjustedClassName = className;
+
+ if (adjustedClassName === 'User' && _CoreManager2.default.get('PERFORM_USER_REWRITE')) {
+ adjustedClassName = '_User';
+ }
+
+ var parentProto = ParseObject.prototype;
+ if (this.hasOwnProperty('__super__') && this.__super__) {
+ parentProto = this.prototype;
+ } else if (classMap[adjustedClassName]) {
+ parentProto = classMap[adjustedClassName].prototype;
+ }
+ var ParseObjectSubclass = function (attributes, options) {
+ this.className = adjustedClassName;
+ this._objCount = objectCount++;
+ // Enable legacy initializers
+ if (typeof this.initialize === 'function') {
+ this.initialize.apply(this, arguments);
+ }
+
+ if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') {
+ if (!this.set(attributes || {}, options)) {
+ throw new Error('Can\'t create an invalid Parse Object');
+ }
+ }
+ };
+ ParseObjectSubclass.className = adjustedClassName;
+ ParseObjectSubclass.__super__ = parentProto;
+
+ ParseObjectSubclass.prototype = (0, _create2.default)(parentProto, {
+ constructor: {
+ value: ParseObjectSubclass,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+ });
+
+ if (protoProps) {
+ for (var prop in protoProps) {
+ if (prop !== 'className') {
+ (0, _defineProperty2.default)(ParseObjectSubclass.prototype, prop, {
+ value: protoProps[prop],
+ enumerable: false,
+ writable: true,
+ configurable: true
+ });
+ }
+ }
+ }
+
+ if (classProps) {
+ for (var prop in classProps) {
+ if (prop !== 'className') {
+ (0, _defineProperty2.default)(ParseObjectSubclass, prop, {
+ value: classProps[prop],
+ enumerable: false,
+ writable: true,
+ configurable: true
+ });
+ }
+ }
+ }
+
+ ParseObjectSubclass.extend = function (name, protoProps, classProps) {
+ if (typeof name === 'string') {
+ return ParseObject.extend.call(ParseObjectSubclass, name, protoProps, classProps);
+ }
+ return ParseObject.extend.call(ParseObjectSubclass, adjustedClassName, name, protoProps);
+ };
+ ParseObjectSubclass.createWithoutData = ParseObject.createWithoutData;
+
+ classMap[adjustedClassName] = ParseObjectSubclass;
+ return ParseObjectSubclass;
+ }
+
+ /**
+ * Enable single instance objects, where any local objects with the same Id
+ * share the same attributes, and stay synchronized with each other.
+ * This is disabled by default in server environments, since it can lead to
+ * security issues.
+ * @method enableSingleInstance
+ */
+
+ }, {
+ key: 'enableSingleInstance',
+ value: function () {
+ singleInstance = true;
+ _CoreManager2.default.setObjectStateController(SingleInstanceStateController);
+ }
+
+ /**
+ * Disable single instance objects, where any local objects with the same Id
+ * share the same attributes, and stay synchronized with each other.
+ * When disabled, you can have two instances of the same object in memory
+ * without them sharing attributes.
+ * @method disableSingleInstance
+ */
+
+ }, {
+ key: 'disableSingleInstance',
+ value: function () {
+ singleInstance = false;
+ _CoreManager2.default.setObjectStateController(UniqueInstanceStateController);
+ }
+ }]);
+ return ParseObject;
+}();
+
+exports.default = ParseObject;
+
+var DefaultController = {
+ fetch: function (target, forceFetch, options) {
+ if (Array.isArray(target)) {
+ if (target.length < 1) {
+ return _ParsePromise2.default.as([]);
+ }
+ var objs = [];
+ var ids = [];
+ var className = null;
+ var results = [];
+ var error = null;
+ target.forEach(function (el, i) {
+ if (error) {
+ return;
+ }
+ if (!className) {
+ className = el.className;
+ }
+ if (className !== el.className) {
+ error = new _ParseError2.default(_ParseError2.default.INVALID_CLASS_NAME, 'All objects should be of the same class');
+ }
+ if (!el.id) {
+ error = new _ParseError2.default(_ParseError2.default.MISSING_OBJECT_ID, 'All objects must have an ID');
+ }
+ if (forceFetch || (0, _keys2.default)(el._getServerData()).length === 0) {
+ ids.push(el.id);
+ objs.push(el);
+ }
+ results.push(el);
+ });
+ if (error) {
+ return _ParsePromise2.default.error(error);
+ }
+ var query = new _ParseQuery2.default(className);
+ query.containedIn('objectId', ids);
+ query._limit = ids.length;
+ return query.find(options).then(function (objects) {
+ var idMap = {};
+ objects.forEach(function (o) {
+ idMap[o.id] = o;
+ });
+ for (var i = 0; i < objs.length; i++) {
+ var obj = objs[i];
+ if (!obj || !obj.id || !idMap[obj.id]) {
+ if (forceFetch) {
+ return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OBJECT_NOT_FOUND, 'All objects must exist on the server.'));
+ }
+ }
+ }
+ if (!singleInstance) {
+ // If single instance objects are disabled, we need to replace the
+ for (var i = 0; i < results.length; i++) {
+ var obj = results[i];
+ if (obj && obj.id && idMap[obj.id]) {
+ var id = obj.id;
+ obj._finishFetch(idMap[id].toJSON());
+ results[i] = idMap[id];
+ }
+ }
+ }
+ return _ParsePromise2.default.as(results);
+ });
+ } else {
+ var RESTController = _CoreManager2.default.getRESTController();
+ return RESTController.request('GET', 'classes/' + target.className + '/' + target._getId(), {}, options).then(function (response, status, xhr) {
+ if (target instanceof ParseObject) {
+ target._clearPendingOps();
+ target._clearServerData();
+ target._finishFetch(response);
+ }
+ return target;
+ });
+ }
+ },
+ destroy: function (target, options) {
+ var RESTController = _CoreManager2.default.getRESTController();
+ if (Array.isArray(target)) {
+ if (target.length < 1) {
+ return _ParsePromise2.default.as([]);
+ }
+ var batches = [[]];
+ target.forEach(function (obj) {
+ if (!obj.id) {
+ return;
+ }
+ batches[batches.length - 1].push(obj);
+ if (batches[batches.length - 1].length >= 20) {
+ batches.push([]);
+ }
+ });
+ if (batches[batches.length - 1].length === 0) {
+ // If the last batch is empty, remove it
+ batches.pop();
+ }
+ var deleteCompleted = _ParsePromise2.default.as();
+ var errors = [];
+ batches.forEach(function (batch) {
+ deleteCompleted = deleteCompleted.then(function () {
+ return RESTController.request('POST', 'batch', {
+ requests: batch.map(function (obj) {
+ return {
+ method: 'DELETE',
+ path: getServerUrlPath() + 'classes/' + obj.className + '/' + obj._getId(),
+ body: {}
+ };
+ })
+ }, options).then(function (results) {
+ for (var i = 0; i < results.length; i++) {
+ if (results[i] && results[i].hasOwnProperty('error')) {
+ var err = new _ParseError2.default(results[i].error.code, results[i].error.error);
+ err.object = batch[i];
+ errors.push(err);
+ }
+ }
+ });
+ });
+ });
+ return deleteCompleted.then(function () {
+ if (errors.length) {
+ var aggregate = new _ParseError2.default(_ParseError2.default.AGGREGATE_ERROR);
+ aggregate.errors = errors;
+ return _ParsePromise2.default.error(aggregate);
+ }
+ return _ParsePromise2.default.as(target);
+ });
+ } else if (target instanceof ParseObject) {
+ return RESTController.request('DELETE', 'classes/' + target.className + '/' + target._getId(), {}, options).then(function () {
+ return _ParsePromise2.default.as(target);
+ });
+ }
+ return _ParsePromise2.default.as(target);
+ },
+ save: function (target, options) {
+ var RESTController = _CoreManager2.default.getRESTController();
+ var stateController = _CoreManager2.default.getObjectStateController();
+ if (Array.isArray(target)) {
+ if (target.length < 1) {
+ return _ParsePromise2.default.as([]);
+ }
+
+ var unsaved = target.concat();
+ for (var i = 0; i < target.length; i++) {
+ if (target[i] instanceof ParseObject) {
+ unsaved = unsaved.concat((0, _unsavedChildren2.default)(target[i], true));
+ }
+ }
+ unsaved = (0, _unique2.default)(unsaved);
+
+ var filesSaved = _ParsePromise2.default.as();
+ var pending = [];
+ unsaved.forEach(function (el) {
+ if (el instanceof _ParseFile2.default) {
+ filesSaved = filesSaved.then(function () {
+ return el.save();
+ });
+ } else if (el instanceof ParseObject) {
+ pending.push(el);
+ }
+ });
+
+ return filesSaved.then(function () {
+ var objectError = null;
+ return _ParsePromise2.default._continueWhile(function () {
+ return pending.length > 0;
+ }, function () {
+ var batch = [];
+ var nextPending = [];
+ pending.forEach(function (el) {
+ if (batch.length < 20 && (0, _canBeSerialized2.default)(el)) {
+ batch.push(el);
+ } else {
+ nextPending.push(el);
+ }
+ });
+ pending = nextPending;
+ if (batch.length < 1) {
+ return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Tried to save a batch with a cycle.'));
+ }
+
+ // Queue up tasks for each object in the batch.
+ // When every task is ready, the API request will execute
+ var batchReturned = new _ParsePromise2.default();
+ var batchReady = [];
+ var batchTasks = [];
+ batch.forEach(function (obj, index) {
+ var ready = new _ParsePromise2.default();
+ batchReady.push(ready);
+
+ stateController.pushPendingState(obj._getStateIdentifier());
+ batchTasks.push(stateController.enqueueTask(obj._getStateIdentifier(), function () {
+ ready.resolve();
+ return batchReturned.then(function (responses, status) {
+ if (responses[index].hasOwnProperty('success')) {
+ obj._handleSaveResponse(responses[index].success, status);
+ } else {
+ if (!objectError && responses[index].hasOwnProperty('error')) {
+ var serverError = responses[index].error;
+ objectError = new _ParseError2.default(serverError.code, serverError.error);
+ // Cancel the rest of the save
+ pending = [];
+ }
+ obj._handleSaveError();
+ }
+ });
+ }));
+ });
+
+ _ParsePromise2.default.when(batchReady).then(function () {
+ // Kick off the batch request
+ return RESTController.request('POST', 'batch', {
+ requests: batch.map(function (obj) {
+ var params = obj._getSaveParams();
+ params.path = getServerUrlPath() + params.path;
+ return params;
+ })
+ }, options);
+ }).then(function (response, status, xhr) {
+ batchReturned.resolve(response, status);
+ });
+
+ return _ParsePromise2.default.when(batchTasks);
+ }).then(function () {
+ if (objectError) {
+ return _ParsePromise2.default.error(objectError);
+ }
+ return _ParsePromise2.default.as(target);
+ });
+ });
+ } else if (target instanceof ParseObject) {
+ // copying target lets Flow guarantee the pointer isn't modified elsewhere
+ var targetCopy = target;
+ var task = function () {
+ var params = targetCopy._getSaveParams();
+ return RESTController.request(params.method, params.path, params.body, options).then(function (response, status) {
+ targetCopy._handleSaveResponse(response, status);
+ }, function (error) {
+ targetCopy._handleSaveError();
+ return _ParsePromise2.default.error(error);
+ });
+ };
+
+ stateController.pushPendingState(target._getStateIdentifier());
+ return stateController.enqueueTask(target._getStateIdentifier(), task).then(function () {
+ return target;
+ }, function (error) {
+ return _ParsePromise2.default.error(error);
+ });
+ }
+ return _ParsePromise2.default.as();
+ }
+};
+
+_CoreManager2.default.setObjectController(DefaultController);
\ No newline at end of file
diff --git a/lib/browser/ParseOp.js b/lib/browser/ParseOp.js
new file mode 100644
index 000000000..1eba19303
--- /dev/null
+++ b/lib/browser/ParseOp.js
@@ -0,0 +1,579 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.RelationOp = exports.RemoveOp = exports.AddUniqueOp = exports.AddOp = exports.IncrementOp = exports.UnsetOp = exports.SetOp = exports.Op = undefined;
+
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+exports.opFromJSON = opFromJSON;
+
+var _arrayContainsObject = require('./arrayContainsObject');
+
+var _arrayContainsObject2 = _interopRequireDefault(_arrayContainsObject);
+
+var _decode = require('./decode');
+
+var _decode2 = _interopRequireDefault(_decode);
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _ParseObject = require('./ParseObject');
+
+var _ParseObject2 = _interopRequireDefault(_ParseObject);
+
+var _ParseRelation = require('./ParseRelation');
+
+var _ParseRelation2 = _interopRequireDefault(_ParseRelation);
+
+var _unique = require('./unique');
+
+var _unique2 = _interopRequireDefault(_unique);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function opFromJSON(json) {
+ if (!json || !json.__op) {
+ return null;
+ }
+ switch (json.__op) {
+ case 'Delete':
+ return new UnsetOp();
+ case 'Increment':
+ return new IncrementOp(json.amount);
+ case 'Add':
+ return new AddOp((0, _decode2.default)(json.objects));
+ case 'AddUnique':
+ return new AddUniqueOp((0, _decode2.default)(json.objects));
+ case 'Remove':
+ return new RemoveOp((0, _decode2.default)(json.objects));
+ case 'AddRelation':
+ var toAdd = (0, _decode2.default)(json.objects);
+ if (!Array.isArray(toAdd)) {
+ return new RelationOp([], []);
+ }
+ return new RelationOp(toAdd, []);
+ case 'RemoveRelation':
+ var toRemove = (0, _decode2.default)(json.objects);
+ if (!Array.isArray(toRemove)) {
+ return new RelationOp([], []);
+ }
+ return new RelationOp([], toRemove);
+ case 'Batch':
+ var toAdd = [];
+ var toRemove = [];
+ for (var i = 0; i < json.ops.length; i++) {
+ if (json.ops[i].__op === 'AddRelation') {
+ toAdd = toAdd.concat((0, _decode2.default)(json.ops[i].objects));
+ } else if (json.ops[i].__op === 'RemoveRelation') {
+ toRemove = toRemove.concat((0, _decode2.default)(json.ops[i].objects));
+ }
+ }
+ return new RelationOp(toAdd, toRemove);
+ }
+ return null;
+}
+
+var Op = exports.Op = function () {
+ function Op() {
+ (0, _classCallCheck3.default)(this, Op);
+ }
+
+ (0, _createClass3.default)(Op, [{
+ key: 'applyTo',
+
+ // Empty parent class
+ value: function (value) {}
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {}
+ }, {
+ key: 'toJSON',
+ value: function () {}
+ }]);
+ return Op;
+}();
+
+var SetOp = exports.SetOp = function (_Op) {
+ (0, _inherits3.default)(SetOp, _Op);
+
+ function SetOp(value) {
+ (0, _classCallCheck3.default)(this, SetOp);
+
+ var _this = (0, _possibleConstructorReturn3.default)(this, (SetOp.__proto__ || (0, _getPrototypeOf2.default)(SetOp)).call(this));
+
+ _this._value = value;
+ return _this;
+ }
+
+ (0, _createClass3.default)(SetOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ return this._value;
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ return new SetOp(this._value);
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return (0, _encode2.default)(this._value, false, true);
+ }
+ }]);
+ return SetOp;
+}(Op);
+
+var UnsetOp = exports.UnsetOp = function (_Op2) {
+ (0, _inherits3.default)(UnsetOp, _Op2);
+
+ function UnsetOp() {
+ (0, _classCallCheck3.default)(this, UnsetOp);
+ return (0, _possibleConstructorReturn3.default)(this, (UnsetOp.__proto__ || (0, _getPrototypeOf2.default)(UnsetOp)).apply(this, arguments));
+ }
+
+ (0, _createClass3.default)(UnsetOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ return undefined;
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ return new UnsetOp();
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'Delete' };
+ }
+ }]);
+ return UnsetOp;
+}(Op);
+
+var IncrementOp = exports.IncrementOp = function (_Op3) {
+ (0, _inherits3.default)(IncrementOp, _Op3);
+
+ function IncrementOp(amount) {
+ (0, _classCallCheck3.default)(this, IncrementOp);
+
+ var _this3 = (0, _possibleConstructorReturn3.default)(this, (IncrementOp.__proto__ || (0, _getPrototypeOf2.default)(IncrementOp)).call(this));
+
+ if (typeof amount !== 'number') {
+ throw new TypeError('Increment Op must be initialized with a numeric amount.');
+ }
+ _this3._amount = amount;
+ return _this3;
+ }
+
+ (0, _createClass3.default)(IncrementOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ if (typeof value === 'undefined') {
+ return this._amount;
+ }
+ if (typeof value !== 'number') {
+ throw new TypeError('Cannot increment a non-numeric value.');
+ }
+ return this._amount + value;
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new SetOp(this._amount);
+ }
+ if (previous instanceof IncrementOp) {
+ return new IncrementOp(this.applyTo(previous._amount));
+ }
+ throw new Error('Cannot merge Increment Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'Increment', amount: this._amount };
+ }
+ }]);
+ return IncrementOp;
+}(Op);
+
+var AddOp = exports.AddOp = function (_Op4) {
+ (0, _inherits3.default)(AddOp, _Op4);
+
+ function AddOp(value) {
+ (0, _classCallCheck3.default)(this, AddOp);
+
+ var _this4 = (0, _possibleConstructorReturn3.default)(this, (AddOp.__proto__ || (0, _getPrototypeOf2.default)(AddOp)).call(this));
+
+ _this4._value = Array.isArray(value) ? value : [value];
+ return _this4;
+ }
+
+ (0, _createClass3.default)(AddOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ if (value == null) {
+ return this._value;
+ }
+ if (Array.isArray(value)) {
+ return value.concat(this._value);
+ }
+ throw new Error('Cannot add elements to a non-array value');
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new SetOp(this._value);
+ }
+ if (previous instanceof AddOp) {
+ return new AddOp(this.applyTo(previous._value));
+ }
+ throw new Error('Cannot merge Add Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'Add', objects: (0, _encode2.default)(this._value, false, true) };
+ }
+ }]);
+ return AddOp;
+}(Op);
+
+var AddUniqueOp = exports.AddUniqueOp = function (_Op5) {
+ (0, _inherits3.default)(AddUniqueOp, _Op5);
+
+ function AddUniqueOp(value) {
+ (0, _classCallCheck3.default)(this, AddUniqueOp);
+
+ var _this5 = (0, _possibleConstructorReturn3.default)(this, (AddUniqueOp.__proto__ || (0, _getPrototypeOf2.default)(AddUniqueOp)).call(this));
+
+ _this5._value = (0, _unique2.default)(Array.isArray(value) ? value : [value]);
+ return _this5;
+ }
+
+ (0, _createClass3.default)(AddUniqueOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ if (value == null) {
+ return this._value || [];
+ }
+ if (Array.isArray(value)) {
+ // copying value lets Flow guarantee the pointer isn't modified elsewhere
+ var valueCopy = value;
+ var toAdd = [];
+ this._value.forEach(function (v) {
+ if (v instanceof _ParseObject2.default) {
+ if (!(0, _arrayContainsObject2.default)(valueCopy, v)) {
+ toAdd.push(v);
+ }
+ } else {
+ if (valueCopy.indexOf(v) < 0) {
+ toAdd.push(v);
+ }
+ }
+ });
+ return value.concat(toAdd);
+ }
+ throw new Error('Cannot add elements to a non-array value');
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new SetOp(this._value);
+ }
+ if (previous instanceof AddUniqueOp) {
+ return new AddUniqueOp(this.applyTo(previous._value));
+ }
+ throw new Error('Cannot merge AddUnique Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'AddUnique', objects: (0, _encode2.default)(this._value, false, true) };
+ }
+ }]);
+ return AddUniqueOp;
+}(Op);
+
+var RemoveOp = exports.RemoveOp = function (_Op6) {
+ (0, _inherits3.default)(RemoveOp, _Op6);
+
+ function RemoveOp(value) {
+ (0, _classCallCheck3.default)(this, RemoveOp);
+
+ var _this6 = (0, _possibleConstructorReturn3.default)(this, (RemoveOp.__proto__ || (0, _getPrototypeOf2.default)(RemoveOp)).call(this));
+
+ _this6._value = (0, _unique2.default)(Array.isArray(value) ? value : [value]);
+ return _this6;
+ }
+
+ (0, _createClass3.default)(RemoveOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ if (value == null) {
+ return [];
+ }
+ if (Array.isArray(value)) {
+ var i = value.indexOf(this._value);
+ var removed = value.concat([]);
+ for (var i = 0; i < this._value.length; i++) {
+ var index = removed.indexOf(this._value[i]);
+ while (index > -1) {
+ removed.splice(index, 1);
+ index = removed.indexOf(this._value[i]);
+ }
+ if (this._value[i] instanceof _ParseObject2.default && this._value[i].id) {
+ for (var j = 0; j < removed.length; j++) {
+ if (removed[j] instanceof _ParseObject2.default && this._value[i].id === removed[j].id) {
+ removed.splice(j, 1);
+ j--;
+ }
+ }
+ }
+ }
+ return removed;
+ }
+ throw new Error('Cannot remove elements from a non-array value');
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new UnsetOp();
+ }
+ if (previous instanceof RemoveOp) {
+ var uniques = previous._value.concat([]);
+ for (var i = 0; i < this._value.length; i++) {
+ if (this._value[i] instanceof _ParseObject2.default) {
+ if (!(0, _arrayContainsObject2.default)(uniques, this._value[i])) {
+ uniques.push(this._value[i]);
+ }
+ } else {
+ if (uniques.indexOf(this._value[i]) < 0) {
+ uniques.push(this._value[i]);
+ }
+ }
+ }
+ return new RemoveOp(uniques);
+ }
+ throw new Error('Cannot merge Remove Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'Remove', objects: (0, _encode2.default)(this._value, false, true) };
+ }
+ }]);
+ return RemoveOp;
+}(Op);
+
+var RelationOp = exports.RelationOp = function (_Op7) {
+ (0, _inherits3.default)(RelationOp, _Op7);
+
+ function RelationOp(adds, removes) {
+ (0, _classCallCheck3.default)(this, RelationOp);
+
+ var _this7 = (0, _possibleConstructorReturn3.default)(this, (RelationOp.__proto__ || (0, _getPrototypeOf2.default)(RelationOp)).call(this));
+
+ _this7._targetClassName = null;
+
+ if (Array.isArray(adds)) {
+ _this7.relationsToAdd = (0, _unique2.default)(adds.map(_this7._extractId, _this7));
+ }
+
+ if (Array.isArray(removes)) {
+ _this7.relationsToRemove = (0, _unique2.default)(removes.map(_this7._extractId, _this7));
+ }
+ return _this7;
+ }
+
+ (0, _createClass3.default)(RelationOp, [{
+ key: '_extractId',
+ value: function (obj) {
+ if (typeof obj === 'string') {
+ return obj;
+ }
+ if (!obj.id) {
+ throw new Error('You cannot add or remove an unsaved Parse Object from a relation');
+ }
+ if (!this._targetClassName) {
+ this._targetClassName = obj.className;
+ }
+ if (this._targetClassName !== obj.className) {
+ throw new Error('Tried to create a Relation with 2 different object types: ' + this._targetClassName + ' and ' + obj.className + '.');
+ }
+ return obj.id;
+ }
+ }, {
+ key: 'applyTo',
+ value: function (value, object, key) {
+ if (!value) {
+ if (!object || !key) {
+ throw new Error('Cannot apply a RelationOp without either a previous value, or an object and a key');
+ }
+ var parent = new _ParseObject2.default(object.className);
+ if (object.id && object.id.indexOf('local') === 0) {
+ parent._localId = object.id;
+ } else if (object.id) {
+ parent.id = object.id;
+ }
+ var relation = new _ParseRelation2.default(parent, key);
+ relation.targetClassName = this._targetClassName;
+ return relation;
+ }
+ if (value instanceof _ParseRelation2.default) {
+ if (this._targetClassName) {
+ if (value.targetClassName) {
+ if (this._targetClassName !== value.targetClassName) {
+ throw new Error('Related object must be a ' + value.targetClassName + ', but a ' + this._targetClassName + ' was passed in.');
+ }
+ } else {
+ value.targetClassName = this._targetClassName;
+ }
+ }
+ return value;
+ } else {
+ throw new Error('Relation cannot be applied to a non-relation field');
+ }
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ } else if (previous instanceof UnsetOp) {
+ throw new Error('You cannot modify a relation after deleting it.');
+ } else if (previous instanceof RelationOp) {
+ if (previous._targetClassName && previous._targetClassName !== this._targetClassName) {
+ throw new Error('Related object must be of class ' + previous._targetClassName + ', but ' + (this._targetClassName || 'null') + ' was passed in.');
+ }
+ var newAdd = previous.relationsToAdd.concat([]);
+ this.relationsToRemove.forEach(function (r) {
+ var index = newAdd.indexOf(r);
+ if (index > -1) {
+ newAdd.splice(index, 1);
+ }
+ });
+ this.relationsToAdd.forEach(function (r) {
+ var index = newAdd.indexOf(r);
+ if (index < 0) {
+ newAdd.push(r);
+ }
+ });
+
+ var newRemove = previous.relationsToRemove.concat([]);
+ this.relationsToAdd.forEach(function (r) {
+ var index = newRemove.indexOf(r);
+ if (index > -1) {
+ newRemove.splice(index, 1);
+ }
+ });
+ this.relationsToRemove.forEach(function (r) {
+ var index = newRemove.indexOf(r);
+ if (index < 0) {
+ newRemove.push(r);
+ }
+ });
+
+ var newRelation = new RelationOp(newAdd, newRemove);
+ newRelation._targetClassName = this._targetClassName;
+ return newRelation;
+ }
+ throw new Error('Cannot merge Relation Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ var _this8 = this;
+
+ var idToPointer = function (id) {
+ return {
+ __type: 'Pointer',
+ className: _this8._targetClassName,
+ objectId: id
+ };
+ };
+
+ var adds = null;
+ var removes = null;
+ var pointers = null;
+
+ if (this.relationsToAdd.length > 0) {
+ pointers = this.relationsToAdd.map(idToPointer);
+ adds = { __op: 'AddRelation', objects: pointers };
+ }
+ if (this.relationsToRemove.length > 0) {
+ pointers = this.relationsToRemove.map(idToPointer);
+ removes = { __op: 'RemoveRelation', objects: pointers };
+ }
+
+ if (adds && removes) {
+ return { __op: 'Batch', ops: [adds, removes] };
+ }
+
+ return adds || removes || {};
+ }
+ }]);
+ return RelationOp;
+}(Op);
\ No newline at end of file
diff --git a/lib/browser/ParsePromise.js b/lib/browser/ParsePromise.js
new file mode 100644
index 000000000..a79b4afee
--- /dev/null
+++ b/lib/browser/ParsePromise.js
@@ -0,0 +1,740 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _getIterator2 = require('babel-runtime/core-js/get-iterator');
+
+var _getIterator3 = _interopRequireDefault(_getIterator2);
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+var _isPromisesAPlusCompliant = true;
+
+/**
+ * A Promise is returned by async methods as a hook to provide callbacks to be
+ * called when the async task is fulfilled.
+ *
+ * Typical usage would be like:
+ * query.find().then(function(results) {
+ * results[0].set("foo", "bar");
+ * return results[0].saveAsync();
+ * }).then(function(result) {
+ * console.log("Updated " + result.id);
+ * });
+ *
+ *
+ * @class Parse.Promise
+ * @constructor
+ */
+
+var ParsePromise = function () {
+ function ParsePromise(executor) {
+ (0, _classCallCheck3.default)(this, ParsePromise);
+
+ this._resolved = false;
+ this._rejected = false;
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+
+ if (typeof executor === 'function') {
+ executor(this.resolve.bind(this), this.reject.bind(this));
+ }
+ }
+
+ /**
+ * Marks this promise as fulfilled, firing any callbacks waiting on it.
+ * @method resolve
+ * @param {Object} result the result to pass to the callbacks.
+ */
+
+ (0, _createClass3.default)(ParsePromise, [{
+ key: 'resolve',
+ value: function () {
+ if (this._resolved || this._rejected) {
+ throw new Error('A promise was resolved even though it had already been ' + (this._resolved ? 'resolved' : 'rejected') + '.');
+ }
+ this._resolved = true;
+
+ for (var _len = arguments.length, results = Array(_len), _key = 0; _key < _len; _key++) {
+ results[_key] = arguments[_key];
+ }
+
+ this._result = results;
+ for (var i = 0; i < this._resolvedCallbacks.length; i++) {
+ this._resolvedCallbacks[i].apply(this, results);
+ }
+
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+ }
+
+ /**
+ * Marks this promise as fulfilled, firing any callbacks waiting on it.
+ * @method reject
+ * @param {Object} error the error to pass to the callbacks.
+ */
+
+ }, {
+ key: 'reject',
+ value: function (error) {
+ if (this._resolved || this._rejected) {
+ throw new Error('A promise was rejected even though it had already been ' + (this._resolved ? 'resolved' : 'rejected') + '.');
+ }
+ this._rejected = true;
+ this._error = error;
+ for (var i = 0; i < this._rejectedCallbacks.length; i++) {
+ this._rejectedCallbacks[i](error);
+ }
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+ }
+
+ /**
+ * Adds callbacks to be called when this promise is fulfilled. Returns a new
+ * Promise that will be fulfilled when the callback is complete. It allows
+ * chaining. If the callback itself returns a Promise, then the one returned
+ * by "then" will not be fulfilled until that one returned by the callback
+ * is fulfilled.
+ * @method then
+ * @param {Function} resolvedCallback Function that is called when this
+ * Promise is resolved. Once the callback is complete, then the Promise
+ * returned by "then" will also be fulfilled.
+ * @param {Function} rejectedCallback Function that is called when this
+ * Promise is rejected with an error. Once the callback is complete, then
+ * the promise returned by "then" with be resolved successfully. If
+ * rejectedCallback is null, or it returns a rejected Promise, then the
+ * Promise returned by "then" will be rejected with that error.
+ * @return {Parse.Promise} A new Promise that will be fulfilled after this
+ * Promise is fulfilled and either callback has completed. If the callback
+ * returned a Promise, then this Promise will not be fulfilled until that
+ * one is.
+ */
+
+ }, {
+ key: 'then',
+ value: function (resolvedCallback, rejectedCallback) {
+ var _this = this;
+
+ var promise = new ParsePromise();
+
+ var wrappedResolvedCallback = function () {
+ for (var _len2 = arguments.length, results = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ results[_key2] = arguments[_key2];
+ }
+
+ if (typeof resolvedCallback === 'function') {
+ if (_isPromisesAPlusCompliant) {
+ try {
+ results = [resolvedCallback.apply(this, results)];
+ } catch (e) {
+ results = [ParsePromise.error(e)];
+ }
+ } else {
+ results = [resolvedCallback.apply(this, results)];
+ }
+ }
+ if (results.length === 1 && ParsePromise.is(results[0])) {
+ results[0].then(function () {
+ promise.resolve.apply(promise, arguments);
+ }, function (error) {
+ promise.reject(error);
+ });
+ } else {
+ promise.resolve.apply(promise, results);
+ }
+ };
+
+ var wrappedRejectedCallback = function (error) {
+ var result = [];
+ if (typeof rejectedCallback === 'function') {
+ if (_isPromisesAPlusCompliant) {
+ try {
+ result = [rejectedCallback(error)];
+ } catch (e) {
+ result = [ParsePromise.error(e)];
+ }
+ } else {
+ result = [rejectedCallback(error)];
+ }
+ if (result.length === 1 && ParsePromise.is(result[0])) {
+ result[0].then(function () {
+ promise.resolve.apply(promise, arguments);
+ }, function (error) {
+ promise.reject(error);
+ });
+ } else {
+ if (_isPromisesAPlusCompliant) {
+ promise.resolve.apply(promise, result);
+ } else {
+ promise.reject(result[0]);
+ }
+ }
+ } else {
+ promise.reject(error);
+ }
+ };
+
+ var runLater = function (fn) {
+ fn.call();
+ };
+ if (_isPromisesAPlusCompliant) {
+ if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
+ runLater = function (fn) {
+ process.nextTick(fn);
+ };
+ } else if (typeof setTimeout === 'function') {
+ runLater = function (fn) {
+ setTimeout(fn, 0);
+ };
+ }
+ }
+
+ if (this._resolved) {
+ runLater(function () {
+ wrappedResolvedCallback.apply(_this, _this._result);
+ });
+ } else if (this._rejected) {
+ runLater(function () {
+ wrappedRejectedCallback(_this._error);
+ });
+ } else {
+ this._resolvedCallbacks.push(wrappedResolvedCallback);
+ this._rejectedCallbacks.push(wrappedRejectedCallback);
+ }
+
+ return promise;
+ }
+
+ /**
+ * Add handlers to be called when the promise
+ * is either resolved or rejected
+ * @method always
+ */
+
+ }, {
+ key: 'always',
+ value: function (callback) {
+ return this.then(callback, callback);
+ }
+
+ /**
+ * Add handlers to be called when the Promise object is resolved
+ * @method done
+ */
+
+ }, {
+ key: 'done',
+ value: function (callback) {
+ return this.then(callback);
+ }
+
+ /**
+ * Add handlers to be called when the Promise object is rejected
+ * Alias for catch().
+ * @method fail
+ */
+
+ }, {
+ key: 'fail',
+ value: function (callback) {
+ return this.then(null, callback);
+ }
+
+ /**
+ * Add handlers to be called when the Promise object is rejected
+ * @method catch
+ */
+
+ }, {
+ key: 'catch',
+ value: function (callback) {
+ return this.then(null, callback);
+ }
+
+ /**
+ * Run the given callbacks after this promise is fulfilled.
+ * @method _thenRunCallbacks
+ * @param optionsOrCallback {} A Backbone-style options callback, or a
+ * callback function. If this is an options object and contains a "model"
+ * attributes, that will be passed to error callbacks as the first argument.
+ * @param model {} If truthy, this will be passed as the first result of
+ * error callbacks. This is for Backbone-compatability.
+ * @return {Parse.Promise} A promise that will be resolved after the
+ * callbacks are run, with the same result as this.
+ */
+
+ }, {
+ key: '_thenRunCallbacks',
+ value: function (optionsOrCallback, model) {
+ var options = {};
+ if (typeof optionsOrCallback === 'function') {
+ options.success = function (result) {
+ optionsOrCallback(result, null);
+ };
+ options.error = function (error) {
+ optionsOrCallback(null, error);
+ };
+ } else if ((typeof optionsOrCallback === 'undefined' ? 'undefined' : (0, _typeof3.default)(optionsOrCallback)) === 'object') {
+ if (typeof optionsOrCallback.success === 'function') {
+ options.success = optionsOrCallback.success;
+ }
+ if (typeof optionsOrCallback.error === 'function') {
+ options.error = optionsOrCallback.error;
+ }
+ }
+
+ return this.then(function () {
+ for (var _len3 = arguments.length, results = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
+ results[_key3] = arguments[_key3];
+ }
+
+ if (options.success) {
+ options.success.apply(this, results);
+ }
+ return ParsePromise.as.apply(ParsePromise, arguments);
+ }, function (error) {
+ if (options.error) {
+ if (typeof model !== 'undefined') {
+ options.error(model, error);
+ } else {
+ options.error(error);
+ }
+ }
+ // By explicitly returning a rejected Promise, this will work with
+ // either jQuery or Promises/A+ semantics.
+ return ParsePromise.error(error);
+ });
+ }
+
+ /**
+ * Adds a callback function that should be called regardless of whether
+ * this promise failed or succeeded. The callback will be given either the
+ * array of results for its first argument, or the error as its second,
+ * depending on whether this Promise was rejected or resolved. Returns a
+ * new Promise, like "then" would.
+ * @method _continueWith
+ * @param {Function} continuation the callback.
+ */
+
+ }, {
+ key: '_continueWith',
+ value: function (continuation) {
+ return this.then(function () {
+ for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
+ args[_key4] = arguments[_key4];
+ }
+
+ return continuation(args, null);
+ }, function (error) {
+ return continuation(null, error);
+ });
+ }
+
+ /**
+ * Returns true iff the given object fulfils the Promise interface.
+ * @method is
+ * @param {Object} promise The object to test
+ * @static
+ * @return {Boolean}
+ */
+
+ }], [{
+ key: 'is',
+ value: function (promise) {
+ return promise != null && typeof promise.then === 'function';
+ }
+
+ /**
+ * Returns a new promise that is resolved with a given value.
+ * @method as
+ * @param value The value to resolve the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'as',
+ value: function () {
+ var promise = new ParsePromise();
+
+ for (var _len5 = arguments.length, values = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
+ values[_key5] = arguments[_key5];
+ }
+
+ promise.resolve.apply(promise, values);
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is resolved with a given value.
+ * If that value is a thenable Promise (has a .then() prototype
+ * method), the new promise will be chained to the end of the
+ * value.
+ * @method resolve
+ * @param value The value to resolve the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'resolve',
+ value: function (value) {
+ return new ParsePromise(function (resolve, reject) {
+ if (ParsePromise.is(value)) {
+ value.then(resolve, reject);
+ } else {
+ resolve(value);
+ }
+ });
+ }
+
+ /**
+ * Returns a new promise that is rejected with a given error.
+ * @method error
+ * @param error The error to reject the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'error',
+ value: function () {
+ var promise = new ParsePromise();
+
+ for (var _len6 = arguments.length, errors = Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
+ errors[_key6] = arguments[_key6];
+ }
+
+ promise.reject.apply(promise, errors);
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is rejected with a given error.
+ * This is an alias for Parse.Promise.error, for compliance with
+ * the ES6 implementation.
+ * @method reject
+ * @param error The error to reject the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'reject',
+ value: function () {
+ for (var _len7 = arguments.length, errors = Array(_len7), _key7 = 0; _key7 < _len7; _key7++) {
+ errors[_key7] = arguments[_key7];
+ }
+
+ return ParsePromise.error.apply(null, errors);
+ }
+
+ /**
+ * Returns a new promise that is fulfilled when all of the input promises
+ * are resolved. If any promise in the list fails, then the returned promise
+ * will be rejected with an array containing the error from each promise.
+ * If they all succeed, then the returned promise will succeed, with the
+ * results being the results of all the input
+ * promises. For example:
+ * var p1 = Parse.Promise.as(1);
+ * var p2 = Parse.Promise.as(2);
+ * var p3 = Parse.Promise.as(3);
+ *
+ * Parse.Promise.when(p1, p2, p3).then(function(r1, r2, r3) {
+ * console.log(r1); // prints 1
+ * console.log(r2); // prints 2
+ * console.log(r3); // prints 3
+ * });
+ *
+ * The input promises can also be specified as an array:
+ * var promises = [p1, p2, p3];
+ * Parse.Promise.when(promises).then(function(results) {
+ * console.log(results); // prints [1,2,3]
+ * });
+ *
+ * @method when
+ * @param {Array} promises a list of promises to wait for.
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'when',
+ value: function (promises) {
+ var objects;
+ var arrayArgument = Array.isArray(promises);
+ if (arrayArgument) {
+ objects = promises;
+ } else {
+ objects = arguments;
+ }
+
+ var total = objects.length;
+ var hadError = false;
+ var results = [];
+ var returnValue = arrayArgument ? [results] : results;
+ var errors = [];
+ results.length = objects.length;
+ errors.length = objects.length;
+
+ if (total === 0) {
+ return ParsePromise.as.apply(this, returnValue);
+ }
+
+ var promise = new ParsePromise();
+
+ var resolveOne = function () {
+ total--;
+ if (total <= 0) {
+ if (hadError) {
+ promise.reject(errors);
+ } else {
+ promise.resolve.apply(promise, returnValue);
+ }
+ }
+ };
+
+ var chain = function (object, index) {
+ if (ParsePromise.is(object)) {
+ object.then(function (result) {
+ results[index] = result;
+ resolveOne();
+ }, function (error) {
+ errors[index] = error;
+ hadError = true;
+ resolveOne();
+ });
+ } else {
+ results[i] = object;
+ resolveOne();
+ }
+ };
+ for (var i = 0; i < objects.length; i++) {
+ chain(objects[i], i);
+ }
+
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is fulfilled when all of the promises in the
+ * iterable argument are resolved. If any promise in the list fails, then
+ * the returned promise will be immediately rejected with the reason that
+ * single promise rejected. If they all succeed, then the returned promise
+ * will succeed, with the results being the results of all the input
+ * promises. If the iterable provided is empty, the returned promise will
+ * be immediately resolved.
+ *
+ * For example:
+ * var p1 = Parse.Promise.as(1);
+ * var p2 = Parse.Promise.as(2);
+ * var p3 = Parse.Promise.as(3);
+ *
+ * Parse.Promise.all([p1, p2, p3]).then(function([r1, r2, r3]) {
+ * console.log(r1); // prints 1
+ * console.log(r2); // prints 2
+ * console.log(r3); // prints 3
+ * });
+ *
+ * @method all
+ * @param {Iterable} promises an iterable of promises to wait for.
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'all',
+ value: function (promises) {
+ var total = 0;
+ var objects = [];
+
+ var _iteratorNormalCompletion = true;
+ var _didIteratorError = false;
+ var _iteratorError = undefined;
+
+ try {
+ for (var _iterator = (0, _getIterator3.default)(promises), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+ var p = _step.value;
+
+ objects[total++] = p;
+ }
+ } catch (err) {
+ _didIteratorError = true;
+ _iteratorError = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion && _iterator.return) {
+ _iterator.return();
+ }
+ } finally {
+ if (_didIteratorError) {
+ throw _iteratorError;
+ }
+ }
+ }
+
+ if (total === 0) {
+ return ParsePromise.as([]);
+ }
+
+ var hadError = false;
+ var promise = new ParsePromise();
+ var resolved = 0;
+ var results = [];
+ objects.forEach(function (object, i) {
+ if (ParsePromise.is(object)) {
+ object.then(function (result) {
+ if (hadError) {
+ return false;
+ }
+ results[i] = result;
+ resolved++;
+ if (resolved >= total) {
+ promise.resolve(results);
+ }
+ }, function (error) {
+ // Reject immediately
+ promise.reject(error);
+ hadError = true;
+ });
+ } else {
+ results[i] = object;
+ resolved++;
+ if (!hadError && resolved >= total) {
+ promise.resolve(results);
+ }
+ }
+ });
+
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is immediately fulfilled when any of the
+ * promises in the iterable argument are resolved or rejected. If the
+ * first promise to complete is resolved, the returned promise will be
+ * resolved with the same value. Likewise, if the first promise to
+ * complete is rejected, the returned promise will be rejected with the
+ * same reason.
+ *
+ * @method race
+ * @param {Iterable} promises an iterable of promises to wait for.
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'race',
+ value: function (promises) {
+ var completed = false;
+ var promise = new ParsePromise();
+ var _iteratorNormalCompletion2 = true;
+ var _didIteratorError2 = false;
+ var _iteratorError2 = undefined;
+
+ try {
+ for (var _iterator2 = (0, _getIterator3.default)(promises), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+ var p = _step2.value;
+
+ if (ParsePromise.is(p)) {
+ p.then(function (result) {
+ if (completed) {
+ return;
+ }
+ completed = true;
+ promise.resolve(result);
+ }, function (error) {
+ if (completed) {
+ return;
+ }
+ completed = true;
+ promise.reject(error);
+ });
+ } else if (!completed) {
+ completed = true;
+ promise.resolve(p);
+ }
+ }
+ } catch (err) {
+ _didIteratorError2 = true;
+ _iteratorError2 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion2 && _iterator2.return) {
+ _iterator2.return();
+ }
+ } finally {
+ if (_didIteratorError2) {
+ throw _iteratorError2;
+ }
+ }
+ }
+
+ return promise;
+ }
+
+ /**
+ * Runs the given asyncFunction repeatedly, as long as the predicate
+ * function returns a truthy value. Stops repeating if asyncFunction returns
+ * a rejected promise.
+ * @method _continueWhile
+ * @param {Function} predicate should return false when ready to stop.
+ * @param {Function} asyncFunction should return a Promise.
+ * @static
+ */
+
+ }, {
+ key: '_continueWhile',
+ value: function (predicate, asyncFunction) {
+ if (predicate()) {
+ return asyncFunction().then(function () {
+ return ParsePromise._continueWhile(predicate, asyncFunction);
+ });
+ }
+ return ParsePromise.as();
+ }
+ }, {
+ key: 'isPromisesAPlusCompliant',
+ value: function () {
+ return _isPromisesAPlusCompliant;
+ }
+ }, {
+ key: 'enableAPlusCompliant',
+ value: function () {
+ _isPromisesAPlusCompliant = true;
+ }
+ }, {
+ key: 'disableAPlusCompliant',
+ value: function () {
+ _isPromisesAPlusCompliant = false;
+ }
+ }]);
+ return ParsePromise;
+}();
+
+exports.default = ParsePromise;
\ No newline at end of file
diff --git a/lib/browser/ParseQuery.js b/lib/browser/ParseQuery.js
new file mode 100644
index 000000000..664f5a737
--- /dev/null
+++ b/lib/browser/ParseQuery.js
@@ -0,0 +1,1340 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _keys = require('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _ParseError = require('./ParseError');
+
+var _ParseError2 = _interopRequireDefault(_ParseError);
+
+var _ParseGeoPoint = require('./ParseGeoPoint');
+
+var _ParseGeoPoint2 = _interopRequireDefault(_ParseGeoPoint);
+
+var _ParseObject = require('./ParseObject');
+
+var _ParseObject2 = _interopRequireDefault(_ParseObject);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Converts a string into a regex that matches it.
+ * Surrounding with \Q .. \E does this, we just need to escape any \E's in
+ * the text separately.
+ */
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function quote(s) {
+ return '\\Q' + s.replace('\\E', '\\E\\\\E\\Q') + '\\E';
+}
+
+/**
+ * Handles pre-populating the result data of a query with select fields,
+ * making sure that the data object contains keys for all objects that have
+ * been requested with a select, so that our cached state updates correctly.
+ */
+function handleSelectResult(data, select) {
+ var serverDataMask = {};
+
+ select.forEach(function (field) {
+ var hasSubObjectSelect = field.indexOf(".") !== -1;
+ if (!hasSubObjectSelect && !data.hasOwnProperty(field)) {
+ // this field was selected, but is missing from the retrieved data
+ data[field] = undefined;
+ } else if (hasSubObjectSelect) {
+ // this field references a sub-object,
+ // so we need to walk down the path components
+ var pathComponents = field.split(".");
+ var obj = data;
+ var serverMask = serverDataMask;
+
+ pathComponents.forEach(function (component, index, arr) {
+ // add keys if the expected data is missing
+ if (!obj[component]) {
+ obj[component] = index == arr.length - 1 ? undefined : {};
+ }
+ obj = obj[component];
+
+ //add this path component to the server mask so we can fill it in later if needed
+ if (index < arr.length - 1) {
+ if (!serverMask[component]) {
+ serverMask[component] = {};
+ }
+ }
+ });
+ }
+ });
+
+ if ((0, _keys2.default)(serverDataMask).length > 0) {
+ var copyMissingDataWithMask = function copyMissingDataWithMask(src, dest, mask, copyThisLevel) {
+ //copy missing elements at this level
+ if (copyThisLevel) {
+ for (var key in src) {
+ if (src.hasOwnProperty(key) && !dest.hasOwnProperty(key)) {
+ dest[key] = src[key];
+ }
+ }
+ }
+ for (var key in mask) {
+ //traverse into objects as needed
+ copyMissingDataWithMask(src[key], dest[key], mask[key], true);
+ }
+ };
+
+ // When selecting from sub-objects, we don't want to blow away the missing
+ // information that we may have retrieved before. We've already added any
+ // missing selected keys to sub-objects, but we still need to add in the
+ // data for any previously retrieved sub-objects that were not selected.
+
+ var serverData = _CoreManager2.default.getObjectStateController().getServerData({ id: data.objectId, className: data.className });
+
+ copyMissingDataWithMask(serverData, data, serverDataMask, false);
+ }
+}
+
+/**
+ * Creates a new parse Parse.Query for the given Parse.Object subclass.
+ * @class Parse.Query
+ * @constructor
+ * @param {} objectClass An instance of a subclass of Parse.Object, or a Parse className string.
+ *
+ * Parse.Query defines a query that is used to fetch Parse.Objects. The
+ * most common use case is finding all objects that match a query through the
+ * find method. For example, this sample code fetches all objects
+ * of class MyClass. It calls a different function depending on
+ * whether the fetch succeeded or not.
+ *
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.find({
+ * success: function(results) {
+ * // results is an array of Parse.Object.
+ * },
+ *
+ * error: function(error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ *
+ * A Parse.Query can also be used to retrieve a single object whose id is
+ * known, through the get method. For example, this sample code fetches an
+ * object of class MyClass and id myId. It calls a
+ * different function depending on whether the fetch succeeded or not.
+ *
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.get(myId, {
+ * success: function(object) {
+ * // object is an instance of Parse.Object.
+ * },
+ *
+ * error: function(object, error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ *
+ * A Parse.Query can also be used to count the number of objects that match
+ * the query without retrieving all of those objects. For example, this
+ * sample code counts the number of objects of the class MyClass
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.count({
+ * success: function(number) {
+ * // There are number instances of MyClass.
+ * },
+ *
+ * error: function(error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ */
+
+var ParseQuery = function () {
+ function ParseQuery(objectClass) {
+ (0, _classCallCheck3.default)(this, ParseQuery);
+
+ if (typeof objectClass === 'string') {
+ if (objectClass === 'User' && _CoreManager2.default.get('PERFORM_USER_REWRITE')) {
+ this.className = '_User';
+ } else {
+ this.className = objectClass;
+ }
+ } else if (objectClass instanceof _ParseObject2.default) {
+ this.className = objectClass.className;
+ } else if (typeof objectClass === 'function') {
+ if (typeof objectClass.className === 'string') {
+ this.className = objectClass.className;
+ } else {
+ var obj = new objectClass();
+ this.className = obj.className;
+ }
+ } else {
+ throw new TypeError('A ParseQuery must be constructed with a ParseObject or class name.');
+ }
+
+ this._where = {};
+ this._include = [];
+ this._limit = -1; // negative limit is not sent in the server request
+ this._skip = 0;
+ this._extraOptions = {};
+ }
+
+ /**
+ * Adds constraint that at least one of the passed in queries matches.
+ * @method _orQuery
+ * @param {Array} queries
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+
+ (0, _createClass3.default)(ParseQuery, [{
+ key: '_orQuery',
+ value: function (queries) {
+ var queryJSON = queries.map(function (q) {
+ return q.toJSON().where;
+ });
+
+ this._where.$or = queryJSON;
+ return this;
+ }
+
+ /**
+ * Helper for condition queries
+ */
+
+ }, {
+ key: '_addCondition',
+ value: function (key, condition, value) {
+ if (!this._where[key] || typeof this._where[key] === 'string') {
+ this._where[key] = {};
+ }
+ this._where[key][condition] = (0, _encode2.default)(value, false, true);
+ return this;
+ }
+
+ /**
+ * Converts string for regular expression at the beginning
+ */
+
+ }, {
+ key: '_regexStartWith',
+ value: function (string) {
+ return '^' + quote(string);
+ }
+
+ /**
+ * Returns a JSON representation of this query.
+ * @method toJSON
+ * @return {Object} The JSON representation of the query.
+ */
+
+ }, {
+ key: 'toJSON',
+ value: function () {
+ var params = {
+ where: this._where
+ };
+
+ if (this._include.length) {
+ params.include = this._include.join(',');
+ }
+ if (this._select) {
+ params.keys = this._select.join(',');
+ }
+ if (this._limit >= 0) {
+ params.limit = this._limit;
+ }
+ if (this._skip > 0) {
+ params.skip = this._skip;
+ }
+ if (this._order) {
+ params.order = this._order.join(',');
+ }
+ for (var key in this._extraOptions) {
+ params[key] = this._extraOptions[key];
+ }
+
+ return params;
+ }
+
+ /**
+ * Constructs a Parse.Object whose id is already known by fetching data from
+ * the server. Either options.success or options.error is called when the
+ * find completes.
+ *
+ * @method get
+ * @param {String} objectId The id of the object to be fetched.
+ * @param {Object} options A Backbone-style options object.
+ * Valid options are:var compoundQuery = Parse.Query.or(query1, query2, query3);+ * + * will create a compoundQuery that is an or of the query1, query2, and + * query3. + * @method or + * @param {...Parse.Query} var_args The list of queries to OR. + * @static + * @return {Parse.Query} The query that is the OR of the passed in queries. + */ + + }], [{ + key: 'or', + value: function () { + var className = null; + + for (var _len7 = arguments.length, queries = Array(_len7), _key7 = 0; _key7 < _len7; _key7++) { + queries[_key7] = arguments[_key7]; + } + + queries.forEach(function (q) { + if (!className) { + className = q.className; + } + + if (className !== q.className) { + throw new Error('All queries must be for the same class.'); + } + }); + + var query = new ParseQuery(className); + query._orQuery(queries); + return query; + } + }]); + return ParseQuery; +}(); + +exports.default = ParseQuery; + +var DefaultController = { + find: function (className, params, options) { + var RESTController = _CoreManager2.default.getRESTController(); + + return RESTController.request('GET', 'classes/' + className, params, options); + } +}; + +_CoreManager2.default.setQueryController(DefaultController); \ No newline at end of file diff --git a/lib/browser/ParseRelation.js b/lib/browser/ParseRelation.js new file mode 100644 index 000000000..fe9ea8769 --- /dev/null +++ b/lib/browser/ParseRelation.js @@ -0,0 +1,182 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = require('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _ParseOp = require('./ParseOp'); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseQuery = require('./ParseQuery'); + +var _ParseQuery2 = _interopRequireDefault(_ParseQuery); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Creates a new Relation for the given parent object and key. This + * constructor should rarely be used directly, but rather created by + * Parse.Object.relation. + * @class Parse.Relation + * @constructor + * @param {Parse.Object} parent The parent of this relation. + * @param {String} key The key for this relation on the parent. + * + *
+ * A class that is used to access all of the children of a many-to-many + * relationship. Each instance of Parse.Relation is associated with a + * particular parent object and key. + *
+ */ +var ParseRelation = function () { + function ParseRelation(parent, key) { + (0, _classCallCheck3.default)(this, ParseRelation); + + this.parent = parent; + this.key = key; + this.targetClassName = null; + } + + /** + * Makes sure that this relation has the right parent and key. + */ + + (0, _createClass3.default)(ParseRelation, [{ + key: '_ensureParentAndKey', + value: function (parent, key) { + this.key = this.key || key; + if (this.key !== key) { + throw new Error('Internal Error. Relation retrieved from two different keys.'); + } + if (this.parent) { + if (this.parent.className !== parent.className) { + throw new Error('Internal Error. Relation retrieved from two different Objects.'); + } + if (this.parent.id) { + if (this.parent.id !== parent.id) { + throw new Error('Internal Error. Relation retrieved from two different Objects.'); + } + } else if (parent.id) { + this.parent = parent; + } + } else { + this.parent = parent; + } + } + + /** + * Adds a Parse.Object or an array of Parse.Objects to the relation. + * @method add + * @param {} objects The item or items to add. + */ + + }, { + key: 'add', + value: function (objects) { + if (!Array.isArray(objects)) { + objects = [objects]; + } + + var change = new _ParseOp.RelationOp(objects, []); + var parent = this.parent; + if (!parent) { + throw new Error('Cannot add to a Relation without a parent'); + } + parent.set(this.key, change); + this.targetClassName = change._targetClassName; + return parent; + } + + /** + * Removes a Parse.Object or an array of Parse.Objects from this relation. + * @method remove + * @param {} objects The item or items to remove. + */ + + }, { + key: 'remove', + value: function (objects) { + if (!Array.isArray(objects)) { + objects = [objects]; + } + + var change = new _ParseOp.RelationOp([], objects); + if (!this.parent) { + throw new Error('Cannot remove from a Relation without a parent'); + } + this.parent.set(this.key, change); + this.targetClassName = change._targetClassName; + } + + /** + * Returns a JSON version of the object suitable for saving to disk. + * @method toJSON + * @return {Object} + */ + + }, { + key: 'toJSON', + value: function () { + return { + __type: 'Relation', + className: this.targetClassName + }; + } + + /** + * Returns a Parse.Query that is limited to objects in this + * relation. + * @method query + * @return {Parse.Query} + */ + + }, { + key: 'query', + value: function () { + var query; + var parent = this.parent; + if (!parent) { + throw new Error('Cannot construct a query for a Relation without a parent'); + } + if (!this.targetClassName) { + query = new _ParseQuery2.default(parent.className); + query._extraOptions.redirectClassNameForKey = this.key; + } else { + query = new _ParseQuery2.default(this.targetClassName); + } + query._addCondition('$relatedTo', 'object', { + __type: 'Pointer', + className: parent.className, + objectId: parent.id + }); + query._addCondition('$relatedTo', 'key', this.key); + + return query; + } + }]); + return ParseRelation; +}(); /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +exports.default = ParseRelation; \ No newline at end of file diff --git a/lib/browser/ParseRole.js b/lib/browser/ParseRole.js new file mode 100644 index 000000000..567af7a54 --- /dev/null +++ b/lib/browser/ParseRole.js @@ -0,0 +1,196 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = require('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +var _get2 = require('babel-runtime/helpers/get'); + +var _get3 = _interopRequireDefault(_get2); + +var _inherits2 = require('babel-runtime/helpers/inherits'); + +var _inherits3 = _interopRequireDefault(_inherits2); + +var _ParseACL = require('./ParseACL'); + +var _ParseACL2 = _interopRequireDefault(_ParseACL); + +var _ParseError = require('./ParseError'); + +var _ParseError2 = _interopRequireDefault(_ParseError); + +var _ParseObject2 = require('./ParseObject'); + +var _ParseObject3 = _interopRequireDefault(_ParseObject2); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Represents a Role on the Parse server. Roles represent groupings of + * Users for the purposes of granting permissions (e.g. specifying an ACL + * for an Object). Roles are specified by their sets of child users and + * child roles, all of which are granted any permissions that the parent + * role has. + * + *Roles must have a name (which cannot be changed after creation of the + * role), and must specify an ACL.
+ * @class Parse.Role + * @constructor + * @param {String} name The name of the Role to create. + * @param {Parse.ACL} acl The ACL for this role. Roles must have an ACL. + * A Parse.Role is a local representation of a role persisted to the Parse + * cloud. + */ +var ParseRole = function (_ParseObject) { + (0, _inherits3.default)(ParseRole, _ParseObject); + + function ParseRole(name, acl) { + (0, _classCallCheck3.default)(this, ParseRole); + + var _this = (0, _possibleConstructorReturn3.default)(this, (ParseRole.__proto__ || (0, _getPrototypeOf2.default)(ParseRole)).call(this, '_Role')); + + if (typeof name === 'string' && acl instanceof _ParseACL2.default) { + _this.setName(name); + _this.setACL(acl); + } + return _this; + } + + /** + * Gets the name of the role. You can alternatively call role.get("name") + * + * @method getName + * @return {String} the name of the role. + */ + + (0, _createClass3.default)(ParseRole, [{ + key: 'getName', + value: function () { + var name = this.get('name'); + if (name == null || typeof name === 'string') { + return name; + } + return ''; + } + + /** + * Sets the name for a role. This value must be set before the role has + * been saved to the server, and cannot be set once the role has been + * saved. + * + *+ * A role's name can only contain alphanumeric characters, _, -, and + * spaces. + *
+ * + *This is equivalent to calling role.set("name", name)
+ * + * @method setName + * @param {String} name The name of the role. + * @param {Object} options Standard options object with success and error + * callbacks. + */ + + }, { + key: 'setName', + value: function (name, options) { + return this.set('name', name, options); + } + + /** + * Gets the Parse.Relation for the Parse.Users that are direct + * children of this role. These users are granted any privileges that this + * role has been granted (e.g. read or write access through ACLs). You can + * add or remove users from the role through this relation. + * + *This is equivalent to calling role.relation("users")
+ * + * @method getUsers + * @return {Parse.Relation} the relation for the users belonging to this + * role. + */ + + }, { + key: 'getUsers', + value: function () { + return this.relation('users'); + } + + /** + * Gets the Parse.Relation for the Parse.Roles that are direct + * children of this role. These roles' users are granted any privileges that + * this role has been granted (e.g. read or write access through ACLs). You + * can add or remove child roles from this role through this relation. + * + *This is equivalent to calling role.relation("roles")
+ * + * @method getRoles + * @return {Parse.Relation} the relation for the roles belonging to this + * role. + */ + + }, { + key: 'getRoles', + value: function () { + return this.relation('roles'); + } + }, { + key: 'validate', + value: function (attrs, options) { + var isInvalid = (0, _get3.default)(ParseRole.prototype.__proto__ || (0, _getPrototypeOf2.default)(ParseRole.prototype), 'validate', this).call(this, attrs, options); + if (isInvalid) { + return isInvalid; + } + + if ('name' in attrs && attrs.name !== this.getName()) { + var newName = attrs.name; + if (this.id && this.id !== attrs.objectId) { + // Check to see if the objectId being set matches this.id + // This happens during a fetch -- the id is set before calling fetch + // Let the name be set in this case + return new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'A role\'s name can only be set before it has been saved.'); + } + if (typeof newName !== 'string') { + return new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'A role\'s name must be a String.'); + } + if (!/^[0-9a-zA-Z\-_ ]+$/.test(newName)) { + return new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'A role\'s name can be only contain alphanumeric characters, _, ' + '-, and spaces.'); + } + } + return false; + } + }]); + return ParseRole; +}(_ParseObject3.default); /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +exports.default = ParseRole; + +_ParseObject3.default.registerSubclass('_Role', ParseRole); \ No newline at end of file diff --git a/lib/browser/ParseSession.js b/lib/browser/ParseSession.js new file mode 100644 index 000000000..7fb75c80d --- /dev/null +++ b/lib/browser/ParseSession.js @@ -0,0 +1,180 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = require('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +var _inherits2 = require('babel-runtime/helpers/inherits'); + +var _inherits3 = _interopRequireDefault(_inherits2); + +var _CoreManager = require('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +var _isRevocableSession = require('./isRevocableSession'); + +var _isRevocableSession2 = _interopRequireDefault(_isRevocableSession); + +var _ParseObject2 = require('./ParseObject'); + +var _ParseObject3 = _interopRequireDefault(_ParseObject2); + +var _ParsePromise = require('./ParsePromise'); + +var _ParsePromise2 = _interopRequireDefault(_ParsePromise); + +var _ParseUser = require('./ParseUser'); + +var _ParseUser2 = _interopRequireDefault(_ParseUser); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * @class Parse.Session + * @constructor + * + *A Parse.Session object is a local representation of a revocable session. + * This class is a subclass of a Parse.Object, and retains the same + * functionality of a Parse.Object.
+ */ +var ParseSession = function (_ParseObject) { + (0, _inherits3.default)(ParseSession, _ParseObject); + + function ParseSession(attributes) { + (0, _classCallCheck3.default)(this, ParseSession); + + var _this = (0, _possibleConstructorReturn3.default)(this, (ParseSession.__proto__ || (0, _getPrototypeOf2.default)(ParseSession)).call(this, '_Session')); + + if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') { + if (!_this.set(attributes || {})) { + throw new Error('Can\'t create an invalid Session'); + } + } + return _this; + } + + /** + * Returns the session token string. + * @method getSessionToken + * @return {String} + */ + + (0, _createClass3.default)(ParseSession, [{ + key: 'getSessionToken', + value: function () { + var token = this.get('sessionToken'); + if (typeof token === 'string') { + return token; + } + return ''; + } + }], [{ + key: 'readOnlyAttributes', + value: function () { + return ['createdWith', 'expiresAt', 'installationId', 'restricted', 'sessionToken', 'user']; + } + + /** + * Retrieves the Session object for the currently logged in session. + * @method current + * @static + * @return {Parse.Promise} A promise that is resolved with the Parse.Session + * object after it has been fetched. If there is no current user, the + * promise will be rejected. + */ + + }, { + key: 'current', + value: function (options) { + options = options || {}; + var controller = _CoreManager2.default.getSessionController(); + + var sessionOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + sessionOptions.useMasterKey = options.useMasterKey; + } + return _ParseUser2.default.currentAsync().then(function (user) { + if (!user) { + return _ParsePromise2.default.error('There is no current user.'); + } + user.getSessionToken(); + + sessionOptions.sessionToken = user.getSessionToken(); + return controller.getSession(sessionOptions); + }); + } + + /** + * Determines whether the current session token is revocable. + * This method is useful for migrating Express.js or Node.js web apps to + * use revocable sessions. If you are migrating an app that uses the Parse + * SDK in the browser only, please use Parse.User.enableRevocableSession() + * instead, so that sessions can be automatically upgraded. + * @method isCurrentSessionRevocable + * @static + * @return {Boolean} + */ + + }, { + key: 'isCurrentSessionRevocable', + value: function () { + var currentUser = _ParseUser2.default.current(); + if (currentUser) { + return (0, _isRevocableSession2.default)(currentUser.getSessionToken() || ''); + } + return false; + } + }]); + return ParseSession; +}(_ParseObject3.default); /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +exports.default = ParseSession; + +_ParseObject3.default.registerSubclass('_Session', ParseSession); + +var DefaultController = { + getSession: function (options) { + var RESTController = _CoreManager2.default.getRESTController(); + var session = new ParseSession(); + + return RESTController.request('GET', 'sessions/me', {}, options).then(function (sessionData) { + session._finishFetch(sessionData); + session._setExisted(true); + return session; + }); + } +}; + +_CoreManager2.default.setSessionController(DefaultController); \ No newline at end of file diff --git a/lib/browser/ParseUser.js b/lib/browser/ParseUser.js new file mode 100644 index 000000000..f18f42820 --- /dev/null +++ b/lib/browser/ParseUser.js @@ -0,0 +1,1150 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _stringify = require('babel-runtime/core-js/json/stringify'); + +var _stringify2 = _interopRequireDefault(_stringify); + +var _defineProperty = require('babel-runtime/core-js/object/define-property'); + +var _defineProperty2 = _interopRequireDefault(_defineProperty); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = require('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +var _get2 = require('babel-runtime/helpers/get'); + +var _get3 = _interopRequireDefault(_get2); + +var _inherits2 = require('babel-runtime/helpers/inherits'); + +var _inherits3 = _interopRequireDefault(_inherits2); + +var _CoreManager = require('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +var _isRevocableSession = require('./isRevocableSession'); + +var _isRevocableSession2 = _interopRequireDefault(_isRevocableSession); + +var _ParseError = require('./ParseError'); + +var _ParseError2 = _interopRequireDefault(_ParseError); + +var _ParseObject2 = require('./ParseObject'); + +var _ParseObject3 = _interopRequireDefault(_ParseObject2); + +var _ParsePromise = require('./ParsePromise'); + +var _ParsePromise2 = _interopRequireDefault(_ParsePromise); + +var _ParseSession = require('./ParseSession'); + +var _ParseSession2 = _interopRequireDefault(_ParseSession); + +var _Storage = require('./Storage'); + +var _Storage2 = _interopRequireDefault(_Storage); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +var CURRENT_USER_KEY = 'currentUser'; /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +var canUseCurrentUser = !_CoreManager2.default.get('IS_NODE'); +var currentUserCacheMatchesDisk = false; +var currentUserCache = null; + +var authProviders = {}; + +/** + * @class Parse.User + * @constructor + * + *A Parse.User object is a local representation of a user persisted to the + * Parse cloud. This class is a subclass of a Parse.Object, and retains the + * same functionality of a Parse.Object, but also extends it with various + * user specific methods, like authentication, signing up, and validation of + * uniqueness.
+ */ + +var ParseUser = function (_ParseObject) { + (0, _inherits3.default)(ParseUser, _ParseObject); + + function ParseUser(attributes) { + (0, _classCallCheck3.default)(this, ParseUser); + + var _this = (0, _possibleConstructorReturn3.default)(this, (ParseUser.__proto__ || (0, _getPrototypeOf2.default)(ParseUser)).call(this, '_User')); + + if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') { + if (!_this.set(attributes || {})) { + throw new Error('Can\'t create an invalid Parse User'); + } + } + return _this; + } + + /** + * Request a revocable session token to replace the older style of token. + * @method _upgradeToRevocableSession + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is resolved when the replacement + * token has been fetched. + */ + + (0, _createClass3.default)(ParseUser, [{ + key: '_upgradeToRevocableSession', + value: function (options) { + options = options || {}; + + var upgradeOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + upgradeOptions.useMasterKey = options.useMasterKey; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.upgradeToRevocableSession(this, upgradeOptions)._thenRunCallbacks(options); + } + + /** + * Unlike in the Android/iOS SDKs, logInWith is unnecessary, since you can + * call linkWith on the user (even if it doesn't exist yet on the server). + * @method _linkWith + */ + + }, { + key: '_linkWith', + value: function (provider, options) { + var _this2 = this; + + var authType; + if (typeof provider === 'string') { + authType = provider; + provider = authProviders[provider]; + } else { + authType = provider.getAuthType(); + } + if (options && options.hasOwnProperty('authData')) { + var authData = this.get('authData') || {}; + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + throw new Error('Invalid type: authData field should be an object'); + } + authData[authType] = options.authData; + + var controller = _CoreManager2.default.getUserController(); + return controller.linkWith(this, authData)._thenRunCallbacks(options, this); + } else { + var promise = new _ParsePromise2.default(); + provider.authenticate({ + success: function (provider, result) { + var opts = {}; + opts.authData = result; + if (options.success) { + opts.success = options.success; + } + if (options.error) { + opts.error = options.error; + } + _this2._linkWith(provider, opts).then(function () { + promise.resolve(_this2); + }, function (error) { + promise.reject(error); + }); + }, + error: function (provider, _error) { + if (typeof options.error === 'function') { + options.error(_this2, _error); + } + promise.reject(_error); + } + }); + return promise; + } + } + + /** + * Synchronizes auth data for a provider (e.g. puts the access token in the + * right place to be used by the Facebook SDK). + * @method _synchronizeAuthData + */ + + }, { + key: '_synchronizeAuthData', + value: function (provider) { + if (!this.isCurrent() || !provider) { + return; + } + var authType; + if (typeof provider === 'string') { + authType = provider; + provider = authProviders[authType]; + } else { + authType = provider.getAuthType(); + } + var authData = this.get('authData'); + if (!provider || !authData || (typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return; + } + var success = provider.restoreAuthentication(authData[authType]); + if (!success) { + this._unlinkFrom(provider); + } + } + + /** + * Synchronizes authData for all providers. + * @method _synchronizeAllAuthData + */ + + }, { + key: '_synchronizeAllAuthData', + value: function () { + var authData = this.get('authData'); + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return; + } + + for (var key in authData) { + this._synchronizeAuthData(key); + } + } + + /** + * Removes null values from authData (which exist temporarily for + * unlinking) + * @method _cleanupAuthData + */ + + }, { + key: '_cleanupAuthData', + value: function () { + if (!this.isCurrent()) { + return; + } + var authData = this.get('authData'); + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return; + } + + for (var key in authData) { + if (!authData[key]) { + delete authData[key]; + } + } + } + + /** + * Unlinks a user from a service. + * @method _unlinkFrom + */ + + }, { + key: '_unlinkFrom', + value: function (provider, options) { + var _this3 = this; + + if (typeof provider === 'string') { + provider = authProviders[provider]; + } else { + provider.getAuthType(); + } + return this._linkWith(provider, { authData: null }).then(function () { + _this3._synchronizeAuthData(provider); + return _ParsePromise2.default.as(_this3); + })._thenRunCallbacks(options); + } + + /** + * Checks whether a user is linked to a service. + * @method _isLinked + */ + + }, { + key: '_isLinked', + value: function (provider) { + var authType; + if (typeof provider === 'string') { + authType = provider; + } else { + authType = provider.getAuthType(); + } + var authData = this.get('authData') || {}; + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return false; + } + return !!authData[authType]; + } + + /** + * Deauthenticates all providers. + * @method _logOutWithAll + */ + + }, { + key: '_logOutWithAll', + value: function () { + var authData = this.get('authData'); + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return; + } + + for (var key in authData) { + this._logOutWith(key); + } + } + + /** + * Deauthenticates a single provider (e.g. removing access tokens from the + * Facebook SDK). + * @method _logOutWith + */ + + }, { + key: '_logOutWith', + value: function (provider) { + if (!this.isCurrent()) { + return; + } + if (typeof provider === 'string') { + provider = authProviders[provider]; + } + if (provider && provider.deauthenticate) { + provider.deauthenticate(); + } + } + + /** + * Class instance method used to maintain specific keys when a fetch occurs. + * Used to ensure that the session token is not lost. + */ + + }, { + key: '_preserveFieldsOnFetch', + value: function () { + return { + sessionToken: this.get('sessionToken') + }; + } + + /** + * Returns true ifcurrent would return this user.
+ * @method isCurrent
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'isCurrent',
+ value: function () {
+ var current = ParseUser.current();
+ return !!current && current.id === this.id;
+ }
+
+ /**
+ * Returns get("username").
+ * @method getUsername
+ * @return {String}
+ */
+
+ }, {
+ key: 'getUsername',
+ value: function () {
+ var username = this.get('username');
+ if (username == null || typeof username === 'string') {
+ return username;
+ }
+ return '';
+ }
+
+ /**
+ * Calls set("username", username, options) and returns the result.
+ * @method setUsername
+ * @param {String} username
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'setUsername',
+ value: function (username) {
+ // Strip anonymity, even we do not support anonymous user in js SDK, we may
+ // encounter anonymous user created by android/iOS in cloud code.
+ var authData = this.get('authData');
+ if (authData && (typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) === 'object' && authData.hasOwnProperty('anonymous')) {
+ // We need to set anonymous to null instead of deleting it in order to remove it from Parse.
+ authData.anonymous = null;
+ }
+ this.set('username', username);
+ }
+
+ /**
+ * Calls set("password", password, options) and returns the result.
+ * @method setPassword
+ * @param {String} password
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'setPassword',
+ value: function (password) {
+ this.set('password', password);
+ }
+
+ /**
+ * Returns get("email").
+ * @method getEmail
+ * @return {String}
+ */
+
+ }, {
+ key: 'getEmail',
+ value: function () {
+ var email = this.get('email');
+ if (email == null || typeof email === 'string') {
+ return email;
+ }
+ return '';
+ }
+
+ /**
+ * Calls set("email", email, options) and returns the result.
+ * @method setEmail
+ * @param {String} email
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'setEmail',
+ value: function (email) {
+ this.set('email', email);
+ }
+
+ /**
+ * Returns the session token for this user, if the user has been logged in,
+ * or if it is the result of a query with the master key. Otherwise, returns
+ * undefined.
+ * @method getSessionToken
+ * @return {String} the session token, or undefined
+ */
+
+ }, {
+ key: 'getSessionToken',
+ value: function () {
+ var token = this.get('sessionToken');
+ if (token == null || typeof token === 'string') {
+ return token;
+ }
+ return '';
+ }
+
+ /**
+ * Checks whether this user is the current user and has been authenticated.
+ * @method authenticated
+ * @return (Boolean) whether this user is the current user and is logged in.
+ */
+
+ }, {
+ key: 'authenticated',
+ value: function () {
+ var current = ParseUser.current();
+ return !!this.get('sessionToken') && !!current && current.id === this.id;
+ }
+
+ /**
+ * Signs up a new user. You should call this instead of save for
+ * new Parse.Users. This will create a new Parse.User on the server, and
+ * also persist the session on disk so that you can access the user using
+ * current.
+ *
+ * A username and password must be set before calling signUp.
+ * + *Calls options.success or options.error on completion.
+ * + * @method signUp + * @param {Object} attrs Extra fields to set on the new user, or null. + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is fulfilled when the signup + * finishes. + */ + + }, { + key: 'signUp', + value: function (attrs, options) { + options = options || {}; + + var signupOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + signupOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('installationId')) { + signupOptions.installationId = options.installationId; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.signUp(this, attrs, signupOptions)._thenRunCallbacks(options, this); + } + + /** + * Logs in a Parse.User. On success, this saves the session to disk, + * so you can retrieve the currently logged in user using + *current.
+ *
+ * A username and password must be set before calling logIn.
+ * + *Calls options.success or options.error on completion.
+ * + * @method logIn + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login is complete. + */ + + }, { + key: 'logIn', + value: function (options) { + options = options || {}; + + var loginOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + loginOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('installationId')) { + loginOptions.installationId = options.installationId; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.logIn(this, loginOptions)._thenRunCallbacks(options, this); + } + + /** + * Wrap the default save behavior with functionality to save to local + * storage if this is current user. + */ + + }, { + key: 'save', + value: function () { + var _this4 = this; + + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return (0, _get3.default)(ParseUser.prototype.__proto__ || (0, _getPrototypeOf2.default)(ParseUser.prototype), 'save', this).apply(this, args).then(function () { + if (_this4.isCurrent()) { + return _CoreManager2.default.getUserController().updateUserOnDisk(_this4); + } + return _this4; + }); + } + + /** + * Wrap the default destroy behavior with functionality that logs out + * the current user when it is destroyed + */ + + }, { + key: 'destroy', + value: function () { + var _this5 = this; + + for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + return (0, _get3.default)(ParseUser.prototype.__proto__ || (0, _getPrototypeOf2.default)(ParseUser.prototype), 'destroy', this).apply(this, args).then(function () { + if (_this5.isCurrent()) { + return _CoreManager2.default.getUserController().removeUserFromDisk(); + } + return _this5; + }); + } + + /** + * Wrap the default fetch behavior with functionality to save to local + * storage if this is current user. + */ + + }, { + key: 'fetch', + value: function () { + var _this6 = this; + + for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + args[_key3] = arguments[_key3]; + } + + return (0, _get3.default)(ParseUser.prototype.__proto__ || (0, _getPrototypeOf2.default)(ParseUser.prototype), 'fetch', this).apply(this, args).then(function () { + if (_this6.isCurrent()) { + return _CoreManager2.default.getUserController().updateUserOnDisk(_this6); + } + return _this6; + }); + } + }], [{ + key: 'readOnlyAttributes', + value: function () { + return ['sessionToken']; + } + + /** + * Adds functionality to the existing Parse.User class + * @method extend + * @param {Object} protoProps A set of properties to add to the prototype + * @param {Object} classProps A set of static properties to add to the class + * @static + * @return {Class} The newly extended Parse.User class + */ + + }, { + key: 'extend', + value: function (protoProps, classProps) { + if (protoProps) { + for (var prop in protoProps) { + if (prop !== 'className') { + (0, _defineProperty2.default)(ParseUser.prototype, prop, { + value: protoProps[prop], + enumerable: false, + writable: true, + configurable: true + }); + } + } + } + + if (classProps) { + for (var prop in classProps) { + if (prop !== 'className') { + (0, _defineProperty2.default)(ParseUser, prop, { + value: classProps[prop], + enumerable: false, + writable: true, + configurable: true + }); + } + } + } + + return ParseUser; + } + + /** + * Retrieves the currently logged in ParseUser with a valid session, + * either from memory or localStorage, if necessary. + * @method current + * @static + * @return {Parse.Object} The currently logged in Parse.User. + */ + + }, { + key: 'current', + value: function () { + if (!canUseCurrentUser) { + return null; + } + var controller = _CoreManager2.default.getUserController(); + return controller.currentUser(); + } + + /** + * Retrieves the currently logged in ParseUser from asynchronous Storage. + * @method currentAsync + * @static + * @return {Parse.Promise} A Promise that is resolved with the currently + * logged in Parse User + */ + + }, { + key: 'currentAsync', + value: function () { + if (!canUseCurrentUser) { + return _ParsePromise2.default.as(null); + } + var controller = _CoreManager2.default.getUserController(); + return controller.currentUserAsync(); + } + + /** + * Signs up a new user with a username (or email) and password. + * This will create a new Parse.User on the server, and also persist the + * session in localStorage so that you can access the user using + * {@link #current}. + * + *Calls options.success or options.error on completion.
+ * + * @method signUp + * @param {String} username The username (or email) to sign up with. + * @param {String} password The password to sign up with. + * @param {Object} attrs Extra fields to set on the new user. + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the signup completes. + */ + + }, { + key: 'signUp', + value: function (username, password, attrs, options) { + attrs = attrs || {}; + attrs.username = username; + attrs.password = password; + var user = new ParseUser(attrs); + return user.signUp({}, options); + } + + /** + * Logs in a user with a username (or email) and password. On success, this + * saves the session to disk, so you can retrieve the currently logged in + * user usingcurrent.
+ *
+ * Calls options.success or options.error on completion.
+ * + * @method logIn + * @param {String} username The username (or email) to log in with. + * @param {String} password The password to log in with. + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login completes. + */ + + }, { + key: 'logIn', + value: function (username, password, options) { + if (typeof username !== 'string') { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Username must be a string.')); + } else if (typeof password !== 'string') { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Password must be a string.')); + } + var user = new ParseUser(); + user._finishFetch({ username: username, password: password }); + return user.logIn(options); + } + + /** + * Logs in a user with a session token. On success, this saves the session + * to disk, so you can retrieve the currently logged in user using + *current.
+ *
+ * Calls options.success or options.error on completion.
+ * + * @method become + * @param {String} sessionToken The sessionToken to log in with. + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login completes. + */ + + }, { + key: 'become', + value: function (sessionToken, options) { + if (!canUseCurrentUser) { + throw new Error('It is not memory-safe to become a user in a server environment'); + } + options = options || {}; + + var becomeOptions = { + sessionToken: sessionToken + }; + if (options.hasOwnProperty('useMasterKey')) { + becomeOptions.useMasterKey = options.useMasterKey; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.become(becomeOptions)._thenRunCallbacks(options); + } + }, { + key: 'logInWith', + value: function (provider, options) { + return ParseUser._logInWith(provider, options); + } + + /** + * Logs out the currently logged in user session. This will remove the + * session from disk, log out of linked services, and future calls to + *current will return null.
+ * @method logOut
+ * @static
+ * @return {Parse.Promise} A promise that is resolved when the session is
+ * destroyed on the server.
+ */
+
+ }, {
+ key: 'logOut',
+ value: function () {
+ if (!canUseCurrentUser) {
+ throw new Error('There is no current user user on a node.js server environment.');
+ }
+
+ var controller = _CoreManager2.default.getUserController();
+ return controller.logOut();
+ }
+
+ /**
+ * Requests a password reset email to be sent to the specified email address
+ * associated with the user account. This email allows the user to securely
+ * reset their password on the Parse site.
+ *
+ * Calls options.success or options.error on completion.
+ * + * @method requestPasswordReset + * @param {String} email The email address associated with the user that + * forgot their password. + * @param {Object} options A Backbone-style options object. + * @static + */ + + }, { + key: 'requestPasswordReset', + value: function (email, options) { + options = options || {}; + + var requestOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + requestOptions.useMasterKey = options.useMasterKey; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.requestPasswordReset(email, requestOptions)._thenRunCallbacks(options); + } + + /** + * Allow someone to define a custom User class without className + * being rewritten to _User. The default behavior is to rewrite + * User to _User for legacy reasons. This allows developers to + * override that behavior. + * + * @method allowCustomUserClass + * @param {Boolean} isAllowed Whether or not to allow custom User class + * @static + */ + + }, { + key: 'allowCustomUserClass', + value: function (isAllowed) { + _CoreManager2.default.set('PERFORM_USER_REWRITE', !isAllowed); + } + + /** + * Allows a legacy application to start using revocable sessions. If the + * current session token is not revocable, a request will be made for a new, + * revocable session. + * It is not necessary to call this method from cloud code unless you are + * handling user signup or login from the server side. In a cloud code call, + * this function will not attempt to upgrade the current token. + * @method enableRevocableSession + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is resolved when the process has + * completed. If a replacement session token is requested, the promise + * will be resolved after a new token has been fetched. + */ + + }, { + key: 'enableRevocableSession', + value: function (options) { + options = options || {}; + _CoreManager2.default.set('FORCE_REVOCABLE_SESSION', true); + if (canUseCurrentUser) { + var current = ParseUser.current(); + if (current) { + return current._upgradeToRevocableSession(options); + } + } + return _ParsePromise2.default.as()._thenRunCallbacks(options); + } + + /** + * Enables the use of become or the current user in a server + * environment. These features are disabled by default, since they depend on + * global objects that are not memory-safe for most servers. + * @method enableUnsafeCurrentUser + * @static + */ + + }, { + key: 'enableUnsafeCurrentUser', + value: function () { + canUseCurrentUser = true; + } + + /** + * Disables the use of become or the current user in any environment. + * These features are disabled on servers by default, since they depend on + * global objects that are not memory-safe for most servers. + * @method disableUnsafeCurrentUser + * @static + */ + + }, { + key: 'disableUnsafeCurrentUser', + value: function () { + canUseCurrentUser = false; + } + }, { + key: '_registerAuthenticationProvider', + value: function (provider) { + authProviders[provider.getAuthType()] = provider; + // Synchronize the current user with the auth provider. + ParseUser.currentAsync().then(function (current) { + if (current) { + current._synchronizeAuthData(provider.getAuthType()); + } + }); + } + }, { + key: '_logInWith', + value: function (provider, options) { + var user = new ParseUser(); + return user._linkWith(provider, options); + } + }, { + key: '_clearCache', + value: function () { + currentUserCache = null; + currentUserCacheMatchesDisk = false; + } + }, { + key: '_setCurrentUserCache', + value: function (user) { + currentUserCache = user; + } + }]); + return ParseUser; +}(_ParseObject3.default); + +exports.default = ParseUser; + +_ParseObject3.default.registerSubclass('_User', ParseUser); + +var DefaultController = { + updateUserOnDisk: function (user) { + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + var json = user.toJSON(); + json.className = '_User'; + return _Storage2.default.setItemAsync(path, (0, _stringify2.default)(json)).then(function () { + return user; + }); + }, + removeUserFromDisk: function () { + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + currentUserCacheMatchesDisk = true; + currentUserCache = null; + return _Storage2.default.removeItemAsync(path); + }, + setCurrentUser: function (user) { + currentUserCache = user; + user._cleanupAuthData(); + user._synchronizeAllAuthData(); + return DefaultController.updateUserOnDisk(user); + }, + currentUser: function () { + if (currentUserCache) { + return currentUserCache; + } + if (currentUserCacheMatchesDisk) { + return null; + } + if (_Storage2.default.async()) { + throw new Error('Cannot call currentUser() when using a platform with an async ' + 'storage system. Call currentUserAsync() instead.'); + } + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + var userData = _Storage2.default.getItem(path); + currentUserCacheMatchesDisk = true; + if (!userData) { + currentUserCache = null; + return null; + } + userData = JSON.parse(userData); + if (!userData.className) { + userData.className = '_User'; + } + if (userData._id) { + if (userData.objectId !== userData._id) { + userData.objectId = userData._id; + } + delete userData._id; + } + if (userData._sessionToken) { + userData.sessionToken = userData._sessionToken; + delete userData._sessionToken; + } + var current = _ParseObject3.default.fromJSON(userData); + currentUserCache = current; + current._synchronizeAllAuthData(); + return current; + }, + currentUserAsync: function () { + if (currentUserCache) { + return _ParsePromise2.default.as(currentUserCache); + } + if (currentUserCacheMatchesDisk) { + return _ParsePromise2.default.as(null); + } + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + return _Storage2.default.getItemAsync(path).then(function (userData) { + currentUserCacheMatchesDisk = true; + if (!userData) { + currentUserCache = null; + return _ParsePromise2.default.as(null); + } + userData = JSON.parse(userData); + if (!userData.className) { + userData.className = '_User'; + } + if (userData._id) { + if (userData.objectId !== userData._id) { + userData.objectId = userData._id; + } + delete userData._id; + } + if (userData._sessionToken) { + userData.sessionToken = userData._sessionToken; + delete userData._sessionToken; + } + var current = _ParseObject3.default.fromJSON(userData); + currentUserCache = current; + current._synchronizeAllAuthData(); + return _ParsePromise2.default.as(current); + }); + }, + signUp: function (user, attrs, options) { + var username = attrs && attrs.username || user.get('username'); + var password = attrs && attrs.password || user.get('password'); + + if (!username || !username.length) { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Cannot sign up user with an empty name.')); + } + if (!password || !password.length) { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Cannot sign up user with an empty password.')); + } + + return user.save(attrs, options).then(function () { + // Clear the password field + user._finishFetch({ password: undefined }); + + if (canUseCurrentUser) { + return DefaultController.setCurrentUser(user); + } + return user; + }); + }, + logIn: function (user, options) { + var RESTController = _CoreManager2.default.getRESTController(); + var stateController = _CoreManager2.default.getObjectStateController(); + var auth = { + username: user.get('username'), + password: user.get('password') + }; + return RESTController.request('GET', 'login', auth, options).then(function (response, status) { + user._migrateId(response.objectId); + user._setExisted(true); + stateController.setPendingOp(user._getStateIdentifier(), 'username', undefined); + stateController.setPendingOp(user._getStateIdentifier(), 'password', undefined); + response.password = undefined; + user._finishFetch(response); + if (!canUseCurrentUser) { + // We can't set the current user, so just return the one we logged in + return _ParsePromise2.default.as(user); + } + return DefaultController.setCurrentUser(user); + }); + }, + become: function (options) { + var user = new ParseUser(); + var RESTController = _CoreManager2.default.getRESTController(); + return RESTController.request('GET', 'users/me', {}, options).then(function (response, status) { + user._finishFetch(response); + user._setExisted(true); + return DefaultController.setCurrentUser(user); + }); + }, + logOut: function () { + return DefaultController.currentUserAsync().then(function (currentUser) { + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + var promise = _Storage2.default.removeItemAsync(path); + var RESTController = _CoreManager2.default.getRESTController(); + if (currentUser !== null) { + var currentSession = currentUser.getSessionToken(); + if (currentSession && (0, _isRevocableSession2.default)(currentSession)) { + promise = promise.then(function () { + return RESTController.request('POST', 'logout', {}, { sessionToken: currentSession }); + }); + } + currentUser._logOutWithAll(); + currentUser._finishFetch({ sessionToken: undefined }); + } + currentUserCacheMatchesDisk = true; + currentUserCache = null; + + return promise; + }); + }, + requestPasswordReset: function (email, options) { + var RESTController = _CoreManager2.default.getRESTController(); + return RESTController.request('POST', 'requestPasswordReset', { email: email }, options); + }, + upgradeToRevocableSession: function (user, options) { + var token = user.getSessionToken(); + if (!token) { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.SESSION_MISSING, 'Cannot upgrade a user with no session token')); + } + + options.sessionToken = token; + + var RESTController = _CoreManager2.default.getRESTController(); + return RESTController.request('POST', 'upgradeToRevocableSession', {}, options).then(function (result) { + var session = new _ParseSession2.default(); + session._finishFetch(result); + user._finishFetch({ sessionToken: session.getSessionToken() }); + if (user.isCurrent()) { + return DefaultController.setCurrentUser(user); + } + return _ParsePromise2.default.as(user); + }); + }, + linkWith: function (user, authData) { + return user.save({ authData: authData }).then(function () { + if (canUseCurrentUser) { + return DefaultController.setCurrentUser(user); + } + return user; + }); + } +}; + +_CoreManager2.default.setUserController(DefaultController); \ No newline at end of file diff --git a/lib/browser/Push.js b/lib/browser/Push.js new file mode 100644 index 000000000..cfb740767 --- /dev/null +++ b/lib/browser/Push.js @@ -0,0 +1,98 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +exports.send = send; + +var _CoreManager = require('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +var _ParseQuery = require('./ParseQuery'); + +var _ParseQuery2 = _interopRequireDefault(_ParseQuery); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Contains functions to deal with Push in Parse. + * @class Parse.Push + * @static + */ + +/** + * Sends a push notification. + * @method send + * @param {Object} data - The data of the push notification. Valid fields + * are: + *
+ * var dimensions = {
+ * gender: 'm',
+ * source: 'web',
+ * dayType: 'weekend'
+ * };
+ * Parse.Analytics.track('signup', dimensions);
+ *
+ *
+ * There is a default limit of 8 dimensions per event tracked.
+ *
+ * @method track
+ * @param {String} name The name of the custom event to report to Parse as
+ * having happened.
+ * @param {Object} dimensions The dictionary of information by which to
+ * segment this event.
+ * @param {Object} options A Backbone-style callback object.
+ * @return {Parse.Promise} A promise that is resolved when the round-trip
+ * to the server completes.
+ */
+function track(name, dimensions, options) {
+ name = name || '';
+ name = name.replace(/^\s*/, '');
+ name = name.replace(/\s*$/, '');
+ if (name.length === 0) {
+ throw new TypeError('A name for the custom event must be provided');
+ }
+
+ for (var key in dimensions) {
+ if (typeof key !== 'string' || typeof dimensions[key] !== 'string') {
+ throw new TypeError('track() dimensions expects keys and values of type "string".');
+ }
+ }
+
+ options = options || {};
+ return _CoreManager2.default.getAnalyticsController().track(name, dimensions)._thenRunCallbacks(options);
+} /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+var DefaultController = {
+ track: function (name, dimensions) {
+ var RESTController = _CoreManager2.default.getRESTController();
+ return RESTController.request('POST', 'events/' + name, { dimensions: dimensions });
+ }
+};
+
+_CoreManager2.default.setAnalyticsController(DefaultController);
\ No newline at end of file
diff --git a/lib/node/Cloud.js b/lib/node/Cloud.js
new file mode 100644
index 000000000..72c333750
--- /dev/null
+++ b/lib/node/Cloud.js
@@ -0,0 +1,109 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.run = run;
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _decode = require('./decode');
+
+var _decode2 = _interopRequireDefault(_decode);
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _ParseError = require('./ParseError');
+
+var _ParseError2 = _interopRequireDefault(_ParseError);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Contains functions for calling and declaring
+ * cloud functions.
+ * + * Some functions are only available from Cloud Code. + *
+ * + * @class Parse.Cloud + * @static + */ + +/** + * Makes a call to a cloud function. + * @method run + * @param {String} name The function name. + * @param {Object} data The parameters to send to the cloud function. + * @param {Object} options A Backbone-style options object + * options.success, if set, should be a function to handle a successful + * call to a cloud function. options.error should be a function that + * handles an error running the cloud function. Both functions are + * optional. Both functions take a single argument. + * @return {Parse.Promise} A promise that will be resolved with the result + * of the function. + */ +function run(name, data, options) { + options = options || {}; + + if (typeof name !== 'string' || name.length === 0) { + throw new TypeError('Cloud function name must be a string.'); + } + + var requestOptions = {}; + if (options.useMasterKey) { + requestOptions.useMasterKey = options.useMasterKey; + } + if (options.sessionToken) { + requestOptions.sessionToken = options.sessionToken; + } + + return _CoreManager2.default.getCloudController().run(name, data, requestOptions)._thenRunCallbacks(options); +} /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +var DefaultController = { + run: function (name, data, options) { + var RESTController = _CoreManager2.default.getRESTController(); + + var payload = (0, _encode2.default)(data, true); + + var requestOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + requestOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + requestOptions.sessionToken = options.sessionToken; + } + + var request = RESTController.request('POST', 'functions/' + name, payload, requestOptions); + + return request.then(function (res) { + var decoded = (0, _decode2.default)(res); + if (decoded && decoded.hasOwnProperty('result')) { + return _ParsePromise2.default.as(decoded.result); + } + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.INVALID_JSON, 'The server returned an invalid response.')); + })._thenRunCallbacks(options); + } +}; + +_CoreManager2.default.setCloudController(DefaultController); \ No newline at end of file diff --git a/lib/node/CoreManager.js b/lib/node/CoreManager.js new file mode 100644 index 000000000..20d66a5f1 --- /dev/null +++ b/lib/node/CoreManager.js @@ -0,0 +1,161 @@ +'use strict'; + +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +var config = { + // Defaults + IS_NODE: typeof process !== 'undefined' && !!process.versions && !!process.versions.node && !process.versions.electron, + REQUEST_ATTEMPT_LIMIT: 5, + SERVER_URL: 'https://api.parse.com/1', + LIVEQUERY_SERVER_URL: null, + VERSION: 'js' + '1.9.2', + APPLICATION_ID: null, + JAVASCRIPT_KEY: null, + MASTER_KEY: null, + USE_MASTER_KEY: false, + PERFORM_USER_REWRITE: true, + FORCE_REVOCABLE_SESSION: false +}; + +function requireMethods(name, methods, controller) { + methods.forEach(function (func) { + if (typeof controller[func] !== 'function') { + throw new Error(name + ' must implement ' + func + '()'); + } + }); +} + +module.exports = { + get: function (key) { + if (config.hasOwnProperty(key)) { + return config[key]; + } + throw new Error('Configuration key not found: ' + key); + }, + + set: function (key, value) { + config[key] = value; + }, + + /* Specialized Controller Setters/Getters */ + + setAnalyticsController: function (controller) { + requireMethods('AnalyticsController', ['track'], controller); + config['AnalyticsController'] = controller; + }, + getAnalyticsController: function () { + return config['AnalyticsController']; + }, + setCloudController: function (controller) { + requireMethods('CloudController', ['run'], controller); + config['CloudController'] = controller; + }, + getCloudController: function () { + return config['CloudController']; + }, + setConfigController: function (controller) { + requireMethods('ConfigController', ['current', 'get'], controller); + config['ConfigController'] = controller; + }, + getConfigController: function () { + return config['ConfigController']; + }, + setFileController: function (controller) { + requireMethods('FileController', ['saveFile', 'saveBase64'], controller); + config['FileController'] = controller; + }, + getFileController: function () { + return config['FileController']; + }, + setInstallationController: function (controller) { + requireMethods('InstallationController', ['currentInstallationId'], controller); + config['InstallationController'] = controller; + }, + getInstallationController: function () { + return config['InstallationController']; + }, + setObjectController: function (controller) { + requireMethods('ObjectController', ['save', 'fetch', 'destroy'], controller); + config['ObjectController'] = controller; + }, + getObjectController: function () { + return config['ObjectController']; + }, + setObjectStateController: function (controller) { + requireMethods('ObjectStateController', ['getState', 'initializeState', 'removeState', 'getServerData', 'setServerData', 'getPendingOps', 'setPendingOp', 'pushPendingState', 'popPendingState', 'mergeFirstPendingState', 'getObjectCache', 'estimateAttribute', 'estimateAttributes', 'commitServerChanges', 'enqueueTask', 'clearAllState'], controller); + + config['ObjectStateController'] = controller; + }, + getObjectStateController: function () { + return config['ObjectStateController']; + }, + setPushController: function (controller) { + requireMethods('PushController', ['send'], controller); + config['PushController'] = controller; + }, + getPushController: function () { + return config['PushController']; + }, + setQueryController: function (controller) { + requireMethods('QueryController', ['find'], controller); + config['QueryController'] = controller; + }, + getQueryController: function () { + return config['QueryController']; + }, + setRESTController: function (controller) { + requireMethods('RESTController', ['request', 'ajax'], controller); + config['RESTController'] = controller; + }, + getRESTController: function () { + return config['RESTController']; + }, + setSessionController: function (controller) { + requireMethods('SessionController', ['getSession'], controller); + config['SessionController'] = controller; + }, + getSessionController: function () { + return config['SessionController']; + }, + setStorageController: function (controller) { + if (controller.async) { + requireMethods('An async StorageController', ['getItemAsync', 'setItemAsync', 'removeItemAsync'], controller); + } else { + requireMethods('A synchronous StorageController', ['getItem', 'setItem', 'removeItem'], controller); + } + config['StorageController'] = controller; + }, + getStorageController: function () { + return config['StorageController']; + }, + setUserController: function (controller) { + requireMethods('UserController', ['setCurrentUser', 'currentUser', 'currentUserAsync', 'signUp', 'logIn', 'become', 'logOut', 'requestPasswordReset', 'upgradeToRevocableSession', 'linkWith'], controller); + config['UserController'] = controller; + }, + getUserController: function () { + return config['UserController']; + }, + setLiveQueryController: function (controller) { + requireMethods('LiveQueryController', ['subscribe', 'unsubscribe', 'open', 'close'], controller); + config['LiveQueryController'] = controller; + }, + getLiveQueryController: function () { + return config['LiveQueryController']; + }, + setHooksController: function (controller) { + requireMethods('HooksController', ['create', 'get', 'update', 'remove'], controller); + config['HooksController'] = controller; + }, + getHooksController: function () { + return config['HooksController']; + } +}; \ No newline at end of file diff --git a/lib/node/EventEmitter.js b/lib/node/EventEmitter.js new file mode 100644 index 000000000..e9414f0bf --- /dev/null +++ b/lib/node/EventEmitter.js @@ -0,0 +1,15 @@ +'use strict'; + +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * This is a simple wrapper to unify EventEmitter implementations across platforms. + */ + +module.exports = require('events').EventEmitter; +var EventEmitter; \ No newline at end of file diff --git a/lib/node/FacebookUtils.js b/lib/node/FacebookUtils.js new file mode 100644 index 000000000..bb8b7ef51 --- /dev/null +++ b/lib/node/FacebookUtils.js @@ -0,0 +1,243 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _parseDate = require('./parseDate'); + +var _parseDate2 = _interopRequireDefault(_parseDate); + +var _ParseUser = require('./ParseUser'); + +var _ParseUser2 = _interopRequireDefault(_ParseUser); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * -weak + */ + +var PUBLIC_KEY = "*"; + +var initialized = false; +var requestedPermissions; +var initOptions; +var provider = { + authenticate: function (options) { + var _this = this; + + if (typeof FB === 'undefined') { + options.error(this, 'Facebook SDK not found.'); + } + FB.login(function (response) { + if (response.authResponse) { + if (options.success) { + options.success(_this, { + id: response.authResponse.userID, + access_token: response.authResponse.accessToken, + expiration_date: new Date(response.authResponse.expiresIn * 1000 + new Date().getTime()).toJSON() + }); + } + } else { + if (options.error) { + options.error(_this, response); + } + } + }, { + scope: requestedPermissions + }); + }, + restoreAuthentication: function (authData) { + if (authData) { + var expiration = (0, _parseDate2.default)(authData.expiration_date); + var expiresIn = expiration ? (expiration.getTime() - new Date().getTime()) / 1000 : 0; + + var authResponse = { + userID: authData.id, + accessToken: authData.access_token, + expiresIn: expiresIn + }; + var newOptions = {}; + if (initOptions) { + for (var key in initOptions) { + newOptions[key] = initOptions[key]; + } + } + newOptions.authResponse = authResponse; + + // Suppress checks for login status from the browser. + newOptions.status = false; + + // If the user doesn't match the one known by the FB SDK, log out. + // Most of the time, the users will match -- it's only in cases where + // the FB SDK knows of a different user than the one being restored + // from a Parse User that logged in with username/password. + var existingResponse = FB.getAuthResponse(); + if (existingResponse && existingResponse.userID !== authResponse.userID) { + FB.logout(); + } + + FB.init(newOptions); + } + return true; + }, + getAuthType: function () { + return 'facebook'; + }, + deauthenticate: function () { + this.restoreAuthentication(null); + } +}; + +/** + * Provides a set of utilities for using Parse with Facebook. + * @class Parse.FacebookUtils + * @static + */ +var FacebookUtils = { + /** + * Initializes Parse Facebook integration. Call this function after you + * have loaded the Facebook Javascript SDK with the same parameters + * as you would pass to
+ *
+ * FB.init(). Parse.FacebookUtils will invoke FB.init() for you
+ * with these arguments.
+ *
+ * @method init
+ * @param {Object} options Facebook options argument as described here:
+ *
+ * FB.init(). The status flag will be coerced to 'false' because it
+ * interferes with Parse Facebook integration. Call FB.getLoginStatus()
+ * explicitly if this behavior is required by your application.
+ */
+ init: function (options) {
+ if (typeof FB === 'undefined') {
+ throw new Error('The Facebook JavaScript SDK must be loaded before calling init.');
+ }
+ initOptions = {};
+ if (options) {
+ for (var key in options) {
+ initOptions[key] = options[key];
+ }
+ }
+ if (initOptions.status && typeof console !== 'undefined') {
+ var warn = console.warn || console.log || function () {};
+ warn.call(console, 'The "status" flag passed into' + ' FB.init, when set to true, can interfere with Parse Facebook' + ' integration, so it has been suppressed. Please call' + ' FB.getLoginStatus() explicitly if you require this behavior.');
+ }
+ initOptions.status = false;
+ FB.init(initOptions);
+ _ParseUser2.default._registerAuthenticationProvider(provider);
+ initialized = true;
+ },
+
+ /**
+ * Gets whether the user has their account linked to Facebook.
+ *
+ * @method isLinked
+ * @param {Parse.User} user User to check for a facebook link.
+ * The user must be logged in on this device.
+ * @return {Boolean} true if the user has their account
+ * linked to Facebook.
+ */
+ isLinked: function (user) {
+ return user._isLinked('facebook');
+ },
+
+ /**
+ * Logs in a user using Facebook. This method delegates to the Facebook
+ * SDK to authenticate the user, and then automatically logs in (or
+ * creates, in the case where it is a new user) a Parse.User.
+ *
+ * @method logIn
+ * @param {String, Object} permissions The permissions required for Facebook
+ * log in. This is a comma-separated string of permissions.
+ * Alternatively, supply a Facebook authData object as described in our
+ * REST API docs if you want to handle getting facebook auth tokens
+ * yourself.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ logIn: function (permissions, options) {
+ if (!permissions || typeof permissions === 'string') {
+ if (!initialized) {
+ throw new Error('You must initialize FacebookUtils before calling logIn.');
+ }
+ requestedPermissions = permissions;
+ return _ParseUser2.default._logInWith('facebook', options);
+ } else {
+ var newOptions = {};
+ if (options) {
+ for (var key in options) {
+ newOptions[key] = options[key];
+ }
+ }
+ newOptions.authData = permissions;
+ return _ParseUser2.default._logInWith('facebook', newOptions);
+ }
+ },
+
+ /**
+ * Links Facebook to an existing PFUser. This method delegates to the
+ * Facebook SDK to authenticate the user, and then automatically links
+ * the account to the Parse.User.
+ *
+ * @method link
+ * @param {Parse.User} user User to link to Facebook. This must be the
+ * current user.
+ * @param {String, Object} permissions The permissions required for Facebook
+ * log in. This is a comma-separated string of permissions.
+ * Alternatively, supply a Facebook authData object as described in our
+ * REST API docs if you want to handle getting facebook auth tokens
+ * yourself.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ link: function (user, permissions, options) {
+ if (!permissions || typeof permissions === 'string') {
+ if (!initialized) {
+ throw new Error('You must initialize FacebookUtils before calling link.');
+ }
+ requestedPermissions = permissions;
+ return user._linkWith('facebook', options);
+ } else {
+ var newOptions = {};
+ if (options) {
+ for (var key in options) {
+ newOptions[key] = options[key];
+ }
+ }
+ newOptions.authData = permissions;
+ return user._linkWith('facebook', newOptions);
+ }
+ },
+
+ /**
+ * Unlinks the Parse.User from a Facebook account.
+ *
+ * @method unlink
+ * @param {Parse.User} user User to unlink from Facebook. This must be the
+ * current user.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ unlink: function (user, options) {
+ if (!initialized) {
+ throw new Error('You must initialize FacebookUtils before calling unlink.');
+ }
+ return user._unlinkFrom('facebook', options);
+ }
+};
+
+exports.default = FacebookUtils;
\ No newline at end of file
diff --git a/lib/node/InstallationController.js b/lib/node/InstallationController.js
new file mode 100644
index 000000000..7b01e3cad
--- /dev/null
+++ b/lib/node/InstallationController.js
@@ -0,0 +1,64 @@
+'use strict';
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+var _Storage = require('./Storage');
+
+var _Storage2 = _interopRequireDefault(_Storage);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+var iidCache = null; /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function hexOctet() {
+ return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
+}
+
+function generateId() {
+ return hexOctet() + hexOctet() + '-' + hexOctet() + '-' + hexOctet() + '-' + hexOctet() + '-' + hexOctet() + hexOctet() + hexOctet();
+}
+
+var InstallationController = {
+ currentInstallationId: function () {
+ if (typeof iidCache === 'string') {
+ return _ParsePromise2.default.as(iidCache);
+ }
+ var path = _Storage2.default.generatePath('installationId');
+ return _Storage2.default.getItemAsync(path).then(function (iid) {
+ if (!iid) {
+ iid = generateId();
+ return _Storage2.default.setItemAsync(path, iid).then(function () {
+ iidCache = iid;
+ return iid;
+ });
+ }
+ iidCache = iid;
+ return iid;
+ });
+ },
+ _clearCache: function () {
+ iidCache = null;
+ },
+ _setInstallationIdCache: function (iid) {
+ iidCache = iid;
+ }
+};
+
+module.exports = InstallationController;
\ No newline at end of file
diff --git a/lib/node/LiveQueryClient.js b/lib/node/LiveQueryClient.js
new file mode 100644
index 000000000..e0420323f
--- /dev/null
+++ b/lib/node/LiveQueryClient.js
@@ -0,0 +1,595 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _getIterator2 = require('babel-runtime/core-js/get-iterator');
+
+var _getIterator3 = _interopRequireDefault(_getIterator2);
+
+var _stringify = require('babel-runtime/core-js/json/stringify');
+
+var _stringify2 = _interopRequireDefault(_stringify);
+
+var _map = require('babel-runtime/core-js/map');
+
+var _map2 = _interopRequireDefault(_map);
+
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _EventEmitter2 = require('./EventEmitter');
+
+var _EventEmitter3 = _interopRequireDefault(_EventEmitter2);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+var _ParseObject = require('./ParseObject');
+
+var _ParseObject2 = _interopRequireDefault(_ParseObject);
+
+var _LiveQuerySubscription = require('./LiveQuerySubscription');
+
+var _LiveQuerySubscription2 = _interopRequireDefault(_LiveQuerySubscription);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+// The LiveQuery client inner state
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+var CLIENT_STATE = {
+ INITIALIZED: 'initialized',
+ CONNECTING: 'connecting',
+ CONNECTED: 'connected',
+ CLOSED: 'closed',
+ RECONNECTING: 'reconnecting',
+ DISCONNECTED: 'disconnected'
+};
+
+// The event type the LiveQuery client should sent to server
+var OP_TYPES = {
+ CONNECT: 'connect',
+ SUBSCRIBE: 'subscribe',
+ UNSUBSCRIBE: 'unsubscribe',
+ ERROR: 'error'
+};
+
+// The event we get back from LiveQuery server
+var OP_EVENTS = {
+ CONNECTED: 'connected',
+ SUBSCRIBED: 'subscribed',
+ UNSUBSCRIBED: 'unsubscribed',
+ ERROR: 'error',
+ CREATE: 'create',
+ UPDATE: 'update',
+ ENTER: 'enter',
+ LEAVE: 'leave',
+ DELETE: 'delete'
+};
+
+// The event the LiveQuery client should emit
+var CLIENT_EMMITER_TYPES = {
+ CLOSE: 'close',
+ ERROR: 'error',
+ OPEN: 'open'
+};
+
+// The event the LiveQuery subscription should emit
+var SUBSCRIPTION_EMMITER_TYPES = {
+ OPEN: 'open',
+ CLOSE: 'close',
+ ERROR: 'error',
+ CREATE: 'create',
+ UPDATE: 'update',
+ ENTER: 'enter',
+ LEAVE: 'leave',
+ DELETE: 'delete'
+};
+
+var generateInterval = function (k) {
+ return Math.random() * Math.min(30, Math.pow(2, k) - 1) * 1000;
+};
+
+/**
+ * Creates a new LiveQueryClient.
+ * Extends events.EventEmitter
+ * cloud functions.
+ *
+ * A wrapper of a standard WebSocket client. We add several useful methods to
+ * help you connect/disconnect to LiveQueryServer, subscribe/unsubscribe a ParseQuery easily.
+ *
+ * javascriptKey and masterKey are used for verifying the LiveQueryClient when it tries
+ * to connect to the LiveQuery server
+ *
+ * @class Parse.LiveQueryClient
+ * @constructor
+ * @param {Object} options
+ * @param {string} options.applicationId - applicationId of your Parse app
+ * @param {string} options.serverURL - the URL of your LiveQuery server
+ * @param {string} options.javascriptKey (optional)
+ * @param {string} options.masterKey (optional) Your Parse Master Key. (Node.js only!)
+ * @param {string} options.sessionToken (optional)
+ *
+ *
+ * We expose three events to help you monitor the status of the LiveQueryClient.
+ *
+ *
+ * let Parse = require('parse/node');
+ * let LiveQueryClient = Parse.LiveQueryClient;
+ * let client = new LiveQueryClient({
+ * applicationId: '',
+ * serverURL: '',
+ * javascriptKey: '',
+ * masterKey: ''
+ * });
+ *
+ *
+ * Open - When we establish the WebSocket connection to the LiveQuery server, you'll get this event.
+ *
+ * client.on('open', () => {
+ *
+ * });
+ *
+ * Close - When we lose the WebSocket connection to the LiveQuery server, you'll get this event.
+ *
+ * client.on('close', () => {
+ *
+ * });
+ *
+ * Error - When some network error or LiveQuery server error happens, you'll get this event.
+ *
+ * client.on('error', (error) => {
+ *
+ * });
+ *
+ *
+ */
+
+var LiveQueryClient = function (_EventEmitter) {
+ (0, _inherits3.default)(LiveQueryClient, _EventEmitter);
+
+ function LiveQueryClient(_ref) {
+ var applicationId = _ref.applicationId,
+ serverURL = _ref.serverURL,
+ javascriptKey = _ref.javascriptKey,
+ masterKey = _ref.masterKey,
+ sessionToken = _ref.sessionToken;
+ (0, _classCallCheck3.default)(this, LiveQueryClient);
+
+ var _this = (0, _possibleConstructorReturn3.default)(this, (LiveQueryClient.__proto__ || (0, _getPrototypeOf2.default)(LiveQueryClient)).call(this));
+
+ if (!serverURL || serverURL.indexOf('ws') !== 0) {
+ throw new Error('You need to set a proper Parse LiveQuery server url before using LiveQueryClient');
+ }
+
+ _this.reconnectHandle = null;
+ _this.attempts = 1;;
+ _this.id = 0;
+ _this.requestId = 1;
+ _this.serverURL = serverURL;
+ _this.applicationId = applicationId;
+ _this.javascriptKey = javascriptKey;
+ _this.masterKey = masterKey;
+ _this.sessionToken = sessionToken;
+ _this.connectPromise = new _ParsePromise2.default();
+ _this.subscriptions = new _map2.default();
+ _this.state = CLIENT_STATE.INITIALIZED;
+ return _this;
+ }
+
+ (0, _createClass3.default)(LiveQueryClient, [{
+ key: 'shouldOpen',
+ value: function () {
+ return this.state === CLIENT_STATE.INITIALIZED || this.state === CLIENT_STATE.DISCONNECTED;
+ }
+
+ /**
+ * Subscribes to a ParseQuery
+ *
+ * If you provide the sessionToken, when the LiveQuery server gets ParseObject's
+ * updates from parse server, it'll try to check whether the sessionToken fulfills
+ * the ParseObject's ACL. The LiveQuery server will only send updates to clients whose
+ * sessionToken is fit for the ParseObject's ACL. You can check the LiveQuery protocol
+ * here for more details. The subscription you get is the same subscription you get
+ * from our Standard API.
+ *
+ * @method subscribe
+ * @param {Object} query - the ParseQuery you want to subscribe to
+ * @param {string} sessionToken (optional)
+ * @return {Object} subscription
+ */
+
+ }, {
+ key: 'subscribe',
+ value: function (query, sessionToken) {
+ var _this2 = this;
+
+ if (!query) {
+ return;
+ }
+ var where = query.toJSON().where;
+ var className = query.className;
+ var subscribeRequest = {
+ op: OP_TYPES.SUBSCRIBE,
+ requestId: this.requestId,
+ query: {
+ className: className,
+ where: where
+ }
+ };
+
+ if (sessionToken) {
+ subscribeRequest.sessionToken = sessionToken;
+ }
+
+ var subscription = new _LiveQuerySubscription2.default(this.requestId, query, sessionToken);
+ this.subscriptions.set(this.requestId, subscription);
+ this.requestId += 1;
+ this.connectPromise.then(function () {
+ _this2.socket.send((0, _stringify2.default)(subscribeRequest));
+ });
+
+ // adding listener so process does not crash
+ // best practice is for developer to register their own listener
+ subscription.on('error', function () {});
+
+ return subscription;
+ }
+
+ /**
+ * After calling unsubscribe you'll stop receiving events from the subscription object.
+ *
+ * @method unsubscribe
+ * @param {Object} subscription - subscription you would like to unsubscribe from.
+ */
+
+ }, {
+ key: 'unsubscribe',
+ value: function (subscription) {
+ var _this3 = this;
+
+ if (!subscription) {
+ return;
+ }
+
+ this.subscriptions.delete(subscription.id);
+ var unsubscribeRequest = {
+ op: OP_TYPES.UNSUBSCRIBE,
+ requestId: subscription.id
+ };
+ this.connectPromise.then(function () {
+ _this3.socket.send((0, _stringify2.default)(unsubscribeRequest));
+ });
+ }
+
+ /**
+ * After open is called, the LiveQueryClient will try to send a connect request
+ * to the LiveQuery server.
+ *
+ * @method open
+ */
+
+ }, {
+ key: 'open',
+ value: function () {
+ var _this4 = this;
+
+ var WebSocketImplementation = this._getWebSocketImplementation();
+ if (!WebSocketImplementation) {
+ this.emit(CLIENT_EMMITER_TYPES.ERROR, 'Can not find WebSocket implementation');
+ return;
+ }
+
+ if (this.state !== CLIENT_STATE.RECONNECTING) {
+ this.state = CLIENT_STATE.CONNECTING;
+ }
+
+ // Get WebSocket implementation
+ this.socket = new WebSocketImplementation(this.serverURL);
+
+ // Bind WebSocket callbacks
+ this.socket.onopen = function () {
+ _this4._handleWebSocketOpen();
+ };
+
+ this.socket.onmessage = function (event) {
+ _this4._handleWebSocketMessage(event);
+ };
+
+ this.socket.onclose = function () {
+ _this4._handleWebSocketClose();
+ };
+
+ this.socket.onerror = function (error) {
+ _this4._handleWebSocketError(error);
+ };
+ }
+ }, {
+ key: 'resubscribe',
+ value: function () {
+ var _this5 = this;
+
+ this.subscriptions.forEach(function (subscription, requestId) {
+ var query = subscription.query;
+ var where = query.toJSON().where;
+ var className = query.className;
+ var sessionToken = subscription.sessionToken;
+ var subscribeRequest = {
+ op: OP_TYPES.SUBSCRIBE,
+ requestId: requestId,
+ query: {
+ className: className,
+ where: where
+ }
+ };
+
+ if (sessionToken) {
+ subscribeRequest.sessionToken = sessionToken;
+ }
+
+ _this5.connectPromise.then(function () {
+ _this5.socket.send((0, _stringify2.default)(subscribeRequest));
+ });
+ });
+ }
+
+ /**
+ * This method will close the WebSocket connection to this LiveQueryClient,
+ * cancel the auto reconnect and unsubscribe all subscriptions based on it.
+ *
+ * @method close
+ */
+
+ }, {
+ key: 'close',
+ value: function () {
+ if (this.state === CLIENT_STATE.INITIALIZED || this.state === CLIENT_STATE.DISCONNECTED) {
+ return;
+ }
+ this.state = CLIENT_STATE.DISCONNECTED;
+ this.socket.close();
+ // Notify each subscription about the close
+ var _iteratorNormalCompletion = true;
+ var _didIteratorError = false;
+ var _iteratorError = undefined;
+
+ try {
+ for (var _iterator = (0, _getIterator3.default)(this.subscriptions.values()), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+ var subscription = _step.value;
+
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.CLOSE);
+ }
+ } catch (err) {
+ _didIteratorError = true;
+ _iteratorError = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion && _iterator.return) {
+ _iterator.return();
+ }
+ } finally {
+ if (_didIteratorError) {
+ throw _iteratorError;
+ }
+ }
+ }
+
+ this._handleReset();
+ this.emit(CLIENT_EMMITER_TYPES.CLOSE);
+ }
+ }, {
+ key: '_getWebSocketImplementation',
+ value: function () {
+ return require('ws');
+ }
+
+ // ensure we start with valid state if connect is called again after close
+
+ }, {
+ key: '_handleReset',
+ value: function () {
+ this.attempts = 1;;
+ this.id = 0;
+ this.requestId = 1;
+ this.connectPromise = new _ParsePromise2.default();
+ this.subscriptions = new _map2.default();
+ }
+ }, {
+ key: '_handleWebSocketOpen',
+ value: function () {
+ this.attempts = 1;
+ var connectRequest = {
+ op: OP_TYPES.CONNECT,
+ applicationId: this.applicationId,
+ javascriptKey: this.javascriptKey,
+ masterKey: this.masterKey,
+ sessionToken: this.sessionToken
+ };
+ this.socket.send((0, _stringify2.default)(connectRequest));
+ }
+ }, {
+ key: '_handleWebSocketMessage',
+ value: function (event) {
+ var data = event.data;
+ if (typeof data === 'string') {
+ data = JSON.parse(data);
+ }
+ var subscription = null;
+ if (data.requestId) {
+ subscription = this.subscriptions.get(data.requestId);
+ }
+ switch (data.op) {
+ case OP_EVENTS.CONNECTED:
+ if (this.state === CLIENT_STATE.RECONNECTING) {
+ this.resubscribe();
+ }
+ this.emit(CLIENT_EMMITER_TYPES.OPEN);
+ this.id = data.clientId;
+ this.connectPromise.resolve();
+ this.state = CLIENT_STATE.CONNECTED;
+ break;
+ case OP_EVENTS.SUBSCRIBED:
+ if (subscription) {
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.OPEN);
+ }
+ break;
+ case OP_EVENTS.ERROR:
+ if (data.requestId) {
+ if (subscription) {
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.ERROR, data.error);
+ }
+ } else {
+ this.emit(CLIENT_EMMITER_TYPES.ERROR, data.error);
+ }
+ break;
+ case OP_EVENTS.UNSUBSCRIBED:
+ // We have already deleted subscription in unsubscribe(), do nothing here
+ break;
+ default:
+ // create, update, enter, leave, delete cases
+ var className = data.object.className;
+ // Delete the extrea __type and className fields during transfer to full JSON
+ delete data.object.__type;
+ delete data.object.className;
+ var parseObject = new _ParseObject2.default(className);
+ parseObject._finishFetch(data.object);
+ if (!subscription) {
+ break;
+ }
+ subscription.emit(data.op, parseObject);
+ }
+ }
+ }, {
+ key: '_handleWebSocketClose',
+ value: function () {
+ if (this.state === CLIENT_STATE.DISCONNECTED) {
+ return;
+ }
+ this.state = CLIENT_STATE.CLOSED;
+ this.emit(CLIENT_EMMITER_TYPES.CLOSE);
+ // Notify each subscription about the close
+ var _iteratorNormalCompletion2 = true;
+ var _didIteratorError2 = false;
+ var _iteratorError2 = undefined;
+
+ try {
+ for (var _iterator2 = (0, _getIterator3.default)(this.subscriptions.values()), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+ var subscription = _step2.value;
+
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.CLOSE);
+ }
+ } catch (err) {
+ _didIteratorError2 = true;
+ _iteratorError2 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion2 && _iterator2.return) {
+ _iterator2.return();
+ }
+ } finally {
+ if (_didIteratorError2) {
+ throw _iteratorError2;
+ }
+ }
+ }
+
+ this._handleReconnect();
+ }
+ }, {
+ key: '_handleWebSocketError',
+ value: function (error) {
+ this.emit(CLIENT_EMMITER_TYPES.ERROR, error);
+ var _iteratorNormalCompletion3 = true;
+ var _didIteratorError3 = false;
+ var _iteratorError3 = undefined;
+
+ try {
+ for (var _iterator3 = (0, _getIterator3.default)(this.subscriptions.values()), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
+ var subscription = _step3.value;
+
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.ERROR);
+ }
+ } catch (err) {
+ _didIteratorError3 = true;
+ _iteratorError3 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion3 && _iterator3.return) {
+ _iterator3.return();
+ }
+ } finally {
+ if (_didIteratorError3) {
+ throw _iteratorError3;
+ }
+ }
+ }
+
+ this._handleReconnect();
+ }
+ }, {
+ key: '_handleReconnect',
+ value: function () {
+ var _this6 = this;
+
+ // if closed or currently reconnecting we stop attempting to reconnect
+ if (this.state === CLIENT_STATE.DISCONNECTED) {
+ return;
+ }
+
+ this.state = CLIENT_STATE.RECONNECTING;
+ var time = generateInterval(this.attempts);
+
+ // handle case when both close/error occur at frequent rates we ensure we do not reconnect unnecessarily.
+ // we're unable to distinguish different between close/error when we're unable to reconnect therefore
+ // we try to reonnect in both cases
+ // server side ws and browser WebSocket behave differently in when close/error get triggered
+
+ if (this.reconnectHandle) {
+ clearTimeout(this.reconnectHandle);
+ }
+
+ this.reconnectHandle = setTimeout(function () {
+ _this6.attempts++;
+ _this6.connectPromise = new _ParsePromise2.default();
+ _this6.open();
+ }.bind(this), time);
+ }
+ }]);
+ return LiveQueryClient;
+}(_EventEmitter3.default);
+
+exports.default = LiveQueryClient;
\ No newline at end of file
diff --git a/lib/node/LiveQuerySubscription.js b/lib/node/LiveQuerySubscription.js
new file mode 100644
index 000000000..4078b65d5
--- /dev/null
+++ b/lib/node/LiveQuerySubscription.js
@@ -0,0 +1,162 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _EventEmitter2 = require('./EventEmitter');
+
+var _EventEmitter3 = _interopRequireDefault(_EventEmitter2);
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Creates a new LiveQuery Subscription.
+ * Extends events.EventEmitter
+ * cloud functions.
+ *
+ * @constructor
+ * @param {string} id - subscription id
+ * @param {string} query - query to subscribe to
+ * @param {string} sessionToken - optional session token
+ *
+ * Open Event - When you call query.subscribe(), we send a subscribe request to + * the LiveQuery server, when we get the confirmation from the LiveQuery server, + * this event will be emitted. When the client loses WebSocket connection to the + * LiveQuery server, we will try to auto reconnect the LiveQuery server. If we + * reconnect the LiveQuery server and successfully resubscribe the ParseQuery, + * you'll also get this event. + * + *
+ * subscription.on('open', () => {
+ *
+ * });
+ *
+ * Create Event - When a new ParseObject is created and it fulfills the ParseQuery you subscribe, + * you'll get this event. The object is the ParseObject which is created. + * + *
+ * subscription.on('create', (object) => {
+ *
+ * });
+ *
+ * Update Event - When an existing ParseObject which fulfills the ParseQuery you subscribe + * is updated (The ParseObject fulfills the ParseQuery before and after changes), + * you'll get this event. The object is the ParseObject which is updated. + * Its content is the latest value of the ParseObject. + * + *
+ * subscription.on('update', (object) => {
+ *
+ * });
+ *
+ * Enter Event - When an existing ParseObject's old value doesn't fulfill the ParseQuery + * but its new value fulfills the ParseQuery, you'll get this event. The object is the + * ParseObject which enters the ParseQuery. Its content is the latest value of the ParseObject. + * + *
+ * subscription.on('enter', (object) => {
+ *
+ * });
+ *
+ *
+ * Update Event - When an existing ParseObject's old value fulfills the ParseQuery but its new value + * doesn't fulfill the ParseQuery, you'll get this event. The object is the ParseObject + * which leaves the ParseQuery. Its content is the latest value of the ParseObject. + * + *
+ * subscription.on('leave', (object) => {
+ *
+ * });
+ *
+ *
+ * Delete Event - When an existing ParseObject which fulfills the ParseQuery is deleted, you'll + * get this event. The object is the ParseObject which is deleted. + * + *
+ * subscription.on('delete', (object) => {
+ *
+ * });
+ *
+ *
+ * Close Event - When the client loses the WebSocket connection to the LiveQuery + * server and we stop receiving events, you'll get this event. + * + *
+ * subscription.on('close', () => {
+ *
+ * });
+ *
+ *
+ */
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+var Subscription = function (_EventEmitter) {
+ (0, _inherits3.default)(Subscription, _EventEmitter);
+
+ function Subscription(id, query, sessionToken) {
+ (0, _classCallCheck3.default)(this, Subscription);
+
+ var _this2 = (0, _possibleConstructorReturn3.default)(this, (Subscription.__proto__ || (0, _getPrototypeOf2.default)(Subscription)).call(this));
+
+ _this2.id = id;
+ _this2.query = query;
+ _this2.sessionToken = sessionToken;
+ return _this2;
+ }
+
+ /**
+ * @method unsubscribe
+ */
+
+ (0, _createClass3.default)(Subscription, [{
+ key: 'unsubscribe',
+ value: function () {
+ var _this3 = this;
+
+ var _this = this;
+ _CoreManager2.default.getLiveQueryController().getDefaultLiveQueryClient().then(function (liveQueryClient) {
+ liveQueryClient.unsubscribe(_this);
+ _this.emit('close');
+ _this3.resolve();
+ });
+ }
+ }]);
+ return Subscription;
+}(_EventEmitter3.default);
+
+exports.default = Subscription;
\ No newline at end of file
diff --git a/lib/node/ObjectStateMutations.js b/lib/node/ObjectStateMutations.js
new file mode 100644
index 000000000..b476a0e2f
--- /dev/null
+++ b/lib/node/ObjectStateMutations.js
@@ -0,0 +1,165 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _stringify = require('babel-runtime/core-js/json/stringify');
+
+var _stringify2 = _interopRequireDefault(_stringify);
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+exports.defaultState = defaultState;
+exports.setServerData = setServerData;
+exports.setPendingOp = setPendingOp;
+exports.pushPendingState = pushPendingState;
+exports.popPendingState = popPendingState;
+exports.mergeFirstPendingState = mergeFirstPendingState;
+exports.estimateAttribute = estimateAttribute;
+exports.estimateAttributes = estimateAttributes;
+exports.commitServerChanges = commitServerChanges;
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _ParseFile = require('./ParseFile');
+
+var _ParseFile2 = _interopRequireDefault(_ParseFile);
+
+var _ParseObject = require('./ParseObject');
+
+var _ParseObject2 = _interopRequireDefault(_ParseObject);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+var _ParseRelation = require('./ParseRelation');
+
+var _ParseRelation2 = _interopRequireDefault(_ParseRelation);
+
+var _TaskQueue = require('./TaskQueue');
+
+var _TaskQueue2 = _interopRequireDefault(_TaskQueue);
+
+var _ParseOp = require('./ParseOp');
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+function defaultState() {
+ return {
+ serverData: {},
+ pendingOps: [{}],
+ objectCache: {},
+ tasks: new _TaskQueue2.default(),
+ existed: false
+ };
+} /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function setServerData(serverData, attributes) {
+ for (var _attr in attributes) {
+ if (typeof attributes[_attr] !== 'undefined') {
+ serverData[_attr] = attributes[_attr];
+ } else {
+ delete serverData[_attr];
+ }
+ }
+}
+
+function setPendingOp(pendingOps, attr, op) {
+ var last = pendingOps.length - 1;
+ if (op) {
+ pendingOps[last][attr] = op;
+ } else {
+ delete pendingOps[last][attr];
+ }
+}
+
+function pushPendingState(pendingOps) {
+ pendingOps.push({});
+}
+
+function popPendingState(pendingOps) {
+ var first = pendingOps.shift();
+ if (!pendingOps.length) {
+ pendingOps[0] = {};
+ }
+ return first;
+}
+
+function mergeFirstPendingState(pendingOps) {
+ var first = popPendingState(pendingOps);
+ var next = pendingOps[0];
+ for (var _attr2 in first) {
+ if (next[_attr2] && first[_attr2]) {
+ var merged = next[_attr2].mergeWith(first[_attr2]);
+ if (merged) {
+ next[_attr2] = merged;
+ }
+ } else {
+ next[_attr2] = first[_attr2];
+ }
+ }
+}
+
+function estimateAttribute(serverData, pendingOps, className, id, attr) {
+ var value = serverData[attr];
+ for (var i = 0; i < pendingOps.length; i++) {
+ if (pendingOps[i][attr]) {
+ if (pendingOps[i][attr] instanceof _ParseOp.RelationOp) {
+ if (id) {
+ value = pendingOps[i][attr].applyTo(value, { className: className, id: id }, attr);
+ }
+ } else {
+ value = pendingOps[i][attr].applyTo(value);
+ }
+ }
+ }
+ return value;
+}
+
+function estimateAttributes(serverData, pendingOps, className, id) {
+ var data = {};
+ var attr = void 0;
+ for (attr in serverData) {
+ data[attr] = serverData[attr];
+ }
+ for (var i = 0; i < pendingOps.length; i++) {
+ for (attr in pendingOps[i]) {
+ if (pendingOps[i][attr] instanceof _ParseOp.RelationOp) {
+ if (id) {
+ data[attr] = pendingOps[i][attr].applyTo(data[attr], { className: className, id: id }, attr);
+ }
+ } else {
+ data[attr] = pendingOps[i][attr].applyTo(data[attr]);
+ }
+ }
+ }
+ return data;
+}
+
+function commitServerChanges(serverData, objectCache, changes) {
+ for (var _attr3 in changes) {
+ var val = changes[_attr3];
+ serverData[_attr3] = val;
+ if (val && (typeof val === 'undefined' ? 'undefined' : (0, _typeof3.default)(val)) === 'object' && !(val instanceof _ParseObject2.default) && !(val instanceof _ParseFile2.default) && !(val instanceof _ParseRelation2.default)) {
+ var json = (0, _encode2.default)(val, false, true);
+ objectCache[_attr3] = (0, _stringify2.default)(json);
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/node/Parse.js b/lib/node/Parse.js
new file mode 100644
index 000000000..280cd4015
--- /dev/null
+++ b/lib/node/Parse.js
@@ -0,0 +1,190 @@
+'use strict';
+
+var _decode = require('./decode');
+
+var _decode2 = _interopRequireDefault(_decode);
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _InstallationController = require('./InstallationController');
+
+var _InstallationController2 = _interopRequireDefault(_InstallationController);
+
+var _ParseOp = require('./ParseOp');
+
+var ParseOp = _interopRequireWildcard(_ParseOp);
+
+var _RESTController = require('./RESTController');
+
+var _RESTController2 = _interopRequireDefault(_RESTController);
+
+function _interopRequireWildcard(obj) {
+ if (obj && obj.__esModule) {
+ return obj;
+ } else {
+ var newObj = {};if (obj != null) {
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];
+ }
+ }newObj.default = obj;return newObj;
+ }
+}
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Contains all Parse API classes and functions.
+ * @class Parse
+ * @static
+ */
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+var Parse = {
+ /**
+ * Call this method first to set up your authentication tokens for Parse.
+ * You can get your keys from the Data Browser on parse.com.
+ * @method initialize
+ * @param {String} applicationId Your Parse Application ID.
+ * @param {String} javaScriptKey (optional) Your Parse JavaScript Key (Not needed for parse-server)
+ * @param {String} masterKey (optional) Your Parse Master Key. (Node.js only!)
+ * @static
+ */
+ initialize: function (applicationId, javaScriptKey) {
+ Parse._initialize(applicationId, javaScriptKey);
+ },
+ _initialize: function (applicationId, javaScriptKey, masterKey) {
+ _CoreManager2.default.set('APPLICATION_ID', applicationId);
+ _CoreManager2.default.set('JAVASCRIPT_KEY', javaScriptKey);
+ _CoreManager2.default.set('MASTER_KEY', masterKey);
+ _CoreManager2.default.set('USE_MASTER_KEY', false);
+ }
+};
+
+/** These legacy setters may eventually be deprecated **/
+Object.defineProperty(Parse, 'applicationId', {
+ get: function () {
+ return _CoreManager2.default.get('APPLICATION_ID');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('APPLICATION_ID', value);
+ }
+});
+Object.defineProperty(Parse, 'javaScriptKey', {
+ get: function () {
+ return _CoreManager2.default.get('JAVASCRIPT_KEY');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('JAVASCRIPT_KEY', value);
+ }
+});
+Object.defineProperty(Parse, 'masterKey', {
+ get: function () {
+ return _CoreManager2.default.get('MASTER_KEY');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('MASTER_KEY', value);
+ }
+});
+Object.defineProperty(Parse, 'serverURL', {
+ get: function () {
+ return _CoreManager2.default.get('SERVER_URL');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('SERVER_URL', value);
+ }
+});
+Object.defineProperty(Parse, 'liveQueryServerURL', {
+ get: function () {
+ return _CoreManager2.default.get('LIVEQUERY_SERVER_URL');
+ },
+ set: function (value) {
+ _CoreManager2.default.set('LIVEQUERY_SERVER_URL', value);
+ }
+});
+/** End setters **/
+
+Parse.ACL = require('./ParseACL').default;
+Parse.Analytics = require('./Analytics');
+Parse.Cloud = require('./Cloud');
+Parse.CoreManager = require('./CoreManager');
+Parse.Config = require('./ParseConfig').default;
+Parse.Error = require('./ParseError').default;
+Parse.FacebookUtils = require('./FacebookUtils').default;
+Parse.File = require('./ParseFile').default;
+Parse.GeoPoint = require('./ParseGeoPoint').default;
+Parse.Installation = require('./ParseInstallation').default;
+Parse.Object = require('./ParseObject').default;
+Parse.Op = {
+ Set: ParseOp.SetOp,
+ Unset: ParseOp.UnsetOp,
+ Increment: ParseOp.IncrementOp,
+ Add: ParseOp.AddOp,
+ Remove: ParseOp.RemoveOp,
+ AddUnique: ParseOp.AddUniqueOp,
+ Relation: ParseOp.RelationOp
+};
+Parse.Promise = require('./ParsePromise').default;
+Parse.Push = require('./Push');
+Parse.Query = require('./ParseQuery').default;
+Parse.Relation = require('./ParseRelation').default;
+Parse.Role = require('./ParseRole').default;
+Parse.Session = require('./ParseSession').default;
+Parse.Storage = require('./Storage');
+Parse.User = require('./ParseUser').default;
+Parse.LiveQuery = require('./ParseLiveQuery').default;
+Parse.LiveQueryClient = require('./LiveQueryClient').default;
+
+Parse._request = function () {
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ return _CoreManager2.default.getRESTController().request.apply(null, args);
+};
+Parse._ajax = function () {
+ for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+
+ return _CoreManager2.default.getRESTController().ajax.apply(null, args);
+};
+// We attempt to match the signatures of the legacy versions of these methods
+Parse._decode = function (_, value) {
+ return (0, _decode2.default)(value);
+};
+Parse._encode = function (value, _, disallowObjects) {
+ return (0, _encode2.default)(value, disallowObjects);
+};
+Parse._getInstallationId = function () {
+ return _CoreManager2.default.getInstallationController().currentInstallationId();
+};
+
+_CoreManager2.default.setInstallationController(_InstallationController2.default);
+_CoreManager2.default.setRESTController(_RESTController2.default);
+
+Parse.initialize = Parse._initialize;
+Parse.Cloud = Parse.Cloud || {};
+Parse.Cloud.useMasterKey = function () {
+ _CoreManager2.default.set('USE_MASTER_KEY', true);
+};
+Parse.Hooks = require('./ParseHooks');
+
+// For legacy requires, of the form `var Parse = require('parse').Parse`
+Parse.Parse = Parse;
+
+module.exports = Parse;
\ No newline at end of file
diff --git a/lib/node/ParseACL.js b/lib/node/ParseACL.js
new file mode 100644
index 000000000..2aa232c18
--- /dev/null
+++ b/lib/node/ParseACL.js
@@ -0,0 +1,406 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _keys = require('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _ParseRole = require('./ParseRole');
+
+var _ParseRole2 = _interopRequireDefault(_ParseRole);
+
+var _ParseUser = require('./ParseUser');
+
+var _ParseUser2 = _interopRequireDefault(_ParseUser);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+var PUBLIC_KEY = '*';
+
+/**
+ * Creates a new ACL.
+ * If no argument is given, the ACL has no permissions for anyone.
+ * If the argument is a Parse.User, the ACL will have read and write
+ * permission for only that user.
+ * If the argument is any other JSON object, that object will be interpretted
+ * as a serialized ACL created with toJSON().
+ * @class Parse.ACL
+ * @constructor
+ *
+ * An ACL, or Access Control List can be added to any
+ * Parse.Object to restrict access to only a subset of users
+ * of your application.
Parse.Error.
+ * @param {String} message A detailed description of the error.
+ */
+var ParseError = function ParseError(code, message) {
+ (0, _classCallCheck3.default)(this, ParseError);
+
+ this.code = code;
+ this.message = message;
+};
+
+/**
+ * Error code indicating some error other than those enumerated here.
+ * @property OTHER_CAUSE
+ * @static
+ * @final
+ */
+
+exports.default = ParseError;
+ParseError.OTHER_CAUSE = -1;
+
+/**
+ * Error code indicating that something has gone wrong with the server.
+ * If you get this error code, it is Parse's fault. Contact us at
+ * https://parse.com/help
+ * @property INTERNAL_SERVER_ERROR
+ * @static
+ * @final
+ */
+ParseError.INTERNAL_SERVER_ERROR = 1;
+
+/**
+ * Error code indicating the connection to the Parse servers failed.
+ * @property CONNECTION_FAILED
+ * @static
+ * @final
+ */
+ParseError.CONNECTION_FAILED = 100;
+
+/**
+ * Error code indicating the specified object doesn't exist.
+ * @property OBJECT_NOT_FOUND
+ * @static
+ * @final
+ */
+ParseError.OBJECT_NOT_FOUND = 101;
+
+/**
+ * Error code indicating you tried to query with a datatype that doesn't
+ * support it, like exact matching an array or object.
+ * @property INVALID_QUERY
+ * @static
+ * @final
+ */
+ParseError.INVALID_QUERY = 102;
+
+/**
+ * Error code indicating a missing or invalid classname. Classnames are
+ * case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the
+ * only valid characters.
+ * @property INVALID_CLASS_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_CLASS_NAME = 103;
+
+/**
+ * Error code indicating an unspecified object id.
+ * @property MISSING_OBJECT_ID
+ * @static
+ * @final
+ */
+ParseError.MISSING_OBJECT_ID = 104;
+
+/**
+ * Error code indicating an invalid key name. Keys are case-sensitive. They
+ * must start with a letter, and a-zA-Z0-9_ are the only valid characters.
+ * @property INVALID_KEY_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_KEY_NAME = 105;
+
+/**
+ * Error code indicating a malformed pointer. You should not see this unless
+ * you have been mucking about changing internal Parse code.
+ * @property INVALID_POINTER
+ * @static
+ * @final
+ */
+ParseError.INVALID_POINTER = 106;
+
+/**
+ * Error code indicating that badly formed JSON was received upstream. This
+ * either indicates you have done something unusual with modifying how
+ * things encode to JSON, or the network is failing badly.
+ * @property INVALID_JSON
+ * @static
+ * @final
+ */
+ParseError.INVALID_JSON = 107;
+
+/**
+ * Error code indicating that the feature you tried to access is only
+ * available internally for testing purposes.
+ * @property COMMAND_UNAVAILABLE
+ * @static
+ * @final
+ */
+ParseError.COMMAND_UNAVAILABLE = 108;
+
+/**
+ * You must call Parse.initialize before using the Parse library.
+ * @property NOT_INITIALIZED
+ * @static
+ * @final
+ */
+ParseError.NOT_INITIALIZED = 109;
+
+/**
+ * Error code indicating that a field was set to an inconsistent type.
+ * @property INCORRECT_TYPE
+ * @static
+ * @final
+ */
+ParseError.INCORRECT_TYPE = 111;
+
+/**
+ * Error code indicating an invalid channel name. A channel name is either
+ * an empty string (the broadcast channel) or contains only a-zA-Z0-9_
+ * characters and starts with a letter.
+ * @property INVALID_CHANNEL_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_CHANNEL_NAME = 112;
+
+/**
+ * Error code indicating that push is misconfigured.
+ * @property PUSH_MISCONFIGURED
+ * @static
+ * @final
+ */
+ParseError.PUSH_MISCONFIGURED = 115;
+
+/**
+ * Error code indicating that the object is too large.
+ * @property OBJECT_TOO_LARGE
+ * @static
+ * @final
+ */
+ParseError.OBJECT_TOO_LARGE = 116;
+
+/**
+ * Error code indicating that the operation isn't allowed for clients.
+ * @property OPERATION_FORBIDDEN
+ * @static
+ * @final
+ */
+ParseError.OPERATION_FORBIDDEN = 119;
+
+/**
+ * Error code indicating the result was not found in the cache.
+ * @property CACHE_MISS
+ * @static
+ * @final
+ */
+ParseError.CACHE_MISS = 120;
+
+/**
+ * Error code indicating that an invalid key was used in a nested
+ * JSONObject.
+ * @property INVALID_NESTED_KEY
+ * @static
+ * @final
+ */
+ParseError.INVALID_NESTED_KEY = 121;
+
+/**
+ * Error code indicating that an invalid filename was used for ParseFile.
+ * A valid file name contains only a-zA-Z0-9_. characters and is between 1
+ * and 128 characters.
+ * @property INVALID_FILE_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_FILE_NAME = 122;
+
+/**
+ * Error code indicating an invalid ACL was provided.
+ * @property INVALID_ACL
+ * @static
+ * @final
+ */
+ParseError.INVALID_ACL = 123;
+
+/**
+ * Error code indicating that the request timed out on the server. Typically
+ * this indicates that the request is too expensive to run.
+ * @property TIMEOUT
+ * @static
+ * @final
+ */
+ParseError.TIMEOUT = 124;
+
+/**
+ * Error code indicating that the email address was invalid.
+ * @property INVALID_EMAIL_ADDRESS
+ * @static
+ * @final
+ */
+ParseError.INVALID_EMAIL_ADDRESS = 125;
+
+/**
+ * Error code indicating a missing content type.
+ * @property MISSING_CONTENT_TYPE
+ * @static
+ * @final
+ */
+ParseError.MISSING_CONTENT_TYPE = 126;
+
+/**
+ * Error code indicating a missing content length.
+ * @property MISSING_CONTENT_LENGTH
+ * @static
+ * @final
+ */
+ParseError.MISSING_CONTENT_LENGTH = 127;
+
+/**
+ * Error code indicating an invalid content length.
+ * @property INVALID_CONTENT_LENGTH
+ * @static
+ * @final
+ */
+ParseError.INVALID_CONTENT_LENGTH = 128;
+
+/**
+ * Error code indicating a file that was too large.
+ * @property FILE_TOO_LARGE
+ * @static
+ * @final
+ */
+ParseError.FILE_TOO_LARGE = 129;
+
+/**
+ * Error code indicating an error saving a file.
+ * @property FILE_SAVE_ERROR
+ * @static
+ * @final
+ */
+ParseError.FILE_SAVE_ERROR = 130;
+
+/**
+ * Error code indicating that a unique field was given a value that is
+ * already taken.
+ * @property DUPLICATE_VALUE
+ * @static
+ * @final
+ */
+ParseError.DUPLICATE_VALUE = 137;
+
+/**
+ * Error code indicating that a role's name is invalid.
+ * @property INVALID_ROLE_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_ROLE_NAME = 139;
+
+/**
+ * Error code indicating that an application quota was exceeded. Upgrade to
+ * resolve.
+ * @property EXCEEDED_QUOTA
+ * @static
+ * @final
+ */
+ParseError.EXCEEDED_QUOTA = 140;
+
+/**
+ * Error code indicating that a Cloud Code script failed.
+ * @property SCRIPT_FAILED
+ * @static
+ * @final
+ */
+ParseError.SCRIPT_FAILED = 141;
+
+/**
+ * Error code indicating that a Cloud Code validation failed.
+ * @property VALIDATION_ERROR
+ * @static
+ * @final
+ */
+ParseError.VALIDATION_ERROR = 142;
+
+/**
+ * Error code indicating that invalid image data was provided.
+ * @property INVALID_IMAGE_DATA
+ * @static
+ * @final
+ */
+ParseError.INVALID_IMAGE_DATA = 143;
+
+/**
+ * Error code indicating an unsaved file.
+ * @property UNSAVED_FILE_ERROR
+ * @static
+ * @final
+ */
+ParseError.UNSAVED_FILE_ERROR = 151;
+
+/**
+ * Error code indicating an invalid push time.
+ * @property INVALID_PUSH_TIME_ERROR
+ * @static
+ * @final
+ */
+ParseError.INVALID_PUSH_TIME_ERROR = 152;
+
+/**
+ * Error code indicating an error deleting a file.
+ * @property FILE_DELETE_ERROR
+ * @static
+ * @final
+ */
+ParseError.FILE_DELETE_ERROR = 153;
+
+/**
+ * Error code indicating that the application has exceeded its request
+ * limit.
+ * @property REQUEST_LIMIT_EXCEEDED
+ * @static
+ * @final
+ */
+ParseError.REQUEST_LIMIT_EXCEEDED = 155;
+
+/**
+ * Error code indicating an invalid event name.
+ * @property INVALID_EVENT_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_EVENT_NAME = 160;
+
+/**
+ * Error code indicating that the username is missing or empty.
+ * @property USERNAME_MISSING
+ * @static
+ * @final
+ */
+ParseError.USERNAME_MISSING = 200;
+
+/**
+ * Error code indicating that the password is missing or empty.
+ * @property PASSWORD_MISSING
+ * @static
+ * @final
+ */
+ParseError.PASSWORD_MISSING = 201;
+
+/**
+ * Error code indicating that the username has already been taken.
+ * @property USERNAME_TAKEN
+ * @static
+ * @final
+ */
+ParseError.USERNAME_TAKEN = 202;
+
+/**
+ * Error code indicating that the email has already been taken.
+ * @property EMAIL_TAKEN
+ * @static
+ * @final
+ */
+ParseError.EMAIL_TAKEN = 203;
+
+/**
+ * Error code indicating that the email is missing, but must be specified.
+ * @property EMAIL_MISSING
+ * @static
+ * @final
+ */
+ParseError.EMAIL_MISSING = 204;
+
+/**
+ * Error code indicating that a user with the specified email was not found.
+ * @property EMAIL_NOT_FOUND
+ * @static
+ * @final
+ */
+ParseError.EMAIL_NOT_FOUND = 205;
+
+/**
+ * Error code indicating that a user object without a valid session could
+ * not be altered.
+ * @property SESSION_MISSING
+ * @static
+ * @final
+ */
+ParseError.SESSION_MISSING = 206;
+
+/**
+ * Error code indicating that a user can only be created through signup.
+ * @property MUST_CREATE_USER_THROUGH_SIGNUP
+ * @static
+ * @final
+ */
+ParseError.MUST_CREATE_USER_THROUGH_SIGNUP = 207;
+
+/**
+ * Error code indicating that an an account being linked is already linked
+ * to another user.
+ * @property ACCOUNT_ALREADY_LINKED
+ * @static
+ * @final
+ */
+ParseError.ACCOUNT_ALREADY_LINKED = 208;
+
+/**
+ * Error code indicating that the current session token is invalid.
+ * @property INVALID_SESSION_TOKEN
+ * @static
+ * @final
+ */
+ParseError.INVALID_SESSION_TOKEN = 209;
+
+/**
+ * Error code indicating that a user cannot be linked to an account because
+ * that account's id could not be found.
+ * @property LINKED_ID_MISSING
+ * @static
+ * @final
+ */
+ParseError.LINKED_ID_MISSING = 250;
+
+/**
+ * Error code indicating that a user with a linked (e.g. Facebook) account
+ * has an invalid session.
+ * @property INVALID_LINKED_SESSION
+ * @static
+ * @final
+ */
+ParseError.INVALID_LINKED_SESSION = 251;
+
+/**
+ * Error code indicating that a service being linked (e.g. Facebook or
+ * Twitter) is unsupported.
+ * @property UNSUPPORTED_SERVICE
+ * @static
+ * @final
+ */
+ParseError.UNSUPPORTED_SERVICE = 252;
+
+/**
+ * Error code indicating that there were multiple errors. Aggregate errors
+ * have an "errors" property, which is an array of error objects with more
+ * detail about each error that occurred.
+ * @property AGGREGATE_ERROR
+ * @static
+ * @final
+ */
+ParseError.AGGREGATE_ERROR = 600;
+
+/**
+ * Error code indicating the client was unable to read an input file.
+ * @property FILE_READ_ERROR
+ * @static
+ * @final
+ */
+ParseError.FILE_READ_ERROR = 601;
+
+/**
+ * Error code indicating a real error code is unavailable because
+ * we had to use an XDomainRequest object to allow CORS requests in
+ * Internet Explorer, which strips the body from HTTP responses that have
+ * a non-2XX status code.
+ * @property X_DOMAIN_REQUEST
+ * @static
+ * @final
+ */
+ParseError.X_DOMAIN_REQUEST = 602;
\ No newline at end of file
diff --git a/lib/node/ParseFile.js b/lib/node/ParseFile.js
new file mode 100644
index 000000000..48efdb9d8
--- /dev/null
+++ b/lib/node/ParseFile.js
@@ -0,0 +1,291 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+var dataUriRegexp = /^data:([a-zA-Z]*\/[a-zA-Z+.-]*);(charset=[a-zA-Z0-9\-\/\s]*,)?base64,/;
+
+function b64Digit(number) {
+ if (number < 26) {
+ return String.fromCharCode(65 + number);
+ }
+ if (number < 52) {
+ return String.fromCharCode(97 + (number - 26));
+ }
+ if (number < 62) {
+ return String.fromCharCode(48 + (number - 52));
+ }
+ if (number === 62) {
+ return '+';
+ }
+ if (number === 63) {
+ return '/';
+ }
+ throw new TypeError('Tried to encode large digit ' + number + ' in base64.');
+}
+
+/**
+ * A Parse.File is a local representation of a file that is saved to the Parse
+ * cloud.
+ * @class Parse.File
+ * @constructor
+ * @param name {String} The file's name. This will be prefixed by a unique
+ * value once the file has finished saving. The file name must begin with
+ * an alphanumeric character, and consist of alphanumeric characters,
+ * periods, spaces, underscores, or dashes.
+ * @param data {Array} The data for the file, as either:
+ * 1. an Array of byte value Numbers, or
+ * 2. an Object like { base64: "..." } with a base64-encoded String.
+ * 3. a File object selected with a file upload control. (3) only works
+ * in Firefox 3.6+, Safari 6.0.2+, Chrome 7+, and IE 10+.
+ * For example:
+ * var fileUploadControl = $("#profilePhotoFileUpload")[0];
+ * if (fileUploadControl.files.length > 0) {
+ * var file = fileUploadControl.files[0];
+ * var name = "photo.jpg";
+ * var parseFile = new Parse.File(name, file);
+ * parseFile.save().then(function() {
+ * // The file has been saved to Parse.
+ * }, function(error) {
+ * // The file either could not be read, or could not be saved to Parse.
+ * });
+ * }
+ * @param type {String} Optional Content-Type header to use for the file. If
+ * this is omitted, the content type will be inferred from the name's
+ * extension.
+ */
+
+var ParseFile = function () {
+ function ParseFile(name, data, type) {
+ (0, _classCallCheck3.default)(this, ParseFile);
+
+ var specifiedType = type || '';
+
+ this._name = name;
+
+ if (data !== undefined) {
+ if (Array.isArray(data)) {
+ this._source = {
+ format: 'base64',
+ base64: ParseFile.encodeBase64(data),
+ type: specifiedType
+ };
+ } else if (typeof File !== 'undefined' && data instanceof File) {
+ this._source = {
+ format: 'file',
+ file: data,
+ type: specifiedType
+ };
+ } else if (data && typeof data.base64 === 'string') {
+ var _base = data.base64;
+ var commaIndex = _base.indexOf(',');
+
+ if (commaIndex !== -1) {
+ var matches = dataUriRegexp.exec(_base.slice(0, commaIndex + 1));
+ // if data URI with type and charset, there will be 4 matches.
+ this._source = {
+ format: 'base64',
+ base64: _base.slice(commaIndex + 1),
+ type: matches[1]
+ };
+ } else {
+ this._source = {
+ format: 'base64',
+ base64: _base,
+ type: specifiedType
+ };
+ }
+ } else {
+ throw new TypeError('Cannot create a Parse.File with that data.');
+ }
+ }
+ }
+
+ /**
+ * Gets the name of the file. Before save is called, this is the filename
+ * given by the user. After save is called, that name gets prefixed with a
+ * unique identifier.
+ * @method name
+ * @return {String}
+ */
+
+ (0, _createClass3.default)(ParseFile, [{
+ key: 'name',
+ value: function () {
+ return this._name;
+ }
+
+ /**
+ * Gets the url of the file. It is only available after you save the file or
+ * after you get the file from a Parse.Object.
+ * @method url
+ * @param {Object} options An object to specify url options
+ * @return {String}
+ */
+
+ }, {
+ key: 'url',
+ value: function (options) {
+ options = options || {};
+ if (!this._url) {
+ return;
+ }
+ if (options.forceSecure) {
+ return this._url.replace(/^http:\/\//i, 'https://');
+ } else {
+ return this._url;
+ }
+ }
+
+ /**
+ * Saves the file to the Parse cloud.
+ * @method save
+ * @param {Object} options A Backbone-style options object.
+ * @return {Parse.Promise} Promise that is resolved when the save finishes.
+ */
+
+ }, {
+ key: 'save',
+ value: function (options) {
+ var _this = this;
+
+ options = options || {};
+ var controller = _CoreManager2.default.getFileController();
+ if (!this._previousSave) {
+ if (this._source.format === 'file') {
+ this._previousSave = controller.saveFile(this._name, this._source).then(function (res) {
+ _this._name = res.name;
+ _this._url = res.url;
+ return _this;
+ });
+ } else {
+ this._previousSave = controller.saveBase64(this._name, this._source).then(function (res) {
+ _this._name = res.name;
+ _this._url = res.url;
+ return _this;
+ });
+ }
+ }
+ if (this._previousSave) {
+ return this._previousSave._thenRunCallbacks(options);
+ }
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return {
+ __type: 'File',
+ name: this._name,
+ url: this._url
+ };
+ }
+ }, {
+ key: 'equals',
+ value: function (other) {
+ if (this === other) {
+ return true;
+ }
+ // Unsaved Files are never equal, since they will be saved to different URLs
+ return other instanceof ParseFile && this.name() === other.name() && this.url() === other.url() && typeof this.url() !== 'undefined';
+ }
+ }], [{
+ key: 'fromJSON',
+ value: function (obj) {
+ if (obj.__type !== 'File') {
+ throw new TypeError('JSON object does not represent a ParseFile');
+ }
+ var file = new ParseFile(obj.name);
+ file._url = obj.url;
+ return file;
+ }
+ }, {
+ key: 'encodeBase64',
+ value: function (bytes) {
+ var chunks = [];
+ chunks.length = Math.ceil(bytes.length / 3);
+ for (var i = 0; i < chunks.length; i++) {
+ var b1 = bytes[i * 3];
+ var b2 = bytes[i * 3 + 1] || 0;
+ var b3 = bytes[i * 3 + 2] || 0;
+
+ var has2 = i * 3 + 1 < bytes.length;
+ var has3 = i * 3 + 2 < bytes.length;
+
+ chunks[i] = [b64Digit(b1 >> 2 & 0x3F), b64Digit(b1 << 4 & 0x30 | b2 >> 4 & 0x0F), has2 ? b64Digit(b2 << 2 & 0x3C | b3 >> 6 & 0x03) : '=', has3 ? b64Digit(b3 & 0x3F) : '='].join('');
+ }
+
+ return chunks.join('');
+ }
+ }]);
+ return ParseFile;
+}();
+
+exports.default = ParseFile;
+
+var DefaultController = {
+ saveFile: function (name, source) {
+ if (source.format !== 'file') {
+ throw new Error('saveFile can only be used with File-type sources.');
+ }
+ // To directly upload a File, we use a REST-style AJAX request
+ var headers = {
+ 'X-Parse-Application-ID': _CoreManager2.default.get('APPLICATION_ID'),
+ 'X-Parse-JavaScript-Key': _CoreManager2.default.get('JAVASCRIPT_KEY'),
+ 'Content-Type': source.type || (source.file ? source.file.type : null)
+ };
+ var url = _CoreManager2.default.get('SERVER_URL');
+ if (url[url.length - 1] !== '/') {
+ url += '/';
+ }
+ url += 'files/' + name;
+ return _CoreManager2.default.getRESTController().ajax('POST', url, source.file, headers);
+ },
+
+ saveBase64: function (name, source) {
+ if (source.format !== 'base64') {
+ throw new Error('saveBase64 can only be used with Base64-type sources.');
+ }
+ var data = {
+ base64: source.base64
+ };
+ if (source.type) {
+ data._ContentType = source.type;
+ }
+
+ return _CoreManager2.default.getRESTController().request('POST', 'files/' + name, data);
+ }
+};
+
+_CoreManager2.default.setFileController(DefaultController);
\ No newline at end of file
diff --git a/lib/node/ParseGeoPoint.js b/lib/node/ParseGeoPoint.js
new file mode 100644
index 000000000..98691f85b
--- /dev/null
+++ b/lib/node/ParseGeoPoint.js
@@ -0,0 +1,235 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Creates a new GeoPoint with any of the following forms:
+ * new GeoPoint(otherGeoPoint)
+ * new GeoPoint(30, 30)
+ * new GeoPoint([30, 30])
+ * new GeoPoint({latitude: 30, longitude: 30})
+ * new GeoPoint() // defaults to (0, 0)
+ *
+ * @class Parse.GeoPoint
+ * @constructor
+ *
+ * Represents a latitude / longitude point that may be associated + * with a key in a ParseObject or used as a reference point for geo queries. + * This allows proximity-based queries on the key.
+ * + *Only one key in a class may contain a GeoPoint.
+ * + *Example:
+ * var point = new Parse.GeoPoint(30.0, -20.0);
+ * var object = new Parse.Object("PlaceObject");
+ * object.set("location", point);
+ * object.save();
+ */
+var ParseGeoPoint = function () {
+ function ParseGeoPoint(arg1, arg2) {
+ (0, _classCallCheck3.default)(this, ParseGeoPoint);
+
+ if (Array.isArray(arg1)) {
+ ParseGeoPoint._validate(arg1[0], arg1[1]);
+ this._latitude = arg1[0];
+ this._longitude = arg1[1];
+ } else if ((typeof arg1 === 'undefined' ? 'undefined' : (0, _typeof3.default)(arg1)) === 'object') {
+ ParseGeoPoint._validate(arg1.latitude, arg1.longitude);
+ this._latitude = arg1.latitude;
+ this._longitude = arg1.longitude;
+ } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
+ ParseGeoPoint._validate(arg1, arg2);
+ this._latitude = arg1;
+ this._longitude = arg2;
+ } else {
+ this._latitude = 0;
+ this._longitude = 0;
+ }
+ }
+
+ /**
+ * North-south portion of the coordinate, in range [-90, 90].
+ * Throws an exception if set out of range in a modern browser.
+ * @property latitude
+ * @type Number
+ */
+
+ (0, _createClass3.default)(ParseGeoPoint, [{
+ key: 'toJSON',
+
+ /**
+ * Returns a JSON representation of the GeoPoint, suitable for Parse.
+ * @method toJSON
+ * @return {Object}
+ */
+ value: function () {
+ ParseGeoPoint._validate(this._latitude, this._longitude);
+ return {
+ __type: 'GeoPoint',
+ latitude: this._latitude,
+ longitude: this._longitude
+ };
+ }
+ }, {
+ key: 'equals',
+ value: function (other) {
+ return other instanceof ParseGeoPoint && this.latitude === other.latitude && this.longitude === other.longitude;
+ }
+
+ /**
+ * Returns the distance from this GeoPoint to another in radians.
+ * @method radiansTo
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+
+ }, {
+ key: 'radiansTo',
+ value: function (point) {
+ var d2r = Math.PI / 180.0;
+ var lat1rad = this.latitude * d2r;
+ var long1rad = this.longitude * d2r;
+ var lat2rad = point.latitude * d2r;
+ var long2rad = point.longitude * d2r;
+
+ var sinDeltaLatDiv2 = Math.sin((lat1rad - lat2rad) / 2);
+ var sinDeltaLongDiv2 = Math.sin((long1rad - long2rad) / 2);
+ // Square of half the straight line chord distance between both points.
+ var a = sinDeltaLatDiv2 * sinDeltaLatDiv2 + Math.cos(lat1rad) * Math.cos(lat2rad) * sinDeltaLongDiv2 * sinDeltaLongDiv2;
+ a = Math.min(1.0, a);
+ return 2 * Math.asin(Math.sqrt(a));
+ }
+
+ /**
+ * Returns the distance from this GeoPoint to another in kilometers.
+ * @method kilometersTo
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+
+ }, {
+ key: 'kilometersTo',
+ value: function (point) {
+ return this.radiansTo(point) * 6371.0;
+ }
+
+ /**
+ * Returns the distance from this GeoPoint to another in miles.
+ * @method milesTo
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+
+ }, {
+ key: 'milesTo',
+ value: function (point) {
+ return this.radiansTo(point) * 3958.8;
+ }
+
+ /**
+ * Throws an exception if the given lat-long is out of bounds.
+ */
+
+ }, {
+ key: 'latitude',
+ get: function () {
+ return this._latitude;
+ },
+ set: function (val) {
+ ParseGeoPoint._validate(val, this.longitude);
+ this._latitude = val;
+ }
+
+ /**
+ * East-west portion of the coordinate, in range [-180, 180].
+ * Throws if set out of range in a modern browser.
+ * @property longitude
+ * @type Number
+ */
+
+ }, {
+ key: 'longitude',
+ get: function () {
+ return this._longitude;
+ },
+ set: function (val) {
+ ParseGeoPoint._validate(this.latitude, val);
+ this._longitude = val;
+ }
+ }], [{
+ key: '_validate',
+ value: function (latitude, longitude) {
+ if (latitude !== latitude || longitude !== longitude) {
+ throw new TypeError('GeoPoint latitude and longitude must be valid numbers');
+ }
+ if (latitude < -90.0) {
+ throw new TypeError('GeoPoint latitude out of bounds: ' + latitude + ' < -90.0.');
+ }
+ if (latitude > 90.0) {
+ throw new TypeError('GeoPoint latitude out of bounds: ' + latitude + ' > 90.0.');
+ }
+ if (longitude < -180.0) {
+ throw new TypeError('GeoPoint longitude out of bounds: ' + longitude + ' < -180.0.');
+ }
+ if (longitude > 180.0) {
+ throw new TypeError('GeoPoint longitude out of bounds: ' + longitude + ' > 180.0.');
+ }
+ }
+
+ /**
+ * Creates a GeoPoint with the user's current location, if available.
+ * Calls options.success with a new GeoPoint instance or calls options.error.
+ * @method current
+ * @param {Object} options An object with success and error callbacks.
+ * @static
+ */
+
+ }, {
+ key: 'current',
+ value: function (options) {
+ var promise = new _ParsePromise2.default();
+ navigator.geolocation.getCurrentPosition(function (location) {
+ promise.resolve(new ParseGeoPoint(location.coords.latitude, location.coords.longitude));
+ }, function (error) {
+ promise.reject(error);
+ });
+
+ return promise._thenRunCallbacks(options);
+ }
+ }]);
+ return ParseGeoPoint;
+}(); /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+exports.default = ParseGeoPoint;
\ No newline at end of file
diff --git a/lib/node/ParseHooks.js b/lib/node/ParseHooks.js
new file mode 100644
index 000000000..a2057e899
--- /dev/null
+++ b/lib/node/ParseHooks.js
@@ -0,0 +1,162 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _promise = require('babel-runtime/core-js/promise');
+
+var _promise2 = _interopRequireDefault(_promise);
+
+exports.getFunctions = getFunctions;
+exports.getTriggers = getTriggers;
+exports.getFunction = getFunction;
+exports.getTrigger = getTrigger;
+exports.createFunction = createFunction;
+exports.createTrigger = createTrigger;
+exports.create = create;
+exports.updateFunction = updateFunction;
+exports.updateTrigger = updateTrigger;
+exports.update = update;
+exports.removeFunction = removeFunction;
+exports.removeTrigger = removeTrigger;
+exports.remove = remove;
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _decode = require('./decode');
+
+var _decode2 = _interopRequireDefault(_decode);
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _ParseError = require('./ParseError');
+
+var _ParseError2 = _interopRequireDefault(_ParseError);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+function getFunctions() {
+ return _CoreManager2.default.getHooksController().get("functions");
+}
+
+function getTriggers() {
+ return _CoreManager2.default.getHooksController().get("triggers");
+}
+
+function getFunction(name) {
+ return _CoreManager2.default.getHooksController().get("functions", name);
+}
+
+function getTrigger(className, triggerName) {
+ return _CoreManager2.default.getHooksController().get("triggers", className, triggerName);
+}
+
+function createFunction(functionName, url) {
+ return create({ functionName: functionName, url: url });
+}
+
+function createTrigger(className, triggerName, url) {
+ return create({ className: className, triggerName: triggerName, url: url });
+}
+
+function create(hook) {
+ return _CoreManager2.default.getHooksController().create(hook);
+}
+
+function updateFunction(functionName, url) {
+ return update({ functionName: functionName, url: url });
+}
+
+function updateTrigger(className, triggerName, url) {
+ return update({ className: className, triggerName: triggerName, url: url });
+}
+
+function update(hook) {
+ return _CoreManager2.default.getHooksController().update(hook);
+}
+
+function removeFunction(functionName) {
+ return remove({ functionName: functionName });
+}
+
+function removeTrigger(className, triggerName) {
+ return remove({ className: className, triggerName: triggerName });
+}
+
+function remove(hook) {
+ return _CoreManager2.default.getHooksController().remove(hook);
+}
+
+var DefaultController = {
+ get: function (type, functionName, triggerName) {
+ var url = "/hooks/" + type;
+ if (functionName) {
+ url += "/" + functionName;
+ if (triggerName) {
+ url += "/" + triggerName;
+ }
+ }
+ return this.sendRequest("GET", url);
+ },
+ create: function (hook) {
+ var url;
+ if (hook.functionName && hook.url) {
+ url = "/hooks/functions";
+ } else if (hook.className && hook.triggerName && hook.url) {
+ url = "/hooks/triggers";
+ } else {
+ return _promise2.default.reject({ error: 'invalid hook declaration', code: 143 });
+ }
+ return this.sendRequest("POST", url, hook);
+ },
+ remove: function (hook) {
+ var url;
+ if (hook.functionName) {
+ url = "/hooks/functions/" + hook.functionName;
+ delete hook.functionName;
+ } else if (hook.className && hook.triggerName) {
+ url = "/hooks/triggers/" + hook.className + "/" + hook.triggerName;
+ delete hook.className;
+ delete hook.triggerName;
+ } else {
+ return _promise2.default.reject({ error: 'invalid hook declaration', code: 143 });
+ }
+ return this.sendRequest("PUT", url, { "__op": "Delete" });
+ },
+ update: function (hook) {
+ var url;
+ if (hook.functionName && hook.url) {
+ url = "/hooks/functions/" + hook.functionName;
+ delete hook.functionName;
+ } else if (hook.className && hook.triggerName && hook.url) {
+ url = "/hooks/triggers/" + hook.className + "/" + hook.triggerName;
+ delete hook.className;
+ delete hook.triggerName;
+ } else {
+ return _promise2.default.reject({ error: 'invalid hook declaration', code: 143 });
+ }
+ return this.sendRequest('PUT', url, hook);
+ },
+ sendRequest: function (method, url, body) {
+ return _CoreManager2.default.getRESTController().request(method, url, body, { useMasterKey: true }).then(function (res) {
+ var decoded = (0, _decode2.default)(res);
+ if (decoded) {
+ return _ParsePromise2.default.as(decoded);
+ }
+ return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.INVALID_JSON, 'The server returned an invalid response.'));
+ });
+ }
+};
+
+_CoreManager2.default.setHooksController(DefaultController);
\ No newline at end of file
diff --git a/lib/node/ParseInstallation.js b/lib/node/ParseInstallation.js
new file mode 100644
index 000000000..02d7be1d8
--- /dev/null
+++ b/lib/node/ParseInstallation.js
@@ -0,0 +1,65 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _ParseObject2 = require('./ParseObject');
+
+var _ParseObject3 = _interopRequireDefault(_ParseObject2);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+var Installation = function (_ParseObject) {
+ (0, _inherits3.default)(Installation, _ParseObject);
+
+ function Installation(attributes) {
+ (0, _classCallCheck3.default)(this, Installation);
+
+ var _this = (0, _possibleConstructorReturn3.default)(this, (Installation.__proto__ || (0, _getPrototypeOf2.default)(Installation)).call(this, '_Installation'));
+
+ if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') {
+ if (!_this.set(attributes || {})) {
+ throw new Error('Can\'t create an invalid Session');
+ }
+ }
+ return _this;
+ }
+
+ return Installation;
+}(_ParseObject3.default); /**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+exports.default = Installation;
+
+_ParseObject3.default.registerSubclass('_Installation', Installation);
\ No newline at end of file
diff --git a/lib/node/ParseLiveQuery.js b/lib/node/ParseLiveQuery.js
new file mode 100644
index 000000000..a3e5fe31d
--- /dev/null
+++ b/lib/node/ParseLiveQuery.js
@@ -0,0 +1,241 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _EventEmitter = require('./EventEmitter');
+
+var _EventEmitter2 = _interopRequireDefault(_EventEmitter);
+
+var _LiveQueryClient = require('./LiveQueryClient');
+
+var _LiveQueryClient2 = _interopRequireDefault(_LiveQueryClient);
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function open() {
+ var LiveQueryController = _CoreManager2.default.getLiveQueryController();
+ LiveQueryController.open();
+}
+
+function close() {
+ var LiveQueryController = _CoreManager2.default.getLiveQueryController();
+ LiveQueryController.close();
+}
+
+/**
+ *
+ * We expose three events to help you monitor the status of the WebSocket connection:
+ *
+ * Open - When we establish the WebSocket connection to the LiveQuery server, you'll get this event. + * + *
+ * Parse.LiveQuery.on('open', () => {
+ *
+ * });
+ *
+ * Close - When we lose the WebSocket connection to the LiveQuery server, you'll get this event. + * + *
+ * Parse.LiveQuery.on('close', () => {
+ *
+ * });
+ *
+ * Error - When some network error or LiveQuery server error happens, you'll get this event. + * + *
+ * Parse.LiveQuery.on('error', (error) => {
+ *
+ * });
+ *
+ * @class Parse.LiveQuery
+ * @static
+ *
+ */
+var LiveQuery = new _EventEmitter2.default();
+
+/**
+ * After open is called, the LiveQuery will try to send a connect request
+ * to the LiveQuery server.
+ *
+ * @method open
+ */
+LiveQuery.open = open;
+
+/**
+ * When you're done using LiveQuery, you can call Parse.LiveQuery.close().
+ * This function will close the WebSocket connection to the LiveQuery server,
+ * cancel the auto reconnect, and unsubscribe all subscriptions based on it.
+ * If you call query.subscribe() after this, we'll create a new WebSocket
+ * connection to the LiveQuery server.
+ *
+ * @method close
+ */
+
+LiveQuery.close = close;
+// Register a default onError callback to make sure we do not crash on error
+LiveQuery.on('error', function () {});
+
+exports.default = LiveQuery;
+
+function getSessionToken() {
+ var controller = _CoreManager2.default.getUserController();
+ return controller.currentUserAsync().then(function (currentUser) {
+ return currentUser ? currentUser.getSessionToken() : undefined;
+ });
+}
+
+function getLiveQueryClient() {
+ return _CoreManager2.default.getLiveQueryController().getDefaultLiveQueryClient();
+}
+
+var defaultLiveQueryClient = void 0;
+var DefaultLiveQueryController = {
+ setDefaultLiveQueryClient: function (liveQueryClient) {
+ defaultLiveQueryClient = liveQueryClient;
+ },
+ getDefaultLiveQueryClient: function () {
+ if (defaultLiveQueryClient) {
+ return _ParsePromise2.default.as(defaultLiveQueryClient);
+ }
+
+ return getSessionToken().then(function (sessionToken) {
+ var liveQueryServerURL = _CoreManager2.default.get('LIVEQUERY_SERVER_URL');
+
+ if (liveQueryServerURL && liveQueryServerURL.indexOf('ws') !== 0) {
+ throw new Error('You need to set a proper Parse LiveQuery server url before using LiveQueryClient');
+ }
+
+ // If we can not find Parse.liveQueryServerURL, we try to extract it from Parse.serverURL
+ if (!liveQueryServerURL) {
+ var tempServerURL = _CoreManager2.default.get('SERVER_URL');
+ var protocol = 'ws://';
+ // If Parse is being served over SSL/HTTPS, ensure LiveQuery Server uses 'wss://' prefix
+ if (tempServerURL.indexOf('https') === 0) {
+ protocol = 'wss://';
+ }
+ var host = tempServerURL.replace(/^https?:\/\//, '');
+ liveQueryServerURL = protocol + host;
+ _CoreManager2.default.set('LIVEQUERY_SERVER_URL', liveQueryServerURL);
+ }
+
+ var applicationId = _CoreManager2.default.get('APPLICATION_ID');
+ var javascriptKey = _CoreManager2.default.get('JAVASCRIPT_KEY');
+ var masterKey = _CoreManager2.default.get('MASTER_KEY');
+ // Get currentUser sessionToken if possible
+ defaultLiveQueryClient = new _LiveQueryClient2.default({
+ applicationId: applicationId,
+ serverURL: liveQueryServerURL,
+ javascriptKey: javascriptKey,
+ masterKey: masterKey,
+ sessionToken: sessionToken
+ });
+ // Register a default onError callback to make sure we do not crash on error
+ // Cannot create these events on a nested way because of EventEmiiter from React Native
+ defaultLiveQueryClient.on('error', function (error) {
+ LiveQuery.emit('error', error);
+ });
+ defaultLiveQueryClient.on('open', function () {
+ LiveQuery.emit('open');
+ });
+ defaultLiveQueryClient.on('close', function () {
+ LiveQuery.emit('close');
+ });
+
+ return defaultLiveQueryClient;
+ });
+ },
+ open: function () {
+ var _this = this;
+
+ getLiveQueryClient().then(function (liveQueryClient) {
+ _this.resolve(liveQueryClient.open());
+ });
+ },
+ close: function () {
+ var _this2 = this;
+
+ getLiveQueryClient().then(function (liveQueryClient) {
+ _this2.resolve(liveQueryClient.close());
+ });
+ },
+ subscribe: function (query) {
+ var _this3 = this;
+
+ var subscriptionWrap = new _EventEmitter2.default();
+
+ getLiveQueryClient().then(function (liveQueryClient) {
+ if (liveQueryClient.shouldOpen()) {
+ liveQueryClient.open();
+ }
+ var promiseSessionToken = getSessionToken();
+ // new event emitter
+ return promiseSessionToken.then(function (sessionToken) {
+
+ var subscription = liveQueryClient.subscribe(query, sessionToken);
+ // enter, leave create, etc
+
+ subscriptionWrap.id = subscription.id;
+ subscriptionWrap.query = subscription.query;
+ subscriptionWrap.sessionToken = subscription.sessionToken;
+ subscriptionWrap.unsubscribe = subscription.unsubscribe;
+ // Cannot create these events on a nested way because of EventEmiiter from React Native
+ subscription.on('open', function () {
+ subscriptionWrap.emit('open');
+ });
+ subscription.on('create', function (object) {
+ subscriptionWrap.emit('create', object);
+ });
+ subscription.on('update', function (object) {
+ subscriptionWrap.emit('update', object);
+ });
+ subscription.on('enter', function (object) {
+ subscriptionWrap.emit('enter', object);
+ });
+ subscription.on('leave', function (object) {
+ subscriptionWrap.emit('leave', object);
+ });
+ subscription.on('delete', function (object) {
+ subscriptionWrap.emit('delete', object);
+ });
+
+ _this3.resolve();
+ });
+ });
+ return subscriptionWrap;
+ },
+ unsubscribe: function (subscription) {
+ var _this4 = this;
+
+ getLiveQueryClient().then(function (liveQueryClient) {
+ _this4.resolve(liveQueryClient.unsubscribe(subscription));
+ });
+ },
+ _clearCachedDefaultClient: function () {
+ defaultLiveQueryClient = null;
+ }
+};
+
+_CoreManager2.default.setLiveQueryController(DefaultLiveQueryController);
\ No newline at end of file
diff --git a/lib/node/ParseObject.js b/lib/node/ParseObject.js
new file mode 100644
index 000000000..f08795e52
--- /dev/null
+++ b/lib/node/ParseObject.js
@@ -0,0 +1,2001 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _defineProperty = require('babel-runtime/core-js/object/define-property');
+
+var _defineProperty2 = _interopRequireDefault(_defineProperty);
+
+var _create = require('babel-runtime/core-js/object/create');
+
+var _create2 = _interopRequireDefault(_create);
+
+var _freeze = require('babel-runtime/core-js/object/freeze');
+
+var _freeze2 = _interopRequireDefault(_freeze);
+
+var _stringify = require('babel-runtime/core-js/json/stringify');
+
+var _stringify2 = _interopRequireDefault(_stringify);
+
+var _keys = require('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _canBeSerialized = require('./canBeSerialized');
+
+var _canBeSerialized2 = _interopRequireDefault(_canBeSerialized);
+
+var _decode = require('./decode');
+
+var _decode2 = _interopRequireDefault(_decode);
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _equals = require('./equals');
+
+var _equals2 = _interopRequireDefault(_equals);
+
+var _escape2 = require('./escape');
+
+var _escape3 = _interopRequireDefault(_escape2);
+
+var _ParseACL = require('./ParseACL');
+
+var _ParseACL2 = _interopRequireDefault(_ParseACL);
+
+var _parseDate = require('./parseDate');
+
+var _parseDate2 = _interopRequireDefault(_parseDate);
+
+var _ParseError = require('./ParseError');
+
+var _ParseError2 = _interopRequireDefault(_ParseError);
+
+var _ParseFile = require('./ParseFile');
+
+var _ParseFile2 = _interopRequireDefault(_ParseFile);
+
+var _ParseOp = require('./ParseOp');
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+var _ParseQuery = require('./ParseQuery');
+
+var _ParseQuery2 = _interopRequireDefault(_ParseQuery);
+
+var _ParseRelation = require('./ParseRelation');
+
+var _ParseRelation2 = _interopRequireDefault(_ParseRelation);
+
+var _SingleInstanceStateController = require('./SingleInstanceStateController');
+
+var SingleInstanceStateController = _interopRequireWildcard(_SingleInstanceStateController);
+
+var _unique = require('./unique');
+
+var _unique2 = _interopRequireDefault(_unique);
+
+var _UniqueInstanceStateController = require('./UniqueInstanceStateController');
+
+var UniqueInstanceStateController = _interopRequireWildcard(_UniqueInstanceStateController);
+
+var _unsavedChildren = require('./unsavedChildren');
+
+var _unsavedChildren2 = _interopRequireDefault(_unsavedChildren);
+
+function _interopRequireWildcard(obj) {
+ if (obj && obj.__esModule) {
+ return obj;
+ } else {
+ var newObj = {};if (obj != null) {
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];
+ }
+ }newObj.default = obj;return newObj;
+ }
+}
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+// Mapping of class names to constructors, so we can populate objects from the
+// server with appropriate subclasses of ParseObject
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+var classMap = {};
+
+// Global counter for generating unique local Ids
+var localCount = 0;
+// Global counter for generating unique Ids for non-single-instance objects
+var objectCount = 0;
+// On web clients, objects are single-instance: any two objects with the same Id
+// will have the same attributes. However, this may be dangerous default
+// behavior in a server scenario
+var singleInstance = !_CoreManager2.default.get('IS_NODE');
+if (singleInstance) {
+ _CoreManager2.default.setObjectStateController(SingleInstanceStateController);
+} else {
+ _CoreManager2.default.setObjectStateController(UniqueInstanceStateController);
+}
+
+function getServerUrlPath() {
+ var serverUrl = _CoreManager2.default.get('SERVER_URL');
+ if (serverUrl[serverUrl.length - 1] !== '/') {
+ serverUrl += '/';
+ }
+ var url = serverUrl.replace(/https?:\/\//, '');
+ return url.substr(url.indexOf('/'));
+}
+
+/**
+ * Creates a new model with defined attributes.
+ *
+ * You won't normally call this method directly. It is recommended that
+ * you use a subclass of Parse.Object instead, created by calling
+ * extend.
However, if you don't want to use a subclass, or aren't sure which + * subclass is appropriate, you can use this form:
+ * var object = new Parse.Object("ClassName");
+ *
+ * That is basically equivalent to:
+ * var MyClass = Parse.Object.extend("ClassName");
+ * var object = new MyClass();
+ *
+ *
+ * @class Parse.Object
+ * @constructor
+ * @param {String} className The class name for the object
+ * @param {Object} attributes The initial set of data to store in the object.
+ * @param {Object} options The options for this object instance.
+ */
+
+var ParseObject = function () {
+ /**
+ * The ID of this object, unique within its class.
+ * @property id
+ * @type String
+ */
+ function ParseObject(className, attributes, options) {
+ (0, _classCallCheck3.default)(this, ParseObject);
+
+ // Enable legacy initializers
+ if (typeof this.initialize === 'function') {
+ this.initialize.apply(this, arguments);
+ }
+
+ var toSet = null;
+ this._objCount = objectCount++;
+ if (typeof className === 'string') {
+ this.className = className;
+ if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') {
+ toSet = attributes;
+ }
+ } else if (className && (typeof className === 'undefined' ? 'undefined' : (0, _typeof3.default)(className)) === 'object') {
+ this.className = className.className;
+ toSet = {};
+ for (var attr in className) {
+ if (attr !== 'className') {
+ toSet[attr] = className[attr];
+ }
+ }
+ if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') {
+ options = attributes;
+ }
+ }
+ if (toSet && !this.set(toSet, options)) {
+ throw new Error('Can\'t create an invalid Parse Object');
+ }
+ }
+
+ /** Prototype getters / setters **/
+
+ (0, _createClass3.default)(ParseObject, [{
+ key: '_getId',
+
+ /** Private methods **/
+
+ /**
+ * Returns a local or server Id used uniquely identify this object
+ */
+ value: function () {
+ if (typeof this.id === 'string') {
+ return this.id;
+ }
+ if (typeof this._localId === 'string') {
+ return this._localId;
+ }
+ var localId = 'local' + String(localCount++);
+ this._localId = localId;
+ return localId;
+ }
+
+ /**
+ * Returns a unique identifier used to pull data from the State Controller.
+ */
+
+ }, {
+ key: '_getStateIdentifier',
+ value: function () {
+ if (singleInstance) {
+ var _id = this.id;
+ if (!_id) {
+ _id = this._getId();
+ }
+ return {
+ id: _id,
+ className: this.className
+ };
+ } else {
+ return this;
+ }
+ }
+ }, {
+ key: '_getServerData',
+ value: function () {
+ var stateController = _CoreManager2.default.getObjectStateController();
+ return stateController.getServerData(this._getStateIdentifier());
+ }
+ }, {
+ key: '_clearServerData',
+ value: function () {
+ var serverData = this._getServerData();
+ var unset = {};
+ for (var attr in serverData) {
+ unset[attr] = undefined;
+ }
+ var stateController = _CoreManager2.default.getObjectStateController();
+ stateController.setServerData(this._getStateIdentifier(), unset);
+ }
+ }, {
+ key: '_getPendingOps',
+ value: function () {
+ var stateController = _CoreManager2.default.getObjectStateController();
+ return stateController.getPendingOps(this._getStateIdentifier());
+ }
+ }, {
+ key: '_clearPendingOps',
+ value: function () {
+ var pending = this._getPendingOps();
+ var latest = pending[pending.length - 1];
+ var keys = (0, _keys2.default)(latest);
+ keys.forEach(function (key) {
+ delete latest[key];
+ });
+ }
+ }, {
+ key: '_getDirtyObjectAttributes',
+ value: function () {
+ var attributes = this.attributes;
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var objectCache = stateController.getObjectCache(this._getStateIdentifier());
+ var dirty = {};
+ for (var attr in attributes) {
+ var val = attributes[attr];
+ if (val && (typeof val === 'undefined' ? 'undefined' : (0, _typeof3.default)(val)) === 'object' && !(val instanceof ParseObject) && !(val instanceof _ParseFile2.default) && !(val instanceof _ParseRelation2.default)) {
+ // Due to the way browsers construct maps, the key order will not change
+ // unless the object is changed
+ try {
+ var json = (0, _encode2.default)(val, false, true);
+ var stringified = (0, _stringify2.default)(json);
+ if (objectCache[attr] !== stringified) {
+ dirty[attr] = val;
+ }
+ } catch (e) {
+ // Error occurred, possibly by a nested unsaved pointer in a mutable container
+ // No matter how it happened, it indicates a change in the attribute
+ dirty[attr] = val;
+ }
+ }
+ }
+ return dirty;
+ }
+ }, {
+ key: '_toFullJSON',
+ value: function (seen) {
+ var json = this.toJSON(seen);
+ json.__type = 'Object';
+ json.className = this.className;
+ return json;
+ }
+ }, {
+ key: '_getSaveJSON',
+ value: function () {
+ var pending = this._getPendingOps();
+ var dirtyObjects = this._getDirtyObjectAttributes();
+ var json = {};
+
+ for (var attr in dirtyObjects) {
+ json[attr] = new _ParseOp.SetOp(dirtyObjects[attr]).toJSON();
+ }
+ for (attr in pending[0]) {
+ json[attr] = pending[0][attr].toJSON();
+ }
+ return json;
+ }
+ }, {
+ key: '_getSaveParams',
+ value: function () {
+ var method = this.id ? 'PUT' : 'POST';
+ var body = this._getSaveJSON();
+ var path = 'classes/' + this.className;
+ if (this.id) {
+ path += '/' + this.id;
+ } else if (this.className === '_User') {
+ path = 'users';
+ }
+ return {
+ method: method,
+ body: body,
+ path: path
+ };
+ }
+ }, {
+ key: '_finishFetch',
+ value: function (serverData) {
+ if (!this.id && serverData.objectId) {
+ this.id = serverData.objectId;
+ }
+ var stateController = _CoreManager2.default.getObjectStateController();
+ stateController.initializeState(this._getStateIdentifier());
+ var decoded = {};
+ for (var attr in serverData) {
+ if (attr === 'ACL') {
+ decoded[attr] = new _ParseACL2.default(serverData[attr]);
+ } else if (attr !== 'objectId') {
+ decoded[attr] = (0, _decode2.default)(serverData[attr]);
+ if (decoded[attr] instanceof _ParseRelation2.default) {
+ decoded[attr]._ensureParentAndKey(this, attr);
+ }
+ }
+ }
+ if (decoded.createdAt && typeof decoded.createdAt === 'string') {
+ decoded.createdAt = (0, _parseDate2.default)(decoded.createdAt);
+ }
+ if (decoded.updatedAt && typeof decoded.updatedAt === 'string') {
+ decoded.updatedAt = (0, _parseDate2.default)(decoded.updatedAt);
+ }
+ if (!decoded.updatedAt && decoded.createdAt) {
+ decoded.updatedAt = decoded.createdAt;
+ }
+ stateController.commitServerChanges(this._getStateIdentifier(), decoded);
+ }
+ }, {
+ key: '_setExisted',
+ value: function (existed) {
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var state = stateController.getState(this._getStateIdentifier());
+ if (state) {
+ state.existed = existed;
+ }
+ }
+ }, {
+ key: '_migrateId',
+ value: function (serverId) {
+ if (this._localId && serverId) {
+ if (singleInstance) {
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var oldState = stateController.removeState(this._getStateIdentifier());
+ this.id = serverId;
+ delete this._localId;
+ if (oldState) {
+ stateController.initializeState(this._getStateIdentifier(), oldState);
+ }
+ } else {
+ this.id = serverId;
+ delete this._localId;
+ }
+ }
+ }
+ }, {
+ key: '_handleSaveResponse',
+ value: function (response, status) {
+ var changes = {};
+
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var pending = stateController.popPendingState(this._getStateIdentifier());
+ for (var attr in pending) {
+ if (pending[attr] instanceof _ParseOp.RelationOp) {
+ changes[attr] = pending[attr].applyTo(undefined, this, attr);
+ } else if (!(attr in response)) {
+ // Only SetOps and UnsetOps should not come back with results
+ changes[attr] = pending[attr].applyTo(undefined);
+ }
+ }
+ for (attr in response) {
+ if ((attr === 'createdAt' || attr === 'updatedAt') && typeof response[attr] === 'string') {
+ changes[attr] = (0, _parseDate2.default)(response[attr]);
+ } else if (attr === 'ACL') {
+ changes[attr] = new _ParseACL2.default(response[attr]);
+ } else if (attr !== 'objectId') {
+ changes[attr] = (0, _decode2.default)(response[attr]);
+ if (changes[attr] instanceof _ParseOp.UnsetOp) {
+ changes[attr] = undefined;
+ }
+ }
+ }
+ if (changes.createdAt && !changes.updatedAt) {
+ changes.updatedAt = changes.createdAt;
+ }
+
+ this._migrateId(response.objectId);
+
+ if (status !== 201) {
+ this._setExisted(true);
+ }
+
+ stateController.commitServerChanges(this._getStateIdentifier(), changes);
+ }
+ }, {
+ key: '_handleSaveError',
+ value: function () {
+ this._getPendingOps();
+
+ var stateController = _CoreManager2.default.getObjectStateController();
+ stateController.mergeFirstPendingState(this._getStateIdentifier());
+ }
+
+ /** Public methods **/
+
+ }, {
+ key: 'initialize',
+ value: function () {}
+ // NOOP
+
+
+ /**
+ * Returns a JSON version of the object suitable for saving to Parse.
+ * @method toJSON
+ * @return {Object}
+ */
+
+ }, {
+ key: 'toJSON',
+ value: function (seen) {
+ var seenEntry = this.id ? this.className + ':' + this.id : this;
+ var seen = seen || [seenEntry];
+ var json = {};
+ var attrs = this.attributes;
+ for (var attr in attrs) {
+ if ((attr === 'createdAt' || attr === 'updatedAt') && attrs[attr].toJSON) {
+ json[attr] = attrs[attr].toJSON();
+ } else {
+ json[attr] = (0, _encode2.default)(attrs[attr], false, false, seen);
+ }
+ }
+ var pending = this._getPendingOps();
+ for (var attr in pending[0]) {
+ json[attr] = pending[0][attr].toJSON();
+ }
+
+ if (this.id) {
+ json.objectId = this.id;
+ }
+ return json;
+ }
+
+ /**
+ * Determines whether this ParseObject is equal to another ParseObject
+ * @method equals
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'equals',
+ value: function (other) {
+ if (this === other) {
+ return true;
+ }
+ return other instanceof ParseObject && this.className === other.className && this.id === other.id && typeof this.id !== 'undefined';
+ }
+
+ /**
+ * Returns true if this object has been modified since its last
+ * save/refresh. If an attribute is specified, it returns true only if that
+ * particular attribute has been modified since the last save/refresh.
+ * @method dirty
+ * @param {String} attr An attribute name (optional).
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'dirty',
+ value: function (attr) {
+ if (!this.id) {
+ return true;
+ }
+ var pendingOps = this._getPendingOps();
+ var dirtyObjects = this._getDirtyObjectAttributes();
+ if (attr) {
+ if (dirtyObjects.hasOwnProperty(attr)) {
+ return true;
+ }
+ for (var i = 0; i < pendingOps.length; i++) {
+ if (pendingOps[i].hasOwnProperty(attr)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ if ((0, _keys2.default)(pendingOps[0]).length !== 0) {
+ return true;
+ }
+ if ((0, _keys2.default)(dirtyObjects).length !== 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns an array of keys that have been modified since last save/refresh
+ * @method dirtyKeys
+ * @return {Array of string}
+ */
+
+ }, {
+ key: 'dirtyKeys',
+ value: function () {
+ var pendingOps = this._getPendingOps();
+ var keys = {};
+ for (var i = 0; i < pendingOps.length; i++) {
+ for (var attr in pendingOps[i]) {
+ keys[attr] = true;
+ }
+ }
+ var dirtyObjects = this._getDirtyObjectAttributes();
+ for (var attr in dirtyObjects) {
+ keys[attr] = true;
+ }
+ return (0, _keys2.default)(keys);
+ }
+
+ /**
+ * Gets a Pointer referencing this Object.
+ * @method toPointer
+ * @return {Object}
+ */
+
+ }, {
+ key: 'toPointer',
+ value: function () {
+ if (!this.id) {
+ throw new Error('Cannot create a pointer to an unsaved ParseObject');
+ }
+ return {
+ __type: 'Pointer',
+ className: this.className,
+ objectId: this.id
+ };
+ }
+
+ /**
+ * Gets the value of an attribute.
+ * @method get
+ * @param {String} attr The string name of an attribute.
+ */
+
+ }, {
+ key: 'get',
+ value: function (attr) {
+ return this.attributes[attr];
+ }
+
+ /**
+ * Gets a relation on the given class for the attribute.
+ * @method relation
+ * @param String attr The attribute to get the relation for.
+ */
+
+ }, {
+ key: 'relation',
+ value: function (attr) {
+ var value = this.get(attr);
+ if (value) {
+ if (!(value instanceof _ParseRelation2.default)) {
+ throw new Error('Called relation() on non-relation field ' + attr);
+ }
+ value._ensureParentAndKey(this, attr);
+ return value;
+ }
+ return new _ParseRelation2.default(this, attr);
+ }
+
+ /**
+ * Gets the HTML-escaped value of an attribute.
+ * @method escape
+ * @param {String} attr The string name of an attribute.
+ */
+
+ }, {
+ key: 'escape',
+ value: function (attr) {
+ var val = this.attributes[attr];
+ if (val == null) {
+ return '';
+ }
+
+ if (typeof val !== 'string') {
+ if (typeof val.toString !== 'function') {
+ return '';
+ }
+ val = val.toString();
+ }
+ return (0, _escape3.default)(val);
+ }
+
+ /**
+ * Returns true if the attribute contains a value that is not
+ * null or undefined.
+ * @method has
+ * @param {String} attr The string name of the attribute.
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'has',
+ value: function (attr) {
+ var attributes = this.attributes;
+ if (attributes.hasOwnProperty(attr)) {
+ return attributes[attr] != null;
+ }
+ return false;
+ }
+
+ /**
+ * Sets a hash of model attributes on the object.
+ *
+ * You can call it with an object containing keys and values, or with one + * key and value. For example:
+ * gameTurn.set({
+ * player: player1,
+ * diceRoll: 2
+ * }, {
+ * error: function(gameTurnAgain, error) {
+ * // The set failed validation.
+ * }
+ * });
+ *
+ * game.set("currentPlayer", player2, {
+ * error: function(gameTurnAgain, error) {
+ * // The set failed validation.
+ * }
+ * });
+ *
+ * game.set("finished", true);
+ *
+ * @method set
+ * @param {String} key The key to set.
+ * @param {} value The value to give it.
+ * @param {Object} options A set of options for the set.
+ * The only supported option is error.
+ * @return {Boolean} true if the set succeeded.
+ */
+
+ }, {
+ key: 'set',
+ value: function (key, value, options) {
+ var changes = {};
+ var newOps = {};
+ if (key && (typeof key === 'undefined' ? 'undefined' : (0, _typeof3.default)(key)) === 'object') {
+ changes = key;
+ options = value;
+ } else if (typeof key === 'string') {
+ changes[key] = value;
+ } else {
+ return this;
+ }
+
+ options = options || {};
+ var readonly = [];
+ if (typeof this.constructor.readOnlyAttributes === 'function') {
+ readonly = readonly.concat(this.constructor.readOnlyAttributes());
+ }
+ for (var k in changes) {
+ if (k === 'createdAt' || k === 'updatedAt') {
+ // This property is read-only, but for legacy reasons we silently
+ // ignore it
+ continue;
+ }
+ if (readonly.indexOf(k) > -1) {
+ throw new Error('Cannot modify readonly attribute: ' + k);
+ }
+ if (options.unset) {
+ newOps[k] = new _ParseOp.UnsetOp();
+ } else if (changes[k] instanceof _ParseOp.Op) {
+ newOps[k] = changes[k];
+ } else if (changes[k] && (0, _typeof3.default)(changes[k]) === 'object' && typeof changes[k].__op === 'string') {
+ newOps[k] = (0, _ParseOp.opFromJSON)(changes[k]);
+ } else if (k === 'objectId' || k === 'id') {
+ if (typeof changes[k] === 'string') {
+ this.id = changes[k];
+ }
+ } else if (k === 'ACL' && (0, _typeof3.default)(changes[k]) === 'object' && !(changes[k] instanceof _ParseACL2.default)) {
+ newOps[k] = new _ParseOp.SetOp(new _ParseACL2.default(changes[k]));
+ } else {
+ newOps[k] = new _ParseOp.SetOp(changes[k]);
+ }
+ }
+
+ // Calculate new values
+ var currentAttributes = this.attributes;
+ var newValues = {};
+ for (var attr in newOps) {
+ if (newOps[attr] instanceof _ParseOp.RelationOp) {
+ newValues[attr] = newOps[attr].applyTo(currentAttributes[attr], this, attr);
+ } else if (!(newOps[attr] instanceof _ParseOp.UnsetOp)) {
+ newValues[attr] = newOps[attr].applyTo(currentAttributes[attr]);
+ }
+ }
+
+ // Validate changes
+ if (!options.ignoreValidation) {
+ var validation = this.validate(newValues);
+ if (validation) {
+ if (typeof options.error === 'function') {
+ options.error(this, validation);
+ }
+ return false;
+ }
+ }
+
+ // Consolidate Ops
+ var pendingOps = this._getPendingOps();
+ var last = pendingOps.length - 1;
+ var stateController = _CoreManager2.default.getObjectStateController();
+ for (var attr in newOps) {
+ var nextOp = newOps[attr].mergeWith(pendingOps[last][attr]);
+ stateController.setPendingOp(this._getStateIdentifier(), attr, nextOp);
+ }
+
+ return this;
+ }
+
+ /**
+ * Remove an attribute from the model. This is a noop if the attribute doesn't
+ * exist.
+ * @method unset
+ * @param {String} attr The string name of an attribute.
+ */
+
+ }, {
+ key: 'unset',
+ value: function (attr, options) {
+ options = options || {};
+ options.unset = true;
+ return this.set(attr, null, options);
+ }
+
+ /**
+ * Atomically increments the value of the given attribute the next time the
+ * object is saved. If no amount is specified, 1 is used by default.
+ *
+ * @method increment
+ * @param attr {String} The key.
+ * @param amount {Number} The amount to increment by (optional).
+ */
+
+ }, {
+ key: 'increment',
+ value: function (attr, amount) {
+ if (typeof amount === 'undefined') {
+ amount = 1;
+ }
+ if (typeof amount !== 'number') {
+ throw new Error('Cannot increment by a non-numeric amount.');
+ }
+ return this.set(attr, new _ParseOp.IncrementOp(amount));
+ }
+
+ /**
+ * Atomically add an object to the end of the array associated with a given
+ * key.
+ * @method add
+ * @param attr {String} The key.
+ * @param item {} The item to add.
+ */
+
+ }, {
+ key: 'add',
+ value: function (attr, item) {
+ return this.set(attr, new _ParseOp.AddOp([item]));
+ }
+
+ /**
+ * Atomically add an object to the array associated with a given key, only
+ * if it is not already present in the array. The position of the insert is
+ * not guaranteed.
+ *
+ * @method addUnique
+ * @param attr {String} The key.
+ * @param item {} The object to add.
+ */
+
+ }, {
+ key: 'addUnique',
+ value: function (attr, item) {
+ return this.set(attr, new _ParseOp.AddUniqueOp([item]));
+ }
+
+ /**
+ * Atomically remove all instances of an object from the array associated
+ * with a given key.
+ *
+ * @method remove
+ * @param attr {String} The key.
+ * @param item {} The object to remove.
+ */
+
+ }, {
+ key: 'remove',
+ value: function (attr, item) {
+ return this.set(attr, new _ParseOp.RemoveOp([item]));
+ }
+
+ /**
+ * Returns an instance of a subclass of Parse.Op describing what kind of
+ * modification has been performed on this field since the last time it was
+ * saved. For example, after calling object.increment("x"), calling
+ * object.op("x") would return an instance of Parse.Op.Increment.
+ *
+ * @method op
+ * @param attr {String} The key.
+ * @returns {Parse.Op} The operation, or undefined if none.
+ */
+
+ }, {
+ key: 'op',
+ value: function (attr) {
+ var pending = this._getPendingOps();
+ for (var i = pending.length; i--;) {
+ if (pending[i][attr]) {
+ return pending[i][attr];
+ }
+ }
+ }
+
+ /**
+ * Creates a new model with identical attributes to this one, similar to Backbone.Model's clone()
+ * @method clone
+ * @return {Parse.Object}
+ */
+
+ }, {
+ key: 'clone',
+ value: function () {
+ var clone = new this.constructor();
+ if (!clone.className) {
+ clone.className = this.className;
+ }
+ var attributes = this.attributes;
+ if (typeof this.constructor.readOnlyAttributes === 'function') {
+ var readonly = this.constructor.readOnlyAttributes() || [];
+ // Attributes are frozen, so we have to rebuild an object,
+ // rather than delete readonly keys
+ var copy = {};
+ for (var a in attributes) {
+ if (readonly.indexOf(a) < 0) {
+ copy[a] = attributes[a];
+ }
+ }
+ attributes = copy;
+ }
+ if (clone.set) {
+ clone.set(attributes);
+ }
+ return clone;
+ }
+
+ /**
+ * Creates a new instance of this object. Not to be confused with clone()
+ * @method newInstance
+ * @return {Parse.Object}
+ */
+
+ }, {
+ key: 'newInstance',
+ value: function () {
+ var clone = new this.constructor();
+ if (!clone.className) {
+ clone.className = this.className;
+ }
+ clone.id = this.id;
+ if (singleInstance) {
+ // Just return an object with the right id
+ return clone;
+ }
+
+ var stateController = _CoreManager2.default.getObjectStateController();
+ if (stateController) {
+ stateController.duplicateState(this._getStateIdentifier(), clone._getStateIdentifier());
+ }
+ return clone;
+ }
+
+ /**
+ * Returns true if this object has never been saved to Parse.
+ * @method isNew
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'isNew',
+ value: function () {
+ return !this.id;
+ }
+
+ /**
+ * Returns true if this object was created by the Parse server when the
+ * object might have already been there (e.g. in the case of a Facebook
+ * login)
+ * @method existed
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'existed',
+ value: function () {
+ if (!this.id) {
+ return false;
+ }
+ var stateController = _CoreManager2.default.getObjectStateController();
+ var state = stateController.getState(this._getStateIdentifier());
+ if (state) {
+ return state.existed;
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the model is currently in a valid state.
+ * @method isValid
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'isValid',
+ value: function () {
+ return !this.validate(this.attributes);
+ }
+
+ /**
+ * You should not call this function directly unless you subclass
+ * Parse.Object, in which case you can override this method
+ * to provide additional validation on set and
+ * save. Your implementation should return
+ *
+ * @method validate
+ * @param {Object} attrs The current data to validate.
+ * @return {} False if the data is valid. An error object otherwise.
+ * @see Parse.Object#set
+ */
+
+ }, {
+ key: 'validate',
+ value: function (attrs) {
+ if (attrs.hasOwnProperty('ACL') && !(attrs.ACL instanceof _ParseACL2.default)) {
+ return new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'ACL must be a Parse ACL.');
+ }
+ for (var key in attrs) {
+ if (!/^[A-Za-z][0-9A-Za-z_]*$/.test(key)) {
+ return new _ParseError2.default(_ParseError2.default.INVALID_KEY_NAME);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the ACL for this object.
+ * @method getACL
+ * @returns {Parse.ACL} An instance of Parse.ACL.
+ * @see Parse.Object#get
+ */
+
+ }, {
+ key: 'getACL',
+ value: function () {
+ var acl = this.get('ACL');
+ if (acl instanceof _ParseACL2.default) {
+ return acl;
+ }
+ return null;
+ }
+
+ /**
+ * Sets the ACL to be used for this object.
+ * @method setACL
+ * @param {Parse.ACL} acl An instance of Parse.ACL.
+ * @param {Object} options Optional Backbone-like options object to be
+ * passed in to set.
+ * @return {Boolean} Whether the set passed validation.
+ * @see Parse.Object#set
+ */
+
+ }, {
+ key: 'setACL',
+ value: function (acl, options) {
+ return this.set('ACL', acl, options);
+ }
+
+ /**
+ * Clears any changes to this object made since the last call to save()
+ * @method revert
+ */
+
+ }, {
+ key: 'revert',
+ value: function () {
+ this._clearPendingOps();
+ }
+
+ /**
+ * Clears all attributes on a model
+ * @method clear
+ */
+
+ }, {
+ key: 'clear',
+ value: function () {
+ var attributes = this.attributes;
+ var erasable = {};
+ var readonly = ['createdAt', 'updatedAt'];
+ if (typeof this.constructor.readOnlyAttributes === 'function') {
+ readonly = readonly.concat(this.constructor.readOnlyAttributes());
+ }
+ for (var attr in attributes) {
+ if (readonly.indexOf(attr) < 0) {
+ erasable[attr] = true;
+ }
+ }
+ return this.set(erasable, { unset: true });
+ }
+
+ /**
+ * Fetch the model from the server. If the server's representation of the
+ * model differs from its current attributes, they will be overriden.
+ *
+ * @method fetch
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:+ * object.save();+ * or
+ * object.save(null, options);+ * or
+ * object.save(attrs, options);+ * or
+ * object.save(key, value, options);+ * + * For example,
+ * gameTurn.save({
+ * player: "Jake Cutter",
+ * diceRoll: 2
+ * }, {
+ * success: function(gameTurnAgain) {
+ * // The save was successful.
+ * },
+ * error: function(gameTurnAgain, error) {
+ * // The save failed. Error is an instance of Parse.Error.
+ * }
+ * });
+ * or with promises:
+ * gameTurn.save({
+ * player: "Jake Cutter",
+ * diceRoll: 2
+ * }).then(function(gameTurnAgain) {
+ * // The save was successful.
+ * }, function(error) {
+ * // The save failed. Error is an instance of Parse.Error.
+ * });
+ *
+ * @method save
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:
+ * Parse.Object.fetchAll([object1, object2, ...], {
+ * success: function(list) {
+ * // All the objects were fetched.
+ * },
+ * error: function(error) {
+ * // An error occurred while fetching one of the objects.
+ * },
+ * });
+ *
+ *
+ * @method fetchAll
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:
+ * Parse.Object.fetchAllIfNeeded([object1, ...], {
+ * success: function(list) {
+ * // Objects were fetched and updated.
+ * },
+ * error: function(error) {
+ * // An error occurred while fetching one of the objects.
+ * },
+ * });
+ *
+ *
+ * @method fetchAllIfNeeded
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:Unlike saveAll, if an error occurs while deleting an individual model, + * this method will continue trying to delete the rest of the models if + * possible, except in the case of a fatal error like a connection error. + * + *
In particular, the Parse.Error object returned in the case of error may + * be one of two types: + * + *
+ * Parse.Object.destroyAll([object1, object2, ...], {
+ * success: function() {
+ * // All the objects were deleted.
+ * },
+ * error: function(error) {
+ * // An error occurred while deleting one or more of the objects.
+ * // If this is an aggregate error, then we can inspect each error
+ * // object individually to determine the reason why a particular
+ * // object was not deleted.
+ * if (error.code === Parse.Error.AGGREGATE_ERROR) {
+ * for (var i = 0; i < error.errors.length; i++) {
+ * console.log("Couldn't delete " + error.errors[i].object.id +
+ * "due to " + error.errors[i].message);
+ * }
+ * } else {
+ * console.log("Delete aborted because of " + error.message);
+ * }
+ * },
+ * });
+ *
+ *
+ * @method destroyAll
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:
+ * Parse.Object.saveAll([object1, object2, ...], {
+ * success: function(list) {
+ * // All the objects were saved.
+ * },
+ * error: function(error) {
+ * // An error occurred while saving one of the objects.
+ * },
+ * });
+ *
+ *
+ * @method saveAll
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:A shortcut for:
+ * var Foo = Parse.Object.extend("Foo");
+ * var pointerToFoo = new Foo();
+ * pointerToFoo.id = "myObjectId";
+ *
+ *
+ * @method createWithoutData
+ * @param {String} id The ID of the object to create a reference to.
+ * @static
+ * @return {Parse.Object} A Parse.Object reference.
+ */
+
+ }, {
+ key: 'createWithoutData',
+ value: function (id) {
+ var obj = new this();
+ obj.id = id;
+ return obj;
+ }
+
+ /**
+ * Creates a new instance of a Parse Object from a JSON representation.
+ * @method fromJSON
+ * @param {Object} json The JSON map of the Object's data
+ * @param {boolean} override In single instance mode, all old server data
+ * is overwritten if this is set to true
+ * @static
+ * @return {Parse.Object} A Parse.Object reference
+ */
+
+ }, {
+ key: 'fromJSON',
+ value: function (json, override) {
+ if (!json.className) {
+ throw new Error('Cannot create an object without a className');
+ }
+ var constructor = classMap[json.className];
+ var o = constructor ? new constructor() : new ParseObject(json.className);
+ var otherAttributes = {};
+ for (var attr in json) {
+ if (attr !== 'className' && attr !== '__type') {
+ otherAttributes[attr] = json[attr];
+ }
+ }
+ if (override) {
+ // id needs to be set before clearServerData can work
+ if (otherAttributes.objectId) {
+ o.id = otherAttributes.objectId;
+ }
+ var preserved = null;
+ if (typeof o._preserveFieldsOnFetch === 'function') {
+ preserved = o._preserveFieldsOnFetch();
+ }
+ o._clearServerData();
+ if (preserved) {
+ o._finishFetch(preserved);
+ }
+ }
+ o._finishFetch(otherAttributes);
+ if (json.objectId) {
+ o._setExisted(true);
+ }
+ return o;
+ }
+
+ /**
+ * Registers a subclass of Parse.Object with a specific class name.
+ * When objects of that class are retrieved from a query, they will be
+ * instantiated with this subclass.
+ * This is only necessary when using ES6 subclassing.
+ * @method registerSubclass
+ * @param {String} className The class name of the subclass
+ * @param {Class} constructor The subclass
+ */
+
+ }, {
+ key: 'registerSubclass',
+ value: function (className, constructor) {
+ if (typeof className !== 'string') {
+ throw new TypeError('The first argument must be a valid class name.');
+ }
+ if (typeof constructor === 'undefined') {
+ throw new TypeError('You must supply a subclass constructor.');
+ }
+ if (typeof constructor !== 'function') {
+ throw new TypeError('You must register the subclass constructor. ' + 'Did you attempt to register an instance of the subclass?');
+ }
+ classMap[className] = constructor;
+ if (!constructor.className) {
+ constructor.className = className;
+ }
+ }
+
+ /**
+ * Creates a new subclass of Parse.Object for the given Parse class name.
+ *
+ * Every extension of a Parse class will inherit from the most recent + * previous extension of that class. When a Parse.Object is automatically + * created by parsing JSON, it will use the most recent extension of that + * class.
+ * + *You should call either:
+ * var MyClass = Parse.Object.extend("MyClass", {
+ * Instance methods,
+ * initialize: function(attrs, options) {
+ * this.someInstanceProperty = [],
+ * Other instance properties
+ * }
+ * }, {
+ * Class properties
+ * });
+ * or, for Backbone compatibility:
+ * var MyClass = Parse.Object.extend({
+ * className: "MyClass",
+ * Instance methods,
+ * initialize: function(attrs, options) {
+ * this.someInstanceProperty = [],
+ * Other instance properties
+ * }
+ * }, {
+ * Class properties
+ * });
+ *
+ * @method extend
+ * @param {String} className The name of the Parse class backing this model.
+ * @param {Object} protoProps Instance properties to add to instances of the
+ * class returned from this method.
+ * @param {Object} classProps Class properties to add the class returned from
+ * this method.
+ * @return {Class} A new subclass of Parse.Object.
+ */
+
+ }, {
+ key: 'extend',
+ value: function (className, protoProps, classProps) {
+ if (typeof className !== 'string') {
+ if (className && typeof className.className === 'string') {
+ return ParseObject.extend(className.className, className, protoProps);
+ } else {
+ throw new Error('Parse.Object.extend\'s first argument should be the className.');
+ }
+ }
+ var adjustedClassName = className;
+
+ if (adjustedClassName === 'User' && _CoreManager2.default.get('PERFORM_USER_REWRITE')) {
+ adjustedClassName = '_User';
+ }
+
+ var parentProto = ParseObject.prototype;
+ if (this.hasOwnProperty('__super__') && this.__super__) {
+ parentProto = this.prototype;
+ } else if (classMap[adjustedClassName]) {
+ parentProto = classMap[adjustedClassName].prototype;
+ }
+ var ParseObjectSubclass = function (attributes, options) {
+ this.className = adjustedClassName;
+ this._objCount = objectCount++;
+ // Enable legacy initializers
+ if (typeof this.initialize === 'function') {
+ this.initialize.apply(this, arguments);
+ }
+
+ if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') {
+ if (!this.set(attributes || {}, options)) {
+ throw new Error('Can\'t create an invalid Parse Object');
+ }
+ }
+ };
+ ParseObjectSubclass.className = adjustedClassName;
+ ParseObjectSubclass.__super__ = parentProto;
+
+ ParseObjectSubclass.prototype = (0, _create2.default)(parentProto, {
+ constructor: {
+ value: ParseObjectSubclass,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+ });
+
+ if (protoProps) {
+ for (var prop in protoProps) {
+ if (prop !== 'className') {
+ (0, _defineProperty2.default)(ParseObjectSubclass.prototype, prop, {
+ value: protoProps[prop],
+ enumerable: false,
+ writable: true,
+ configurable: true
+ });
+ }
+ }
+ }
+
+ if (classProps) {
+ for (var prop in classProps) {
+ if (prop !== 'className') {
+ (0, _defineProperty2.default)(ParseObjectSubclass, prop, {
+ value: classProps[prop],
+ enumerable: false,
+ writable: true,
+ configurable: true
+ });
+ }
+ }
+ }
+
+ ParseObjectSubclass.extend = function (name, protoProps, classProps) {
+ if (typeof name === 'string') {
+ return ParseObject.extend.call(ParseObjectSubclass, name, protoProps, classProps);
+ }
+ return ParseObject.extend.call(ParseObjectSubclass, adjustedClassName, name, protoProps);
+ };
+ ParseObjectSubclass.createWithoutData = ParseObject.createWithoutData;
+
+ classMap[adjustedClassName] = ParseObjectSubclass;
+ return ParseObjectSubclass;
+ }
+
+ /**
+ * Enable single instance objects, where any local objects with the same Id
+ * share the same attributes, and stay synchronized with each other.
+ * This is disabled by default in server environments, since it can lead to
+ * security issues.
+ * @method enableSingleInstance
+ */
+
+ }, {
+ key: 'enableSingleInstance',
+ value: function () {
+ singleInstance = true;
+ _CoreManager2.default.setObjectStateController(SingleInstanceStateController);
+ }
+
+ /**
+ * Disable single instance objects, where any local objects with the same Id
+ * share the same attributes, and stay synchronized with each other.
+ * When disabled, you can have two instances of the same object in memory
+ * without them sharing attributes.
+ * @method disableSingleInstance
+ */
+
+ }, {
+ key: 'disableSingleInstance',
+ value: function () {
+ singleInstance = false;
+ _CoreManager2.default.setObjectStateController(UniqueInstanceStateController);
+ }
+ }]);
+ return ParseObject;
+}();
+
+exports.default = ParseObject;
+
+var DefaultController = {
+ fetch: function (target, forceFetch, options) {
+ if (Array.isArray(target)) {
+ if (target.length < 1) {
+ return _ParsePromise2.default.as([]);
+ }
+ var objs = [];
+ var ids = [];
+ var className = null;
+ var results = [];
+ var error = null;
+ target.forEach(function (el, i) {
+ if (error) {
+ return;
+ }
+ if (!className) {
+ className = el.className;
+ }
+ if (className !== el.className) {
+ error = new _ParseError2.default(_ParseError2.default.INVALID_CLASS_NAME, 'All objects should be of the same class');
+ }
+ if (!el.id) {
+ error = new _ParseError2.default(_ParseError2.default.MISSING_OBJECT_ID, 'All objects must have an ID');
+ }
+ if (forceFetch || (0, _keys2.default)(el._getServerData()).length === 0) {
+ ids.push(el.id);
+ objs.push(el);
+ }
+ results.push(el);
+ });
+ if (error) {
+ return _ParsePromise2.default.error(error);
+ }
+ var query = new _ParseQuery2.default(className);
+ query.containedIn('objectId', ids);
+ query._limit = ids.length;
+ return query.find(options).then(function (objects) {
+ var idMap = {};
+ objects.forEach(function (o) {
+ idMap[o.id] = o;
+ });
+ for (var i = 0; i < objs.length; i++) {
+ var obj = objs[i];
+ if (!obj || !obj.id || !idMap[obj.id]) {
+ if (forceFetch) {
+ return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OBJECT_NOT_FOUND, 'All objects must exist on the server.'));
+ }
+ }
+ }
+ if (!singleInstance) {
+ // If single instance objects are disabled, we need to replace the
+ for (var i = 0; i < results.length; i++) {
+ var obj = results[i];
+ if (obj && obj.id && idMap[obj.id]) {
+ var id = obj.id;
+ obj._finishFetch(idMap[id].toJSON());
+ results[i] = idMap[id];
+ }
+ }
+ }
+ return _ParsePromise2.default.as(results);
+ });
+ } else {
+ var RESTController = _CoreManager2.default.getRESTController();
+ return RESTController.request('GET', 'classes/' + target.className + '/' + target._getId(), {}, options).then(function (response, status, xhr) {
+ if (target instanceof ParseObject) {
+ target._clearPendingOps();
+ target._clearServerData();
+ target._finishFetch(response);
+ }
+ return target;
+ });
+ }
+ },
+ destroy: function (target, options) {
+ var RESTController = _CoreManager2.default.getRESTController();
+ if (Array.isArray(target)) {
+ if (target.length < 1) {
+ return _ParsePromise2.default.as([]);
+ }
+ var batches = [[]];
+ target.forEach(function (obj) {
+ if (!obj.id) {
+ return;
+ }
+ batches[batches.length - 1].push(obj);
+ if (batches[batches.length - 1].length >= 20) {
+ batches.push([]);
+ }
+ });
+ if (batches[batches.length - 1].length === 0) {
+ // If the last batch is empty, remove it
+ batches.pop();
+ }
+ var deleteCompleted = _ParsePromise2.default.as();
+ var errors = [];
+ batches.forEach(function (batch) {
+ deleteCompleted = deleteCompleted.then(function () {
+ return RESTController.request('POST', 'batch', {
+ requests: batch.map(function (obj) {
+ return {
+ method: 'DELETE',
+ path: getServerUrlPath() + 'classes/' + obj.className + '/' + obj._getId(),
+ body: {}
+ };
+ })
+ }, options).then(function (results) {
+ for (var i = 0; i < results.length; i++) {
+ if (results[i] && results[i].hasOwnProperty('error')) {
+ var err = new _ParseError2.default(results[i].error.code, results[i].error.error);
+ err.object = batch[i];
+ errors.push(err);
+ }
+ }
+ });
+ });
+ });
+ return deleteCompleted.then(function () {
+ if (errors.length) {
+ var aggregate = new _ParseError2.default(_ParseError2.default.AGGREGATE_ERROR);
+ aggregate.errors = errors;
+ return _ParsePromise2.default.error(aggregate);
+ }
+ return _ParsePromise2.default.as(target);
+ });
+ } else if (target instanceof ParseObject) {
+ return RESTController.request('DELETE', 'classes/' + target.className + '/' + target._getId(), {}, options).then(function () {
+ return _ParsePromise2.default.as(target);
+ });
+ }
+ return _ParsePromise2.default.as(target);
+ },
+ save: function (target, options) {
+ var RESTController = _CoreManager2.default.getRESTController();
+ var stateController = _CoreManager2.default.getObjectStateController();
+ if (Array.isArray(target)) {
+ if (target.length < 1) {
+ return _ParsePromise2.default.as([]);
+ }
+
+ var unsaved = target.concat();
+ for (var i = 0; i < target.length; i++) {
+ if (target[i] instanceof ParseObject) {
+ unsaved = unsaved.concat((0, _unsavedChildren2.default)(target[i], true));
+ }
+ }
+ unsaved = (0, _unique2.default)(unsaved);
+
+ var filesSaved = _ParsePromise2.default.as();
+ var pending = [];
+ unsaved.forEach(function (el) {
+ if (el instanceof _ParseFile2.default) {
+ filesSaved = filesSaved.then(function () {
+ return el.save();
+ });
+ } else if (el instanceof ParseObject) {
+ pending.push(el);
+ }
+ });
+
+ return filesSaved.then(function () {
+ var objectError = null;
+ return _ParsePromise2.default._continueWhile(function () {
+ return pending.length > 0;
+ }, function () {
+ var batch = [];
+ var nextPending = [];
+ pending.forEach(function (el) {
+ if (batch.length < 20 && (0, _canBeSerialized2.default)(el)) {
+ batch.push(el);
+ } else {
+ nextPending.push(el);
+ }
+ });
+ pending = nextPending;
+ if (batch.length < 1) {
+ return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Tried to save a batch with a cycle.'));
+ }
+
+ // Queue up tasks for each object in the batch.
+ // When every task is ready, the API request will execute
+ var batchReturned = new _ParsePromise2.default();
+ var batchReady = [];
+ var batchTasks = [];
+ batch.forEach(function (obj, index) {
+ var ready = new _ParsePromise2.default();
+ batchReady.push(ready);
+
+ stateController.pushPendingState(obj._getStateIdentifier());
+ batchTasks.push(stateController.enqueueTask(obj._getStateIdentifier(), function () {
+ ready.resolve();
+ return batchReturned.then(function (responses, status) {
+ if (responses[index].hasOwnProperty('success')) {
+ obj._handleSaveResponse(responses[index].success, status);
+ } else {
+ if (!objectError && responses[index].hasOwnProperty('error')) {
+ var serverError = responses[index].error;
+ objectError = new _ParseError2.default(serverError.code, serverError.error);
+ // Cancel the rest of the save
+ pending = [];
+ }
+ obj._handleSaveError();
+ }
+ });
+ }));
+ });
+
+ _ParsePromise2.default.when(batchReady).then(function () {
+ // Kick off the batch request
+ return RESTController.request('POST', 'batch', {
+ requests: batch.map(function (obj) {
+ var params = obj._getSaveParams();
+ params.path = getServerUrlPath() + params.path;
+ return params;
+ })
+ }, options);
+ }).then(function (response, status, xhr) {
+ batchReturned.resolve(response, status);
+ });
+
+ return _ParsePromise2.default.when(batchTasks);
+ }).then(function () {
+ if (objectError) {
+ return _ParsePromise2.default.error(objectError);
+ }
+ return _ParsePromise2.default.as(target);
+ });
+ });
+ } else if (target instanceof ParseObject) {
+ // copying target lets Flow guarantee the pointer isn't modified elsewhere
+ var targetCopy = target;
+ var task = function () {
+ var params = targetCopy._getSaveParams();
+ return RESTController.request(params.method, params.path, params.body, options).then(function (response, status) {
+ targetCopy._handleSaveResponse(response, status);
+ }, function (error) {
+ targetCopy._handleSaveError();
+ return _ParsePromise2.default.error(error);
+ });
+ };
+
+ stateController.pushPendingState(target._getStateIdentifier());
+ return stateController.enqueueTask(target._getStateIdentifier(), task).then(function () {
+ return target;
+ }, function (error) {
+ return _ParsePromise2.default.error(error);
+ });
+ }
+ return _ParsePromise2.default.as();
+ }
+};
+
+_CoreManager2.default.setObjectController(DefaultController);
\ No newline at end of file
diff --git a/lib/node/ParseOp.js b/lib/node/ParseOp.js
new file mode 100644
index 000000000..1eba19303
--- /dev/null
+++ b/lib/node/ParseOp.js
@@ -0,0 +1,579 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.RelationOp = exports.RemoveOp = exports.AddUniqueOp = exports.AddOp = exports.IncrementOp = exports.UnsetOp = exports.SetOp = exports.Op = undefined;
+
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+exports.opFromJSON = opFromJSON;
+
+var _arrayContainsObject = require('./arrayContainsObject');
+
+var _arrayContainsObject2 = _interopRequireDefault(_arrayContainsObject);
+
+var _decode = require('./decode');
+
+var _decode2 = _interopRequireDefault(_decode);
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _ParseObject = require('./ParseObject');
+
+var _ParseObject2 = _interopRequireDefault(_ParseObject);
+
+var _ParseRelation = require('./ParseRelation');
+
+var _ParseRelation2 = _interopRequireDefault(_ParseRelation);
+
+var _unique = require('./unique');
+
+var _unique2 = _interopRequireDefault(_unique);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function opFromJSON(json) {
+ if (!json || !json.__op) {
+ return null;
+ }
+ switch (json.__op) {
+ case 'Delete':
+ return new UnsetOp();
+ case 'Increment':
+ return new IncrementOp(json.amount);
+ case 'Add':
+ return new AddOp((0, _decode2.default)(json.objects));
+ case 'AddUnique':
+ return new AddUniqueOp((0, _decode2.default)(json.objects));
+ case 'Remove':
+ return new RemoveOp((0, _decode2.default)(json.objects));
+ case 'AddRelation':
+ var toAdd = (0, _decode2.default)(json.objects);
+ if (!Array.isArray(toAdd)) {
+ return new RelationOp([], []);
+ }
+ return new RelationOp(toAdd, []);
+ case 'RemoveRelation':
+ var toRemove = (0, _decode2.default)(json.objects);
+ if (!Array.isArray(toRemove)) {
+ return new RelationOp([], []);
+ }
+ return new RelationOp([], toRemove);
+ case 'Batch':
+ var toAdd = [];
+ var toRemove = [];
+ for (var i = 0; i < json.ops.length; i++) {
+ if (json.ops[i].__op === 'AddRelation') {
+ toAdd = toAdd.concat((0, _decode2.default)(json.ops[i].objects));
+ } else if (json.ops[i].__op === 'RemoveRelation') {
+ toRemove = toRemove.concat((0, _decode2.default)(json.ops[i].objects));
+ }
+ }
+ return new RelationOp(toAdd, toRemove);
+ }
+ return null;
+}
+
+var Op = exports.Op = function () {
+ function Op() {
+ (0, _classCallCheck3.default)(this, Op);
+ }
+
+ (0, _createClass3.default)(Op, [{
+ key: 'applyTo',
+
+ // Empty parent class
+ value: function (value) {}
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {}
+ }, {
+ key: 'toJSON',
+ value: function () {}
+ }]);
+ return Op;
+}();
+
+var SetOp = exports.SetOp = function (_Op) {
+ (0, _inherits3.default)(SetOp, _Op);
+
+ function SetOp(value) {
+ (0, _classCallCheck3.default)(this, SetOp);
+
+ var _this = (0, _possibleConstructorReturn3.default)(this, (SetOp.__proto__ || (0, _getPrototypeOf2.default)(SetOp)).call(this));
+
+ _this._value = value;
+ return _this;
+ }
+
+ (0, _createClass3.default)(SetOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ return this._value;
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ return new SetOp(this._value);
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return (0, _encode2.default)(this._value, false, true);
+ }
+ }]);
+ return SetOp;
+}(Op);
+
+var UnsetOp = exports.UnsetOp = function (_Op2) {
+ (0, _inherits3.default)(UnsetOp, _Op2);
+
+ function UnsetOp() {
+ (0, _classCallCheck3.default)(this, UnsetOp);
+ return (0, _possibleConstructorReturn3.default)(this, (UnsetOp.__proto__ || (0, _getPrototypeOf2.default)(UnsetOp)).apply(this, arguments));
+ }
+
+ (0, _createClass3.default)(UnsetOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ return undefined;
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ return new UnsetOp();
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'Delete' };
+ }
+ }]);
+ return UnsetOp;
+}(Op);
+
+var IncrementOp = exports.IncrementOp = function (_Op3) {
+ (0, _inherits3.default)(IncrementOp, _Op3);
+
+ function IncrementOp(amount) {
+ (0, _classCallCheck3.default)(this, IncrementOp);
+
+ var _this3 = (0, _possibleConstructorReturn3.default)(this, (IncrementOp.__proto__ || (0, _getPrototypeOf2.default)(IncrementOp)).call(this));
+
+ if (typeof amount !== 'number') {
+ throw new TypeError('Increment Op must be initialized with a numeric amount.');
+ }
+ _this3._amount = amount;
+ return _this3;
+ }
+
+ (0, _createClass3.default)(IncrementOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ if (typeof value === 'undefined') {
+ return this._amount;
+ }
+ if (typeof value !== 'number') {
+ throw new TypeError('Cannot increment a non-numeric value.');
+ }
+ return this._amount + value;
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new SetOp(this._amount);
+ }
+ if (previous instanceof IncrementOp) {
+ return new IncrementOp(this.applyTo(previous._amount));
+ }
+ throw new Error('Cannot merge Increment Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'Increment', amount: this._amount };
+ }
+ }]);
+ return IncrementOp;
+}(Op);
+
+var AddOp = exports.AddOp = function (_Op4) {
+ (0, _inherits3.default)(AddOp, _Op4);
+
+ function AddOp(value) {
+ (0, _classCallCheck3.default)(this, AddOp);
+
+ var _this4 = (0, _possibleConstructorReturn3.default)(this, (AddOp.__proto__ || (0, _getPrototypeOf2.default)(AddOp)).call(this));
+
+ _this4._value = Array.isArray(value) ? value : [value];
+ return _this4;
+ }
+
+ (0, _createClass3.default)(AddOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ if (value == null) {
+ return this._value;
+ }
+ if (Array.isArray(value)) {
+ return value.concat(this._value);
+ }
+ throw new Error('Cannot add elements to a non-array value');
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new SetOp(this._value);
+ }
+ if (previous instanceof AddOp) {
+ return new AddOp(this.applyTo(previous._value));
+ }
+ throw new Error('Cannot merge Add Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'Add', objects: (0, _encode2.default)(this._value, false, true) };
+ }
+ }]);
+ return AddOp;
+}(Op);
+
+var AddUniqueOp = exports.AddUniqueOp = function (_Op5) {
+ (0, _inherits3.default)(AddUniqueOp, _Op5);
+
+ function AddUniqueOp(value) {
+ (0, _classCallCheck3.default)(this, AddUniqueOp);
+
+ var _this5 = (0, _possibleConstructorReturn3.default)(this, (AddUniqueOp.__proto__ || (0, _getPrototypeOf2.default)(AddUniqueOp)).call(this));
+
+ _this5._value = (0, _unique2.default)(Array.isArray(value) ? value : [value]);
+ return _this5;
+ }
+
+ (0, _createClass3.default)(AddUniqueOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ if (value == null) {
+ return this._value || [];
+ }
+ if (Array.isArray(value)) {
+ // copying value lets Flow guarantee the pointer isn't modified elsewhere
+ var valueCopy = value;
+ var toAdd = [];
+ this._value.forEach(function (v) {
+ if (v instanceof _ParseObject2.default) {
+ if (!(0, _arrayContainsObject2.default)(valueCopy, v)) {
+ toAdd.push(v);
+ }
+ } else {
+ if (valueCopy.indexOf(v) < 0) {
+ toAdd.push(v);
+ }
+ }
+ });
+ return value.concat(toAdd);
+ }
+ throw new Error('Cannot add elements to a non-array value');
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new SetOp(this._value);
+ }
+ if (previous instanceof AddUniqueOp) {
+ return new AddUniqueOp(this.applyTo(previous._value));
+ }
+ throw new Error('Cannot merge AddUnique Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'AddUnique', objects: (0, _encode2.default)(this._value, false, true) };
+ }
+ }]);
+ return AddUniqueOp;
+}(Op);
+
+var RemoveOp = exports.RemoveOp = function (_Op6) {
+ (0, _inherits3.default)(RemoveOp, _Op6);
+
+ function RemoveOp(value) {
+ (0, _classCallCheck3.default)(this, RemoveOp);
+
+ var _this6 = (0, _possibleConstructorReturn3.default)(this, (RemoveOp.__proto__ || (0, _getPrototypeOf2.default)(RemoveOp)).call(this));
+
+ _this6._value = (0, _unique2.default)(Array.isArray(value) ? value : [value]);
+ return _this6;
+ }
+
+ (0, _createClass3.default)(RemoveOp, [{
+ key: 'applyTo',
+ value: function (value) {
+ if (value == null) {
+ return [];
+ }
+ if (Array.isArray(value)) {
+ var i = value.indexOf(this._value);
+ var removed = value.concat([]);
+ for (var i = 0; i < this._value.length; i++) {
+ var index = removed.indexOf(this._value[i]);
+ while (index > -1) {
+ removed.splice(index, 1);
+ index = removed.indexOf(this._value[i]);
+ }
+ if (this._value[i] instanceof _ParseObject2.default && this._value[i].id) {
+ for (var j = 0; j < removed.length; j++) {
+ if (removed[j] instanceof _ParseObject2.default && this._value[i].id === removed[j].id) {
+ removed.splice(j, 1);
+ j--;
+ }
+ }
+ }
+ }
+ return removed;
+ }
+ throw new Error('Cannot remove elements from a non-array value');
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new UnsetOp();
+ }
+ if (previous instanceof RemoveOp) {
+ var uniques = previous._value.concat([]);
+ for (var i = 0; i < this._value.length; i++) {
+ if (this._value[i] instanceof _ParseObject2.default) {
+ if (!(0, _arrayContainsObject2.default)(uniques, this._value[i])) {
+ uniques.push(this._value[i]);
+ }
+ } else {
+ if (uniques.indexOf(this._value[i]) < 0) {
+ uniques.push(this._value[i]);
+ }
+ }
+ }
+ return new RemoveOp(uniques);
+ }
+ throw new Error('Cannot merge Remove Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ return { __op: 'Remove', objects: (0, _encode2.default)(this._value, false, true) };
+ }
+ }]);
+ return RemoveOp;
+}(Op);
+
+var RelationOp = exports.RelationOp = function (_Op7) {
+ (0, _inherits3.default)(RelationOp, _Op7);
+
+ function RelationOp(adds, removes) {
+ (0, _classCallCheck3.default)(this, RelationOp);
+
+ var _this7 = (0, _possibleConstructorReturn3.default)(this, (RelationOp.__proto__ || (0, _getPrototypeOf2.default)(RelationOp)).call(this));
+
+ _this7._targetClassName = null;
+
+ if (Array.isArray(adds)) {
+ _this7.relationsToAdd = (0, _unique2.default)(adds.map(_this7._extractId, _this7));
+ }
+
+ if (Array.isArray(removes)) {
+ _this7.relationsToRemove = (0, _unique2.default)(removes.map(_this7._extractId, _this7));
+ }
+ return _this7;
+ }
+
+ (0, _createClass3.default)(RelationOp, [{
+ key: '_extractId',
+ value: function (obj) {
+ if (typeof obj === 'string') {
+ return obj;
+ }
+ if (!obj.id) {
+ throw new Error('You cannot add or remove an unsaved Parse Object from a relation');
+ }
+ if (!this._targetClassName) {
+ this._targetClassName = obj.className;
+ }
+ if (this._targetClassName !== obj.className) {
+ throw new Error('Tried to create a Relation with 2 different object types: ' + this._targetClassName + ' and ' + obj.className + '.');
+ }
+ return obj.id;
+ }
+ }, {
+ key: 'applyTo',
+ value: function (value, object, key) {
+ if (!value) {
+ if (!object || !key) {
+ throw new Error('Cannot apply a RelationOp without either a previous value, or an object and a key');
+ }
+ var parent = new _ParseObject2.default(object.className);
+ if (object.id && object.id.indexOf('local') === 0) {
+ parent._localId = object.id;
+ } else if (object.id) {
+ parent.id = object.id;
+ }
+ var relation = new _ParseRelation2.default(parent, key);
+ relation.targetClassName = this._targetClassName;
+ return relation;
+ }
+ if (value instanceof _ParseRelation2.default) {
+ if (this._targetClassName) {
+ if (value.targetClassName) {
+ if (this._targetClassName !== value.targetClassName) {
+ throw new Error('Related object must be a ' + value.targetClassName + ', but a ' + this._targetClassName + ' was passed in.');
+ }
+ } else {
+ value.targetClassName = this._targetClassName;
+ }
+ }
+ return value;
+ } else {
+ throw new Error('Relation cannot be applied to a non-relation field');
+ }
+ }
+ }, {
+ key: 'mergeWith',
+ value: function (previous) {
+ if (!previous) {
+ return this;
+ } else if (previous instanceof UnsetOp) {
+ throw new Error('You cannot modify a relation after deleting it.');
+ } else if (previous instanceof RelationOp) {
+ if (previous._targetClassName && previous._targetClassName !== this._targetClassName) {
+ throw new Error('Related object must be of class ' + previous._targetClassName + ', but ' + (this._targetClassName || 'null') + ' was passed in.');
+ }
+ var newAdd = previous.relationsToAdd.concat([]);
+ this.relationsToRemove.forEach(function (r) {
+ var index = newAdd.indexOf(r);
+ if (index > -1) {
+ newAdd.splice(index, 1);
+ }
+ });
+ this.relationsToAdd.forEach(function (r) {
+ var index = newAdd.indexOf(r);
+ if (index < 0) {
+ newAdd.push(r);
+ }
+ });
+
+ var newRemove = previous.relationsToRemove.concat([]);
+ this.relationsToAdd.forEach(function (r) {
+ var index = newRemove.indexOf(r);
+ if (index > -1) {
+ newRemove.splice(index, 1);
+ }
+ });
+ this.relationsToRemove.forEach(function (r) {
+ var index = newRemove.indexOf(r);
+ if (index < 0) {
+ newRemove.push(r);
+ }
+ });
+
+ var newRelation = new RelationOp(newAdd, newRemove);
+ newRelation._targetClassName = this._targetClassName;
+ return newRelation;
+ }
+ throw new Error('Cannot merge Relation Op with the previous Op');
+ }
+ }, {
+ key: 'toJSON',
+ value: function () {
+ var _this8 = this;
+
+ var idToPointer = function (id) {
+ return {
+ __type: 'Pointer',
+ className: _this8._targetClassName,
+ objectId: id
+ };
+ };
+
+ var adds = null;
+ var removes = null;
+ var pointers = null;
+
+ if (this.relationsToAdd.length > 0) {
+ pointers = this.relationsToAdd.map(idToPointer);
+ adds = { __op: 'AddRelation', objects: pointers };
+ }
+ if (this.relationsToRemove.length > 0) {
+ pointers = this.relationsToRemove.map(idToPointer);
+ removes = { __op: 'RemoveRelation', objects: pointers };
+ }
+
+ if (adds && removes) {
+ return { __op: 'Batch', ops: [adds, removes] };
+ }
+
+ return adds || removes || {};
+ }
+ }]);
+ return RelationOp;
+}(Op);
\ No newline at end of file
diff --git a/lib/node/ParsePromise.js b/lib/node/ParsePromise.js
new file mode 100644
index 000000000..a79b4afee
--- /dev/null
+++ b/lib/node/ParsePromise.js
@@ -0,0 +1,740 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _getIterator2 = require('babel-runtime/core-js/get-iterator');
+
+var _getIterator3 = _interopRequireDefault(_getIterator2);
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+var _isPromisesAPlusCompliant = true;
+
+/**
+ * A Promise is returned by async methods as a hook to provide callbacks to be
+ * called when the async task is fulfilled.
+ *
+ * Typical usage would be like:
+ * query.find().then(function(results) {
+ * results[0].set("foo", "bar");
+ * return results[0].saveAsync();
+ * }).then(function(result) {
+ * console.log("Updated " + result.id);
+ * });
+ *
+ *
+ * @class Parse.Promise
+ * @constructor
+ */
+
+var ParsePromise = function () {
+ function ParsePromise(executor) {
+ (0, _classCallCheck3.default)(this, ParsePromise);
+
+ this._resolved = false;
+ this._rejected = false;
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+
+ if (typeof executor === 'function') {
+ executor(this.resolve.bind(this), this.reject.bind(this));
+ }
+ }
+
+ /**
+ * Marks this promise as fulfilled, firing any callbacks waiting on it.
+ * @method resolve
+ * @param {Object} result the result to pass to the callbacks.
+ */
+
+ (0, _createClass3.default)(ParsePromise, [{
+ key: 'resolve',
+ value: function () {
+ if (this._resolved || this._rejected) {
+ throw new Error('A promise was resolved even though it had already been ' + (this._resolved ? 'resolved' : 'rejected') + '.');
+ }
+ this._resolved = true;
+
+ for (var _len = arguments.length, results = Array(_len), _key = 0; _key < _len; _key++) {
+ results[_key] = arguments[_key];
+ }
+
+ this._result = results;
+ for (var i = 0; i < this._resolvedCallbacks.length; i++) {
+ this._resolvedCallbacks[i].apply(this, results);
+ }
+
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+ }
+
+ /**
+ * Marks this promise as fulfilled, firing any callbacks waiting on it.
+ * @method reject
+ * @param {Object} error the error to pass to the callbacks.
+ */
+
+ }, {
+ key: 'reject',
+ value: function (error) {
+ if (this._resolved || this._rejected) {
+ throw new Error('A promise was rejected even though it had already been ' + (this._resolved ? 'resolved' : 'rejected') + '.');
+ }
+ this._rejected = true;
+ this._error = error;
+ for (var i = 0; i < this._rejectedCallbacks.length; i++) {
+ this._rejectedCallbacks[i](error);
+ }
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+ }
+
+ /**
+ * Adds callbacks to be called when this promise is fulfilled. Returns a new
+ * Promise that will be fulfilled when the callback is complete. It allows
+ * chaining. If the callback itself returns a Promise, then the one returned
+ * by "then" will not be fulfilled until that one returned by the callback
+ * is fulfilled.
+ * @method then
+ * @param {Function} resolvedCallback Function that is called when this
+ * Promise is resolved. Once the callback is complete, then the Promise
+ * returned by "then" will also be fulfilled.
+ * @param {Function} rejectedCallback Function that is called when this
+ * Promise is rejected with an error. Once the callback is complete, then
+ * the promise returned by "then" with be resolved successfully. If
+ * rejectedCallback is null, or it returns a rejected Promise, then the
+ * Promise returned by "then" will be rejected with that error.
+ * @return {Parse.Promise} A new Promise that will be fulfilled after this
+ * Promise is fulfilled and either callback has completed. If the callback
+ * returned a Promise, then this Promise will not be fulfilled until that
+ * one is.
+ */
+
+ }, {
+ key: 'then',
+ value: function (resolvedCallback, rejectedCallback) {
+ var _this = this;
+
+ var promise = new ParsePromise();
+
+ var wrappedResolvedCallback = function () {
+ for (var _len2 = arguments.length, results = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ results[_key2] = arguments[_key2];
+ }
+
+ if (typeof resolvedCallback === 'function') {
+ if (_isPromisesAPlusCompliant) {
+ try {
+ results = [resolvedCallback.apply(this, results)];
+ } catch (e) {
+ results = [ParsePromise.error(e)];
+ }
+ } else {
+ results = [resolvedCallback.apply(this, results)];
+ }
+ }
+ if (results.length === 1 && ParsePromise.is(results[0])) {
+ results[0].then(function () {
+ promise.resolve.apply(promise, arguments);
+ }, function (error) {
+ promise.reject(error);
+ });
+ } else {
+ promise.resolve.apply(promise, results);
+ }
+ };
+
+ var wrappedRejectedCallback = function (error) {
+ var result = [];
+ if (typeof rejectedCallback === 'function') {
+ if (_isPromisesAPlusCompliant) {
+ try {
+ result = [rejectedCallback(error)];
+ } catch (e) {
+ result = [ParsePromise.error(e)];
+ }
+ } else {
+ result = [rejectedCallback(error)];
+ }
+ if (result.length === 1 && ParsePromise.is(result[0])) {
+ result[0].then(function () {
+ promise.resolve.apply(promise, arguments);
+ }, function (error) {
+ promise.reject(error);
+ });
+ } else {
+ if (_isPromisesAPlusCompliant) {
+ promise.resolve.apply(promise, result);
+ } else {
+ promise.reject(result[0]);
+ }
+ }
+ } else {
+ promise.reject(error);
+ }
+ };
+
+ var runLater = function (fn) {
+ fn.call();
+ };
+ if (_isPromisesAPlusCompliant) {
+ if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
+ runLater = function (fn) {
+ process.nextTick(fn);
+ };
+ } else if (typeof setTimeout === 'function') {
+ runLater = function (fn) {
+ setTimeout(fn, 0);
+ };
+ }
+ }
+
+ if (this._resolved) {
+ runLater(function () {
+ wrappedResolvedCallback.apply(_this, _this._result);
+ });
+ } else if (this._rejected) {
+ runLater(function () {
+ wrappedRejectedCallback(_this._error);
+ });
+ } else {
+ this._resolvedCallbacks.push(wrappedResolvedCallback);
+ this._rejectedCallbacks.push(wrappedRejectedCallback);
+ }
+
+ return promise;
+ }
+
+ /**
+ * Add handlers to be called when the promise
+ * is either resolved or rejected
+ * @method always
+ */
+
+ }, {
+ key: 'always',
+ value: function (callback) {
+ return this.then(callback, callback);
+ }
+
+ /**
+ * Add handlers to be called when the Promise object is resolved
+ * @method done
+ */
+
+ }, {
+ key: 'done',
+ value: function (callback) {
+ return this.then(callback);
+ }
+
+ /**
+ * Add handlers to be called when the Promise object is rejected
+ * Alias for catch().
+ * @method fail
+ */
+
+ }, {
+ key: 'fail',
+ value: function (callback) {
+ return this.then(null, callback);
+ }
+
+ /**
+ * Add handlers to be called when the Promise object is rejected
+ * @method catch
+ */
+
+ }, {
+ key: 'catch',
+ value: function (callback) {
+ return this.then(null, callback);
+ }
+
+ /**
+ * Run the given callbacks after this promise is fulfilled.
+ * @method _thenRunCallbacks
+ * @param optionsOrCallback {} A Backbone-style options callback, or a
+ * callback function. If this is an options object and contains a "model"
+ * attributes, that will be passed to error callbacks as the first argument.
+ * @param model {} If truthy, this will be passed as the first result of
+ * error callbacks. This is for Backbone-compatability.
+ * @return {Parse.Promise} A promise that will be resolved after the
+ * callbacks are run, with the same result as this.
+ */
+
+ }, {
+ key: '_thenRunCallbacks',
+ value: function (optionsOrCallback, model) {
+ var options = {};
+ if (typeof optionsOrCallback === 'function') {
+ options.success = function (result) {
+ optionsOrCallback(result, null);
+ };
+ options.error = function (error) {
+ optionsOrCallback(null, error);
+ };
+ } else if ((typeof optionsOrCallback === 'undefined' ? 'undefined' : (0, _typeof3.default)(optionsOrCallback)) === 'object') {
+ if (typeof optionsOrCallback.success === 'function') {
+ options.success = optionsOrCallback.success;
+ }
+ if (typeof optionsOrCallback.error === 'function') {
+ options.error = optionsOrCallback.error;
+ }
+ }
+
+ return this.then(function () {
+ for (var _len3 = arguments.length, results = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
+ results[_key3] = arguments[_key3];
+ }
+
+ if (options.success) {
+ options.success.apply(this, results);
+ }
+ return ParsePromise.as.apply(ParsePromise, arguments);
+ }, function (error) {
+ if (options.error) {
+ if (typeof model !== 'undefined') {
+ options.error(model, error);
+ } else {
+ options.error(error);
+ }
+ }
+ // By explicitly returning a rejected Promise, this will work with
+ // either jQuery or Promises/A+ semantics.
+ return ParsePromise.error(error);
+ });
+ }
+
+ /**
+ * Adds a callback function that should be called regardless of whether
+ * this promise failed or succeeded. The callback will be given either the
+ * array of results for its first argument, or the error as its second,
+ * depending on whether this Promise was rejected or resolved. Returns a
+ * new Promise, like "then" would.
+ * @method _continueWith
+ * @param {Function} continuation the callback.
+ */
+
+ }, {
+ key: '_continueWith',
+ value: function (continuation) {
+ return this.then(function () {
+ for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
+ args[_key4] = arguments[_key4];
+ }
+
+ return continuation(args, null);
+ }, function (error) {
+ return continuation(null, error);
+ });
+ }
+
+ /**
+ * Returns true iff the given object fulfils the Promise interface.
+ * @method is
+ * @param {Object} promise The object to test
+ * @static
+ * @return {Boolean}
+ */
+
+ }], [{
+ key: 'is',
+ value: function (promise) {
+ return promise != null && typeof promise.then === 'function';
+ }
+
+ /**
+ * Returns a new promise that is resolved with a given value.
+ * @method as
+ * @param value The value to resolve the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'as',
+ value: function () {
+ var promise = new ParsePromise();
+
+ for (var _len5 = arguments.length, values = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
+ values[_key5] = arguments[_key5];
+ }
+
+ promise.resolve.apply(promise, values);
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is resolved with a given value.
+ * If that value is a thenable Promise (has a .then() prototype
+ * method), the new promise will be chained to the end of the
+ * value.
+ * @method resolve
+ * @param value The value to resolve the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'resolve',
+ value: function (value) {
+ return new ParsePromise(function (resolve, reject) {
+ if (ParsePromise.is(value)) {
+ value.then(resolve, reject);
+ } else {
+ resolve(value);
+ }
+ });
+ }
+
+ /**
+ * Returns a new promise that is rejected with a given error.
+ * @method error
+ * @param error The error to reject the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'error',
+ value: function () {
+ var promise = new ParsePromise();
+
+ for (var _len6 = arguments.length, errors = Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
+ errors[_key6] = arguments[_key6];
+ }
+
+ promise.reject.apply(promise, errors);
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is rejected with a given error.
+ * This is an alias for Parse.Promise.error, for compliance with
+ * the ES6 implementation.
+ * @method reject
+ * @param error The error to reject the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'reject',
+ value: function () {
+ for (var _len7 = arguments.length, errors = Array(_len7), _key7 = 0; _key7 < _len7; _key7++) {
+ errors[_key7] = arguments[_key7];
+ }
+
+ return ParsePromise.error.apply(null, errors);
+ }
+
+ /**
+ * Returns a new promise that is fulfilled when all of the input promises
+ * are resolved. If any promise in the list fails, then the returned promise
+ * will be rejected with an array containing the error from each promise.
+ * If they all succeed, then the returned promise will succeed, with the
+ * results being the results of all the input
+ * promises. For example:
+ * var p1 = Parse.Promise.as(1);
+ * var p2 = Parse.Promise.as(2);
+ * var p3 = Parse.Promise.as(3);
+ *
+ * Parse.Promise.when(p1, p2, p3).then(function(r1, r2, r3) {
+ * console.log(r1); // prints 1
+ * console.log(r2); // prints 2
+ * console.log(r3); // prints 3
+ * });
+ *
+ * The input promises can also be specified as an array:
+ * var promises = [p1, p2, p3];
+ * Parse.Promise.when(promises).then(function(results) {
+ * console.log(results); // prints [1,2,3]
+ * });
+ *
+ * @method when
+ * @param {Array} promises a list of promises to wait for.
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'when',
+ value: function (promises) {
+ var objects;
+ var arrayArgument = Array.isArray(promises);
+ if (arrayArgument) {
+ objects = promises;
+ } else {
+ objects = arguments;
+ }
+
+ var total = objects.length;
+ var hadError = false;
+ var results = [];
+ var returnValue = arrayArgument ? [results] : results;
+ var errors = [];
+ results.length = objects.length;
+ errors.length = objects.length;
+
+ if (total === 0) {
+ return ParsePromise.as.apply(this, returnValue);
+ }
+
+ var promise = new ParsePromise();
+
+ var resolveOne = function () {
+ total--;
+ if (total <= 0) {
+ if (hadError) {
+ promise.reject(errors);
+ } else {
+ promise.resolve.apply(promise, returnValue);
+ }
+ }
+ };
+
+ var chain = function (object, index) {
+ if (ParsePromise.is(object)) {
+ object.then(function (result) {
+ results[index] = result;
+ resolveOne();
+ }, function (error) {
+ errors[index] = error;
+ hadError = true;
+ resolveOne();
+ });
+ } else {
+ results[i] = object;
+ resolveOne();
+ }
+ };
+ for (var i = 0; i < objects.length; i++) {
+ chain(objects[i], i);
+ }
+
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is fulfilled when all of the promises in the
+ * iterable argument are resolved. If any promise in the list fails, then
+ * the returned promise will be immediately rejected with the reason that
+ * single promise rejected. If they all succeed, then the returned promise
+ * will succeed, with the results being the results of all the input
+ * promises. If the iterable provided is empty, the returned promise will
+ * be immediately resolved.
+ *
+ * For example:
+ * var p1 = Parse.Promise.as(1);
+ * var p2 = Parse.Promise.as(2);
+ * var p3 = Parse.Promise.as(3);
+ *
+ * Parse.Promise.all([p1, p2, p3]).then(function([r1, r2, r3]) {
+ * console.log(r1); // prints 1
+ * console.log(r2); // prints 2
+ * console.log(r3); // prints 3
+ * });
+ *
+ * @method all
+ * @param {Iterable} promises an iterable of promises to wait for.
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'all',
+ value: function (promises) {
+ var total = 0;
+ var objects = [];
+
+ var _iteratorNormalCompletion = true;
+ var _didIteratorError = false;
+ var _iteratorError = undefined;
+
+ try {
+ for (var _iterator = (0, _getIterator3.default)(promises), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+ var p = _step.value;
+
+ objects[total++] = p;
+ }
+ } catch (err) {
+ _didIteratorError = true;
+ _iteratorError = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion && _iterator.return) {
+ _iterator.return();
+ }
+ } finally {
+ if (_didIteratorError) {
+ throw _iteratorError;
+ }
+ }
+ }
+
+ if (total === 0) {
+ return ParsePromise.as([]);
+ }
+
+ var hadError = false;
+ var promise = new ParsePromise();
+ var resolved = 0;
+ var results = [];
+ objects.forEach(function (object, i) {
+ if (ParsePromise.is(object)) {
+ object.then(function (result) {
+ if (hadError) {
+ return false;
+ }
+ results[i] = result;
+ resolved++;
+ if (resolved >= total) {
+ promise.resolve(results);
+ }
+ }, function (error) {
+ // Reject immediately
+ promise.reject(error);
+ hadError = true;
+ });
+ } else {
+ results[i] = object;
+ resolved++;
+ if (!hadError && resolved >= total) {
+ promise.resolve(results);
+ }
+ }
+ });
+
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is immediately fulfilled when any of the
+ * promises in the iterable argument are resolved or rejected. If the
+ * first promise to complete is resolved, the returned promise will be
+ * resolved with the same value. Likewise, if the first promise to
+ * complete is rejected, the returned promise will be rejected with the
+ * same reason.
+ *
+ * @method race
+ * @param {Iterable} promises an iterable of promises to wait for.
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+
+ }, {
+ key: 'race',
+ value: function (promises) {
+ var completed = false;
+ var promise = new ParsePromise();
+ var _iteratorNormalCompletion2 = true;
+ var _didIteratorError2 = false;
+ var _iteratorError2 = undefined;
+
+ try {
+ for (var _iterator2 = (0, _getIterator3.default)(promises), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+ var p = _step2.value;
+
+ if (ParsePromise.is(p)) {
+ p.then(function (result) {
+ if (completed) {
+ return;
+ }
+ completed = true;
+ promise.resolve(result);
+ }, function (error) {
+ if (completed) {
+ return;
+ }
+ completed = true;
+ promise.reject(error);
+ });
+ } else if (!completed) {
+ completed = true;
+ promise.resolve(p);
+ }
+ }
+ } catch (err) {
+ _didIteratorError2 = true;
+ _iteratorError2 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion2 && _iterator2.return) {
+ _iterator2.return();
+ }
+ } finally {
+ if (_didIteratorError2) {
+ throw _iteratorError2;
+ }
+ }
+ }
+
+ return promise;
+ }
+
+ /**
+ * Runs the given asyncFunction repeatedly, as long as the predicate
+ * function returns a truthy value. Stops repeating if asyncFunction returns
+ * a rejected promise.
+ * @method _continueWhile
+ * @param {Function} predicate should return false when ready to stop.
+ * @param {Function} asyncFunction should return a Promise.
+ * @static
+ */
+
+ }, {
+ key: '_continueWhile',
+ value: function (predicate, asyncFunction) {
+ if (predicate()) {
+ return asyncFunction().then(function () {
+ return ParsePromise._continueWhile(predicate, asyncFunction);
+ });
+ }
+ return ParsePromise.as();
+ }
+ }, {
+ key: 'isPromisesAPlusCompliant',
+ value: function () {
+ return _isPromisesAPlusCompliant;
+ }
+ }, {
+ key: 'enableAPlusCompliant',
+ value: function () {
+ _isPromisesAPlusCompliant = true;
+ }
+ }, {
+ key: 'disableAPlusCompliant',
+ value: function () {
+ _isPromisesAPlusCompliant = false;
+ }
+ }]);
+ return ParsePromise;
+}();
+
+exports.default = ParsePromise;
\ No newline at end of file
diff --git a/lib/node/ParseQuery.js b/lib/node/ParseQuery.js
new file mode 100644
index 000000000..664f5a737
--- /dev/null
+++ b/lib/node/ParseQuery.js
@@ -0,0 +1,1340 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _typeof2 = require('babel-runtime/helpers/typeof');
+
+var _typeof3 = _interopRequireDefault(_typeof2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _keys = require('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
+var _CoreManager = require('./CoreManager');
+
+var _CoreManager2 = _interopRequireDefault(_CoreManager);
+
+var _encode = require('./encode');
+
+var _encode2 = _interopRequireDefault(_encode);
+
+var _ParseError = require('./ParseError');
+
+var _ParseError2 = _interopRequireDefault(_ParseError);
+
+var _ParseGeoPoint = require('./ParseGeoPoint');
+
+var _ParseGeoPoint2 = _interopRequireDefault(_ParseGeoPoint);
+
+var _ParseObject = require('./ParseObject');
+
+var _ParseObject2 = _interopRequireDefault(_ParseObject);
+
+var _ParsePromise = require('./ParsePromise');
+
+var _ParsePromise2 = _interopRequireDefault(_ParsePromise);
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+/**
+ * Converts a string into a regex that matches it.
+ * Surrounding with \Q .. \E does this, we just need to escape any \E's in
+ * the text separately.
+ */
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+function quote(s) {
+ return '\\Q' + s.replace('\\E', '\\E\\\\E\\Q') + '\\E';
+}
+
+/**
+ * Handles pre-populating the result data of a query with select fields,
+ * making sure that the data object contains keys for all objects that have
+ * been requested with a select, so that our cached state updates correctly.
+ */
+function handleSelectResult(data, select) {
+ var serverDataMask = {};
+
+ select.forEach(function (field) {
+ var hasSubObjectSelect = field.indexOf(".") !== -1;
+ if (!hasSubObjectSelect && !data.hasOwnProperty(field)) {
+ // this field was selected, but is missing from the retrieved data
+ data[field] = undefined;
+ } else if (hasSubObjectSelect) {
+ // this field references a sub-object,
+ // so we need to walk down the path components
+ var pathComponents = field.split(".");
+ var obj = data;
+ var serverMask = serverDataMask;
+
+ pathComponents.forEach(function (component, index, arr) {
+ // add keys if the expected data is missing
+ if (!obj[component]) {
+ obj[component] = index == arr.length - 1 ? undefined : {};
+ }
+ obj = obj[component];
+
+ //add this path component to the server mask so we can fill it in later if needed
+ if (index < arr.length - 1) {
+ if (!serverMask[component]) {
+ serverMask[component] = {};
+ }
+ }
+ });
+ }
+ });
+
+ if ((0, _keys2.default)(serverDataMask).length > 0) {
+ var copyMissingDataWithMask = function copyMissingDataWithMask(src, dest, mask, copyThisLevel) {
+ //copy missing elements at this level
+ if (copyThisLevel) {
+ for (var key in src) {
+ if (src.hasOwnProperty(key) && !dest.hasOwnProperty(key)) {
+ dest[key] = src[key];
+ }
+ }
+ }
+ for (var key in mask) {
+ //traverse into objects as needed
+ copyMissingDataWithMask(src[key], dest[key], mask[key], true);
+ }
+ };
+
+ // When selecting from sub-objects, we don't want to blow away the missing
+ // information that we may have retrieved before. We've already added any
+ // missing selected keys to sub-objects, but we still need to add in the
+ // data for any previously retrieved sub-objects that were not selected.
+
+ var serverData = _CoreManager2.default.getObjectStateController().getServerData({ id: data.objectId, className: data.className });
+
+ copyMissingDataWithMask(serverData, data, serverDataMask, false);
+ }
+}
+
+/**
+ * Creates a new parse Parse.Query for the given Parse.Object subclass.
+ * @class Parse.Query
+ * @constructor
+ * @param {} objectClass An instance of a subclass of Parse.Object, or a Parse className string.
+ *
+ * Parse.Query defines a query that is used to fetch Parse.Objects. The
+ * most common use case is finding all objects that match a query through the
+ * find method. For example, this sample code fetches all objects
+ * of class MyClass. It calls a different function depending on
+ * whether the fetch succeeded or not.
+ *
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.find({
+ * success: function(results) {
+ * // results is an array of Parse.Object.
+ * },
+ *
+ * error: function(error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ *
+ * A Parse.Query can also be used to retrieve a single object whose id is
+ * known, through the get method. For example, this sample code fetches an
+ * object of class MyClass and id myId. It calls a
+ * different function depending on whether the fetch succeeded or not.
+ *
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.get(myId, {
+ * success: function(object) {
+ * // object is an instance of Parse.Object.
+ * },
+ *
+ * error: function(object, error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ *
+ * A Parse.Query can also be used to count the number of objects that match
+ * the query without retrieving all of those objects. For example, this
+ * sample code counts the number of objects of the class MyClass
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.count({
+ * success: function(number) {
+ * // There are number instances of MyClass.
+ * },
+ *
+ * error: function(error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ */
+
+var ParseQuery = function () {
+ function ParseQuery(objectClass) {
+ (0, _classCallCheck3.default)(this, ParseQuery);
+
+ if (typeof objectClass === 'string') {
+ if (objectClass === 'User' && _CoreManager2.default.get('PERFORM_USER_REWRITE')) {
+ this.className = '_User';
+ } else {
+ this.className = objectClass;
+ }
+ } else if (objectClass instanceof _ParseObject2.default) {
+ this.className = objectClass.className;
+ } else if (typeof objectClass === 'function') {
+ if (typeof objectClass.className === 'string') {
+ this.className = objectClass.className;
+ } else {
+ var obj = new objectClass();
+ this.className = obj.className;
+ }
+ } else {
+ throw new TypeError('A ParseQuery must be constructed with a ParseObject or class name.');
+ }
+
+ this._where = {};
+ this._include = [];
+ this._limit = -1; // negative limit is not sent in the server request
+ this._skip = 0;
+ this._extraOptions = {};
+ }
+
+ /**
+ * Adds constraint that at least one of the passed in queries matches.
+ * @method _orQuery
+ * @param {Array} queries
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+
+ (0, _createClass3.default)(ParseQuery, [{
+ key: '_orQuery',
+ value: function (queries) {
+ var queryJSON = queries.map(function (q) {
+ return q.toJSON().where;
+ });
+
+ this._where.$or = queryJSON;
+ return this;
+ }
+
+ /**
+ * Helper for condition queries
+ */
+
+ }, {
+ key: '_addCondition',
+ value: function (key, condition, value) {
+ if (!this._where[key] || typeof this._where[key] === 'string') {
+ this._where[key] = {};
+ }
+ this._where[key][condition] = (0, _encode2.default)(value, false, true);
+ return this;
+ }
+
+ /**
+ * Converts string for regular expression at the beginning
+ */
+
+ }, {
+ key: '_regexStartWith',
+ value: function (string) {
+ return '^' + quote(string);
+ }
+
+ /**
+ * Returns a JSON representation of this query.
+ * @method toJSON
+ * @return {Object} The JSON representation of the query.
+ */
+
+ }, {
+ key: 'toJSON',
+ value: function () {
+ var params = {
+ where: this._where
+ };
+
+ if (this._include.length) {
+ params.include = this._include.join(',');
+ }
+ if (this._select) {
+ params.keys = this._select.join(',');
+ }
+ if (this._limit >= 0) {
+ params.limit = this._limit;
+ }
+ if (this._skip > 0) {
+ params.skip = this._skip;
+ }
+ if (this._order) {
+ params.order = this._order.join(',');
+ }
+ for (var key in this._extraOptions) {
+ params[key] = this._extraOptions[key];
+ }
+
+ return params;
+ }
+
+ /**
+ * Constructs a Parse.Object whose id is already known by fetching data from
+ * the server. Either options.success or options.error is called when the
+ * find completes.
+ *
+ * @method get
+ * @param {String} objectId The id of the object to be fetched.
+ * @param {Object} options A Backbone-style options object.
+ * Valid options are:var compoundQuery = Parse.Query.or(query1, query2, query3);+ * + * will create a compoundQuery that is an or of the query1, query2, and + * query3. + * @method or + * @param {...Parse.Query} var_args The list of queries to OR. + * @static + * @return {Parse.Query} The query that is the OR of the passed in queries. + */ + + }], [{ + key: 'or', + value: function () { + var className = null; + + for (var _len7 = arguments.length, queries = Array(_len7), _key7 = 0; _key7 < _len7; _key7++) { + queries[_key7] = arguments[_key7]; + } + + queries.forEach(function (q) { + if (!className) { + className = q.className; + } + + if (className !== q.className) { + throw new Error('All queries must be for the same class.'); + } + }); + + var query = new ParseQuery(className); + query._orQuery(queries); + return query; + } + }]); + return ParseQuery; +}(); + +exports.default = ParseQuery; + +var DefaultController = { + find: function (className, params, options) { + var RESTController = _CoreManager2.default.getRESTController(); + + return RESTController.request('GET', 'classes/' + className, params, options); + } +}; + +_CoreManager2.default.setQueryController(DefaultController); \ No newline at end of file diff --git a/lib/node/ParseRelation.js b/lib/node/ParseRelation.js new file mode 100644 index 000000000..fe9ea8769 --- /dev/null +++ b/lib/node/ParseRelation.js @@ -0,0 +1,182 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = require('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _ParseOp = require('./ParseOp'); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseQuery = require('./ParseQuery'); + +var _ParseQuery2 = _interopRequireDefault(_ParseQuery); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Creates a new Relation for the given parent object and key. This + * constructor should rarely be used directly, but rather created by + * Parse.Object.relation. + * @class Parse.Relation + * @constructor + * @param {Parse.Object} parent The parent of this relation. + * @param {String} key The key for this relation on the parent. + * + *
+ * A class that is used to access all of the children of a many-to-many + * relationship. Each instance of Parse.Relation is associated with a + * particular parent object and key. + *
+ */ +var ParseRelation = function () { + function ParseRelation(parent, key) { + (0, _classCallCheck3.default)(this, ParseRelation); + + this.parent = parent; + this.key = key; + this.targetClassName = null; + } + + /** + * Makes sure that this relation has the right parent and key. + */ + + (0, _createClass3.default)(ParseRelation, [{ + key: '_ensureParentAndKey', + value: function (parent, key) { + this.key = this.key || key; + if (this.key !== key) { + throw new Error('Internal Error. Relation retrieved from two different keys.'); + } + if (this.parent) { + if (this.parent.className !== parent.className) { + throw new Error('Internal Error. Relation retrieved from two different Objects.'); + } + if (this.parent.id) { + if (this.parent.id !== parent.id) { + throw new Error('Internal Error. Relation retrieved from two different Objects.'); + } + } else if (parent.id) { + this.parent = parent; + } + } else { + this.parent = parent; + } + } + + /** + * Adds a Parse.Object or an array of Parse.Objects to the relation. + * @method add + * @param {} objects The item or items to add. + */ + + }, { + key: 'add', + value: function (objects) { + if (!Array.isArray(objects)) { + objects = [objects]; + } + + var change = new _ParseOp.RelationOp(objects, []); + var parent = this.parent; + if (!parent) { + throw new Error('Cannot add to a Relation without a parent'); + } + parent.set(this.key, change); + this.targetClassName = change._targetClassName; + return parent; + } + + /** + * Removes a Parse.Object or an array of Parse.Objects from this relation. + * @method remove + * @param {} objects The item or items to remove. + */ + + }, { + key: 'remove', + value: function (objects) { + if (!Array.isArray(objects)) { + objects = [objects]; + } + + var change = new _ParseOp.RelationOp([], objects); + if (!this.parent) { + throw new Error('Cannot remove from a Relation without a parent'); + } + this.parent.set(this.key, change); + this.targetClassName = change._targetClassName; + } + + /** + * Returns a JSON version of the object suitable for saving to disk. + * @method toJSON + * @return {Object} + */ + + }, { + key: 'toJSON', + value: function () { + return { + __type: 'Relation', + className: this.targetClassName + }; + } + + /** + * Returns a Parse.Query that is limited to objects in this + * relation. + * @method query + * @return {Parse.Query} + */ + + }, { + key: 'query', + value: function () { + var query; + var parent = this.parent; + if (!parent) { + throw new Error('Cannot construct a query for a Relation without a parent'); + } + if (!this.targetClassName) { + query = new _ParseQuery2.default(parent.className); + query._extraOptions.redirectClassNameForKey = this.key; + } else { + query = new _ParseQuery2.default(this.targetClassName); + } + query._addCondition('$relatedTo', 'object', { + __type: 'Pointer', + className: parent.className, + objectId: parent.id + }); + query._addCondition('$relatedTo', 'key', this.key); + + return query; + } + }]); + return ParseRelation; +}(); /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +exports.default = ParseRelation; \ No newline at end of file diff --git a/lib/node/ParseRole.js b/lib/node/ParseRole.js new file mode 100644 index 000000000..567af7a54 --- /dev/null +++ b/lib/node/ParseRole.js @@ -0,0 +1,196 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = require('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +var _get2 = require('babel-runtime/helpers/get'); + +var _get3 = _interopRequireDefault(_get2); + +var _inherits2 = require('babel-runtime/helpers/inherits'); + +var _inherits3 = _interopRequireDefault(_inherits2); + +var _ParseACL = require('./ParseACL'); + +var _ParseACL2 = _interopRequireDefault(_ParseACL); + +var _ParseError = require('./ParseError'); + +var _ParseError2 = _interopRequireDefault(_ParseError); + +var _ParseObject2 = require('./ParseObject'); + +var _ParseObject3 = _interopRequireDefault(_ParseObject2); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Represents a Role on the Parse server. Roles represent groupings of + * Users for the purposes of granting permissions (e.g. specifying an ACL + * for an Object). Roles are specified by their sets of child users and + * child roles, all of which are granted any permissions that the parent + * role has. + * + *Roles must have a name (which cannot be changed after creation of the + * role), and must specify an ACL.
+ * @class Parse.Role + * @constructor + * @param {String} name The name of the Role to create. + * @param {Parse.ACL} acl The ACL for this role. Roles must have an ACL. + * A Parse.Role is a local representation of a role persisted to the Parse + * cloud. + */ +var ParseRole = function (_ParseObject) { + (0, _inherits3.default)(ParseRole, _ParseObject); + + function ParseRole(name, acl) { + (0, _classCallCheck3.default)(this, ParseRole); + + var _this = (0, _possibleConstructorReturn3.default)(this, (ParseRole.__proto__ || (0, _getPrototypeOf2.default)(ParseRole)).call(this, '_Role')); + + if (typeof name === 'string' && acl instanceof _ParseACL2.default) { + _this.setName(name); + _this.setACL(acl); + } + return _this; + } + + /** + * Gets the name of the role. You can alternatively call role.get("name") + * + * @method getName + * @return {String} the name of the role. + */ + + (0, _createClass3.default)(ParseRole, [{ + key: 'getName', + value: function () { + var name = this.get('name'); + if (name == null || typeof name === 'string') { + return name; + } + return ''; + } + + /** + * Sets the name for a role. This value must be set before the role has + * been saved to the server, and cannot be set once the role has been + * saved. + * + *+ * A role's name can only contain alphanumeric characters, _, -, and + * spaces. + *
+ * + *This is equivalent to calling role.set("name", name)
+ * + * @method setName + * @param {String} name The name of the role. + * @param {Object} options Standard options object with success and error + * callbacks. + */ + + }, { + key: 'setName', + value: function (name, options) { + return this.set('name', name, options); + } + + /** + * Gets the Parse.Relation for the Parse.Users that are direct + * children of this role. These users are granted any privileges that this + * role has been granted (e.g. read or write access through ACLs). You can + * add or remove users from the role through this relation. + * + *This is equivalent to calling role.relation("users")
+ * + * @method getUsers + * @return {Parse.Relation} the relation for the users belonging to this + * role. + */ + + }, { + key: 'getUsers', + value: function () { + return this.relation('users'); + } + + /** + * Gets the Parse.Relation for the Parse.Roles that are direct + * children of this role. These roles' users are granted any privileges that + * this role has been granted (e.g. read or write access through ACLs). You + * can add or remove child roles from this role through this relation. + * + *This is equivalent to calling role.relation("roles")
+ * + * @method getRoles + * @return {Parse.Relation} the relation for the roles belonging to this + * role. + */ + + }, { + key: 'getRoles', + value: function () { + return this.relation('roles'); + } + }, { + key: 'validate', + value: function (attrs, options) { + var isInvalid = (0, _get3.default)(ParseRole.prototype.__proto__ || (0, _getPrototypeOf2.default)(ParseRole.prototype), 'validate', this).call(this, attrs, options); + if (isInvalid) { + return isInvalid; + } + + if ('name' in attrs && attrs.name !== this.getName()) { + var newName = attrs.name; + if (this.id && this.id !== attrs.objectId) { + // Check to see if the objectId being set matches this.id + // This happens during a fetch -- the id is set before calling fetch + // Let the name be set in this case + return new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'A role\'s name can only be set before it has been saved.'); + } + if (typeof newName !== 'string') { + return new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'A role\'s name must be a String.'); + } + if (!/^[0-9a-zA-Z\-_ ]+$/.test(newName)) { + return new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'A role\'s name can be only contain alphanumeric characters, _, ' + '-, and spaces.'); + } + } + return false; + } + }]); + return ParseRole; +}(_ParseObject3.default); /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +exports.default = ParseRole; + +_ParseObject3.default.registerSubclass('_Role', ParseRole); \ No newline at end of file diff --git a/lib/node/ParseSession.js b/lib/node/ParseSession.js new file mode 100644 index 000000000..7fb75c80d --- /dev/null +++ b/lib/node/ParseSession.js @@ -0,0 +1,180 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = require('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +var _inherits2 = require('babel-runtime/helpers/inherits'); + +var _inherits3 = _interopRequireDefault(_inherits2); + +var _CoreManager = require('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +var _isRevocableSession = require('./isRevocableSession'); + +var _isRevocableSession2 = _interopRequireDefault(_isRevocableSession); + +var _ParseObject2 = require('./ParseObject'); + +var _ParseObject3 = _interopRequireDefault(_ParseObject2); + +var _ParsePromise = require('./ParsePromise'); + +var _ParsePromise2 = _interopRequireDefault(_ParsePromise); + +var _ParseUser = require('./ParseUser'); + +var _ParseUser2 = _interopRequireDefault(_ParseUser); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * @class Parse.Session + * @constructor + * + *A Parse.Session object is a local representation of a revocable session. + * This class is a subclass of a Parse.Object, and retains the same + * functionality of a Parse.Object.
+ */ +var ParseSession = function (_ParseObject) { + (0, _inherits3.default)(ParseSession, _ParseObject); + + function ParseSession(attributes) { + (0, _classCallCheck3.default)(this, ParseSession); + + var _this = (0, _possibleConstructorReturn3.default)(this, (ParseSession.__proto__ || (0, _getPrototypeOf2.default)(ParseSession)).call(this, '_Session')); + + if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') { + if (!_this.set(attributes || {})) { + throw new Error('Can\'t create an invalid Session'); + } + } + return _this; + } + + /** + * Returns the session token string. + * @method getSessionToken + * @return {String} + */ + + (0, _createClass3.default)(ParseSession, [{ + key: 'getSessionToken', + value: function () { + var token = this.get('sessionToken'); + if (typeof token === 'string') { + return token; + } + return ''; + } + }], [{ + key: 'readOnlyAttributes', + value: function () { + return ['createdWith', 'expiresAt', 'installationId', 'restricted', 'sessionToken', 'user']; + } + + /** + * Retrieves the Session object for the currently logged in session. + * @method current + * @static + * @return {Parse.Promise} A promise that is resolved with the Parse.Session + * object after it has been fetched. If there is no current user, the + * promise will be rejected. + */ + + }, { + key: 'current', + value: function (options) { + options = options || {}; + var controller = _CoreManager2.default.getSessionController(); + + var sessionOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + sessionOptions.useMasterKey = options.useMasterKey; + } + return _ParseUser2.default.currentAsync().then(function (user) { + if (!user) { + return _ParsePromise2.default.error('There is no current user.'); + } + user.getSessionToken(); + + sessionOptions.sessionToken = user.getSessionToken(); + return controller.getSession(sessionOptions); + }); + } + + /** + * Determines whether the current session token is revocable. + * This method is useful for migrating Express.js or Node.js web apps to + * use revocable sessions. If you are migrating an app that uses the Parse + * SDK in the browser only, please use Parse.User.enableRevocableSession() + * instead, so that sessions can be automatically upgraded. + * @method isCurrentSessionRevocable + * @static + * @return {Boolean} + */ + + }, { + key: 'isCurrentSessionRevocable', + value: function () { + var currentUser = _ParseUser2.default.current(); + if (currentUser) { + return (0, _isRevocableSession2.default)(currentUser.getSessionToken() || ''); + } + return false; + } + }]); + return ParseSession; +}(_ParseObject3.default); /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +exports.default = ParseSession; + +_ParseObject3.default.registerSubclass('_Session', ParseSession); + +var DefaultController = { + getSession: function (options) { + var RESTController = _CoreManager2.default.getRESTController(); + var session = new ParseSession(); + + return RESTController.request('GET', 'sessions/me', {}, options).then(function (sessionData) { + session._finishFetch(sessionData); + session._setExisted(true); + return session; + }); + } +}; + +_CoreManager2.default.setSessionController(DefaultController); \ No newline at end of file diff --git a/lib/node/ParseUser.js b/lib/node/ParseUser.js new file mode 100644 index 000000000..f18f42820 --- /dev/null +++ b/lib/node/ParseUser.js @@ -0,0 +1,1150 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _stringify = require('babel-runtime/core-js/json/stringify'); + +var _stringify2 = _interopRequireDefault(_stringify); + +var _defineProperty = require('babel-runtime/core-js/object/define-property'); + +var _defineProperty2 = _interopRequireDefault(_defineProperty); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = require('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +var _get2 = require('babel-runtime/helpers/get'); + +var _get3 = _interopRequireDefault(_get2); + +var _inherits2 = require('babel-runtime/helpers/inherits'); + +var _inherits3 = _interopRequireDefault(_inherits2); + +var _CoreManager = require('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +var _isRevocableSession = require('./isRevocableSession'); + +var _isRevocableSession2 = _interopRequireDefault(_isRevocableSession); + +var _ParseError = require('./ParseError'); + +var _ParseError2 = _interopRequireDefault(_ParseError); + +var _ParseObject2 = require('./ParseObject'); + +var _ParseObject3 = _interopRequireDefault(_ParseObject2); + +var _ParsePromise = require('./ParsePromise'); + +var _ParsePromise2 = _interopRequireDefault(_ParsePromise); + +var _ParseSession = require('./ParseSession'); + +var _ParseSession2 = _interopRequireDefault(_ParseSession); + +var _Storage = require('./Storage'); + +var _Storage2 = _interopRequireDefault(_Storage); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +var CURRENT_USER_KEY = 'currentUser'; /** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +var canUseCurrentUser = !_CoreManager2.default.get('IS_NODE'); +var currentUserCacheMatchesDisk = false; +var currentUserCache = null; + +var authProviders = {}; + +/** + * @class Parse.User + * @constructor + * + *A Parse.User object is a local representation of a user persisted to the + * Parse cloud. This class is a subclass of a Parse.Object, and retains the + * same functionality of a Parse.Object, but also extends it with various + * user specific methods, like authentication, signing up, and validation of + * uniqueness.
+ */ + +var ParseUser = function (_ParseObject) { + (0, _inherits3.default)(ParseUser, _ParseObject); + + function ParseUser(attributes) { + (0, _classCallCheck3.default)(this, ParseUser); + + var _this = (0, _possibleConstructorReturn3.default)(this, (ParseUser.__proto__ || (0, _getPrototypeOf2.default)(ParseUser)).call(this, '_User')); + + if (attributes && (typeof attributes === 'undefined' ? 'undefined' : (0, _typeof3.default)(attributes)) === 'object') { + if (!_this.set(attributes || {})) { + throw new Error('Can\'t create an invalid Parse User'); + } + } + return _this; + } + + /** + * Request a revocable session token to replace the older style of token. + * @method _upgradeToRevocableSession + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is resolved when the replacement + * token has been fetched. + */ + + (0, _createClass3.default)(ParseUser, [{ + key: '_upgradeToRevocableSession', + value: function (options) { + options = options || {}; + + var upgradeOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + upgradeOptions.useMasterKey = options.useMasterKey; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.upgradeToRevocableSession(this, upgradeOptions)._thenRunCallbacks(options); + } + + /** + * Unlike in the Android/iOS SDKs, logInWith is unnecessary, since you can + * call linkWith on the user (even if it doesn't exist yet on the server). + * @method _linkWith + */ + + }, { + key: '_linkWith', + value: function (provider, options) { + var _this2 = this; + + var authType; + if (typeof provider === 'string') { + authType = provider; + provider = authProviders[provider]; + } else { + authType = provider.getAuthType(); + } + if (options && options.hasOwnProperty('authData')) { + var authData = this.get('authData') || {}; + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + throw new Error('Invalid type: authData field should be an object'); + } + authData[authType] = options.authData; + + var controller = _CoreManager2.default.getUserController(); + return controller.linkWith(this, authData)._thenRunCallbacks(options, this); + } else { + var promise = new _ParsePromise2.default(); + provider.authenticate({ + success: function (provider, result) { + var opts = {}; + opts.authData = result; + if (options.success) { + opts.success = options.success; + } + if (options.error) { + opts.error = options.error; + } + _this2._linkWith(provider, opts).then(function () { + promise.resolve(_this2); + }, function (error) { + promise.reject(error); + }); + }, + error: function (provider, _error) { + if (typeof options.error === 'function') { + options.error(_this2, _error); + } + promise.reject(_error); + } + }); + return promise; + } + } + + /** + * Synchronizes auth data for a provider (e.g. puts the access token in the + * right place to be used by the Facebook SDK). + * @method _synchronizeAuthData + */ + + }, { + key: '_synchronizeAuthData', + value: function (provider) { + if (!this.isCurrent() || !provider) { + return; + } + var authType; + if (typeof provider === 'string') { + authType = provider; + provider = authProviders[authType]; + } else { + authType = provider.getAuthType(); + } + var authData = this.get('authData'); + if (!provider || !authData || (typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return; + } + var success = provider.restoreAuthentication(authData[authType]); + if (!success) { + this._unlinkFrom(provider); + } + } + + /** + * Synchronizes authData for all providers. + * @method _synchronizeAllAuthData + */ + + }, { + key: '_synchronizeAllAuthData', + value: function () { + var authData = this.get('authData'); + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return; + } + + for (var key in authData) { + this._synchronizeAuthData(key); + } + } + + /** + * Removes null values from authData (which exist temporarily for + * unlinking) + * @method _cleanupAuthData + */ + + }, { + key: '_cleanupAuthData', + value: function () { + if (!this.isCurrent()) { + return; + } + var authData = this.get('authData'); + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return; + } + + for (var key in authData) { + if (!authData[key]) { + delete authData[key]; + } + } + } + + /** + * Unlinks a user from a service. + * @method _unlinkFrom + */ + + }, { + key: '_unlinkFrom', + value: function (provider, options) { + var _this3 = this; + + if (typeof provider === 'string') { + provider = authProviders[provider]; + } else { + provider.getAuthType(); + } + return this._linkWith(provider, { authData: null }).then(function () { + _this3._synchronizeAuthData(provider); + return _ParsePromise2.default.as(_this3); + })._thenRunCallbacks(options); + } + + /** + * Checks whether a user is linked to a service. + * @method _isLinked + */ + + }, { + key: '_isLinked', + value: function (provider) { + var authType; + if (typeof provider === 'string') { + authType = provider; + } else { + authType = provider.getAuthType(); + } + var authData = this.get('authData') || {}; + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return false; + } + return !!authData[authType]; + } + + /** + * Deauthenticates all providers. + * @method _logOutWithAll + */ + + }, { + key: '_logOutWithAll', + value: function () { + var authData = this.get('authData'); + if ((typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) !== 'object') { + return; + } + + for (var key in authData) { + this._logOutWith(key); + } + } + + /** + * Deauthenticates a single provider (e.g. removing access tokens from the + * Facebook SDK). + * @method _logOutWith + */ + + }, { + key: '_logOutWith', + value: function (provider) { + if (!this.isCurrent()) { + return; + } + if (typeof provider === 'string') { + provider = authProviders[provider]; + } + if (provider && provider.deauthenticate) { + provider.deauthenticate(); + } + } + + /** + * Class instance method used to maintain specific keys when a fetch occurs. + * Used to ensure that the session token is not lost. + */ + + }, { + key: '_preserveFieldsOnFetch', + value: function () { + return { + sessionToken: this.get('sessionToken') + }; + } + + /** + * Returns true ifcurrent would return this user.
+ * @method isCurrent
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'isCurrent',
+ value: function () {
+ var current = ParseUser.current();
+ return !!current && current.id === this.id;
+ }
+
+ /**
+ * Returns get("username").
+ * @method getUsername
+ * @return {String}
+ */
+
+ }, {
+ key: 'getUsername',
+ value: function () {
+ var username = this.get('username');
+ if (username == null || typeof username === 'string') {
+ return username;
+ }
+ return '';
+ }
+
+ /**
+ * Calls set("username", username, options) and returns the result.
+ * @method setUsername
+ * @param {String} username
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'setUsername',
+ value: function (username) {
+ // Strip anonymity, even we do not support anonymous user in js SDK, we may
+ // encounter anonymous user created by android/iOS in cloud code.
+ var authData = this.get('authData');
+ if (authData && (typeof authData === 'undefined' ? 'undefined' : (0, _typeof3.default)(authData)) === 'object' && authData.hasOwnProperty('anonymous')) {
+ // We need to set anonymous to null instead of deleting it in order to remove it from Parse.
+ authData.anonymous = null;
+ }
+ this.set('username', username);
+ }
+
+ /**
+ * Calls set("password", password, options) and returns the result.
+ * @method setPassword
+ * @param {String} password
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'setPassword',
+ value: function (password) {
+ this.set('password', password);
+ }
+
+ /**
+ * Returns get("email").
+ * @method getEmail
+ * @return {String}
+ */
+
+ }, {
+ key: 'getEmail',
+ value: function () {
+ var email = this.get('email');
+ if (email == null || typeof email === 'string') {
+ return email;
+ }
+ return '';
+ }
+
+ /**
+ * Calls set("email", email, options) and returns the result.
+ * @method setEmail
+ * @param {String} email
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'setEmail',
+ value: function (email) {
+ this.set('email', email);
+ }
+
+ /**
+ * Returns the session token for this user, if the user has been logged in,
+ * or if it is the result of a query with the master key. Otherwise, returns
+ * undefined.
+ * @method getSessionToken
+ * @return {String} the session token, or undefined
+ */
+
+ }, {
+ key: 'getSessionToken',
+ value: function () {
+ var token = this.get('sessionToken');
+ if (token == null || typeof token === 'string') {
+ return token;
+ }
+ return '';
+ }
+
+ /**
+ * Checks whether this user is the current user and has been authenticated.
+ * @method authenticated
+ * @return (Boolean) whether this user is the current user and is logged in.
+ */
+
+ }, {
+ key: 'authenticated',
+ value: function () {
+ var current = ParseUser.current();
+ return !!this.get('sessionToken') && !!current && current.id === this.id;
+ }
+
+ /**
+ * Signs up a new user. You should call this instead of save for
+ * new Parse.Users. This will create a new Parse.User on the server, and
+ * also persist the session on disk so that you can access the user using
+ * current.
+ *
+ * A username and password must be set before calling signUp.
+ * + *Calls options.success or options.error on completion.
+ * + * @method signUp + * @param {Object} attrs Extra fields to set on the new user, or null. + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is fulfilled when the signup + * finishes. + */ + + }, { + key: 'signUp', + value: function (attrs, options) { + options = options || {}; + + var signupOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + signupOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('installationId')) { + signupOptions.installationId = options.installationId; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.signUp(this, attrs, signupOptions)._thenRunCallbacks(options, this); + } + + /** + * Logs in a Parse.User. On success, this saves the session to disk, + * so you can retrieve the currently logged in user using + *current.
+ *
+ * A username and password must be set before calling logIn.
+ * + *Calls options.success or options.error on completion.
+ * + * @method logIn + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login is complete. + */ + + }, { + key: 'logIn', + value: function (options) { + options = options || {}; + + var loginOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + loginOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('installationId')) { + loginOptions.installationId = options.installationId; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.logIn(this, loginOptions)._thenRunCallbacks(options, this); + } + + /** + * Wrap the default save behavior with functionality to save to local + * storage if this is current user. + */ + + }, { + key: 'save', + value: function () { + var _this4 = this; + + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return (0, _get3.default)(ParseUser.prototype.__proto__ || (0, _getPrototypeOf2.default)(ParseUser.prototype), 'save', this).apply(this, args).then(function () { + if (_this4.isCurrent()) { + return _CoreManager2.default.getUserController().updateUserOnDisk(_this4); + } + return _this4; + }); + } + + /** + * Wrap the default destroy behavior with functionality that logs out + * the current user when it is destroyed + */ + + }, { + key: 'destroy', + value: function () { + var _this5 = this; + + for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + return (0, _get3.default)(ParseUser.prototype.__proto__ || (0, _getPrototypeOf2.default)(ParseUser.prototype), 'destroy', this).apply(this, args).then(function () { + if (_this5.isCurrent()) { + return _CoreManager2.default.getUserController().removeUserFromDisk(); + } + return _this5; + }); + } + + /** + * Wrap the default fetch behavior with functionality to save to local + * storage if this is current user. + */ + + }, { + key: 'fetch', + value: function () { + var _this6 = this; + + for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + args[_key3] = arguments[_key3]; + } + + return (0, _get3.default)(ParseUser.prototype.__proto__ || (0, _getPrototypeOf2.default)(ParseUser.prototype), 'fetch', this).apply(this, args).then(function () { + if (_this6.isCurrent()) { + return _CoreManager2.default.getUserController().updateUserOnDisk(_this6); + } + return _this6; + }); + } + }], [{ + key: 'readOnlyAttributes', + value: function () { + return ['sessionToken']; + } + + /** + * Adds functionality to the existing Parse.User class + * @method extend + * @param {Object} protoProps A set of properties to add to the prototype + * @param {Object} classProps A set of static properties to add to the class + * @static + * @return {Class} The newly extended Parse.User class + */ + + }, { + key: 'extend', + value: function (protoProps, classProps) { + if (protoProps) { + for (var prop in protoProps) { + if (prop !== 'className') { + (0, _defineProperty2.default)(ParseUser.prototype, prop, { + value: protoProps[prop], + enumerable: false, + writable: true, + configurable: true + }); + } + } + } + + if (classProps) { + for (var prop in classProps) { + if (prop !== 'className') { + (0, _defineProperty2.default)(ParseUser, prop, { + value: classProps[prop], + enumerable: false, + writable: true, + configurable: true + }); + } + } + } + + return ParseUser; + } + + /** + * Retrieves the currently logged in ParseUser with a valid session, + * either from memory or localStorage, if necessary. + * @method current + * @static + * @return {Parse.Object} The currently logged in Parse.User. + */ + + }, { + key: 'current', + value: function () { + if (!canUseCurrentUser) { + return null; + } + var controller = _CoreManager2.default.getUserController(); + return controller.currentUser(); + } + + /** + * Retrieves the currently logged in ParseUser from asynchronous Storage. + * @method currentAsync + * @static + * @return {Parse.Promise} A Promise that is resolved with the currently + * logged in Parse User + */ + + }, { + key: 'currentAsync', + value: function () { + if (!canUseCurrentUser) { + return _ParsePromise2.default.as(null); + } + var controller = _CoreManager2.default.getUserController(); + return controller.currentUserAsync(); + } + + /** + * Signs up a new user with a username (or email) and password. + * This will create a new Parse.User on the server, and also persist the + * session in localStorage so that you can access the user using + * {@link #current}. + * + *Calls options.success or options.error on completion.
+ * + * @method signUp + * @param {String} username The username (or email) to sign up with. + * @param {String} password The password to sign up with. + * @param {Object} attrs Extra fields to set on the new user. + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the signup completes. + */ + + }, { + key: 'signUp', + value: function (username, password, attrs, options) { + attrs = attrs || {}; + attrs.username = username; + attrs.password = password; + var user = new ParseUser(attrs); + return user.signUp({}, options); + } + + /** + * Logs in a user with a username (or email) and password. On success, this + * saves the session to disk, so you can retrieve the currently logged in + * user usingcurrent.
+ *
+ * Calls options.success or options.error on completion.
+ * + * @method logIn + * @param {String} username The username (or email) to log in with. + * @param {String} password The password to log in with. + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login completes. + */ + + }, { + key: 'logIn', + value: function (username, password, options) { + if (typeof username !== 'string') { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Username must be a string.')); + } else if (typeof password !== 'string') { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Password must be a string.')); + } + var user = new ParseUser(); + user._finishFetch({ username: username, password: password }); + return user.logIn(options); + } + + /** + * Logs in a user with a session token. On success, this saves the session + * to disk, so you can retrieve the currently logged in user using + *current.
+ *
+ * Calls options.success or options.error on completion.
+ * + * @method become + * @param {String} sessionToken The sessionToken to log in with. + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login completes. + */ + + }, { + key: 'become', + value: function (sessionToken, options) { + if (!canUseCurrentUser) { + throw new Error('It is not memory-safe to become a user in a server environment'); + } + options = options || {}; + + var becomeOptions = { + sessionToken: sessionToken + }; + if (options.hasOwnProperty('useMasterKey')) { + becomeOptions.useMasterKey = options.useMasterKey; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.become(becomeOptions)._thenRunCallbacks(options); + } + }, { + key: 'logInWith', + value: function (provider, options) { + return ParseUser._logInWith(provider, options); + } + + /** + * Logs out the currently logged in user session. This will remove the + * session from disk, log out of linked services, and future calls to + *current will return null.
+ * @method logOut
+ * @static
+ * @return {Parse.Promise} A promise that is resolved when the session is
+ * destroyed on the server.
+ */
+
+ }, {
+ key: 'logOut',
+ value: function () {
+ if (!canUseCurrentUser) {
+ throw new Error('There is no current user user on a node.js server environment.');
+ }
+
+ var controller = _CoreManager2.default.getUserController();
+ return controller.logOut();
+ }
+
+ /**
+ * Requests a password reset email to be sent to the specified email address
+ * associated with the user account. This email allows the user to securely
+ * reset their password on the Parse site.
+ *
+ * Calls options.success or options.error on completion.
+ * + * @method requestPasswordReset + * @param {String} email The email address associated with the user that + * forgot their password. + * @param {Object} options A Backbone-style options object. + * @static + */ + + }, { + key: 'requestPasswordReset', + value: function (email, options) { + options = options || {}; + + var requestOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + requestOptions.useMasterKey = options.useMasterKey; + } + + var controller = _CoreManager2.default.getUserController(); + return controller.requestPasswordReset(email, requestOptions)._thenRunCallbacks(options); + } + + /** + * Allow someone to define a custom User class without className + * being rewritten to _User. The default behavior is to rewrite + * User to _User for legacy reasons. This allows developers to + * override that behavior. + * + * @method allowCustomUserClass + * @param {Boolean} isAllowed Whether or not to allow custom User class + * @static + */ + + }, { + key: 'allowCustomUserClass', + value: function (isAllowed) { + _CoreManager2.default.set('PERFORM_USER_REWRITE', !isAllowed); + } + + /** + * Allows a legacy application to start using revocable sessions. If the + * current session token is not revocable, a request will be made for a new, + * revocable session. + * It is not necessary to call this method from cloud code unless you are + * handling user signup or login from the server side. In a cloud code call, + * this function will not attempt to upgrade the current token. + * @method enableRevocableSession + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is resolved when the process has + * completed. If a replacement session token is requested, the promise + * will be resolved after a new token has been fetched. + */ + + }, { + key: 'enableRevocableSession', + value: function (options) { + options = options || {}; + _CoreManager2.default.set('FORCE_REVOCABLE_SESSION', true); + if (canUseCurrentUser) { + var current = ParseUser.current(); + if (current) { + return current._upgradeToRevocableSession(options); + } + } + return _ParsePromise2.default.as()._thenRunCallbacks(options); + } + + /** + * Enables the use of become or the current user in a server + * environment. These features are disabled by default, since they depend on + * global objects that are not memory-safe for most servers. + * @method enableUnsafeCurrentUser + * @static + */ + + }, { + key: 'enableUnsafeCurrentUser', + value: function () { + canUseCurrentUser = true; + } + + /** + * Disables the use of become or the current user in any environment. + * These features are disabled on servers by default, since they depend on + * global objects that are not memory-safe for most servers. + * @method disableUnsafeCurrentUser + * @static + */ + + }, { + key: 'disableUnsafeCurrentUser', + value: function () { + canUseCurrentUser = false; + } + }, { + key: '_registerAuthenticationProvider', + value: function (provider) { + authProviders[provider.getAuthType()] = provider; + // Synchronize the current user with the auth provider. + ParseUser.currentAsync().then(function (current) { + if (current) { + current._synchronizeAuthData(provider.getAuthType()); + } + }); + } + }, { + key: '_logInWith', + value: function (provider, options) { + var user = new ParseUser(); + return user._linkWith(provider, options); + } + }, { + key: '_clearCache', + value: function () { + currentUserCache = null; + currentUserCacheMatchesDisk = false; + } + }, { + key: '_setCurrentUserCache', + value: function (user) { + currentUserCache = user; + } + }]); + return ParseUser; +}(_ParseObject3.default); + +exports.default = ParseUser; + +_ParseObject3.default.registerSubclass('_User', ParseUser); + +var DefaultController = { + updateUserOnDisk: function (user) { + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + var json = user.toJSON(); + json.className = '_User'; + return _Storage2.default.setItemAsync(path, (0, _stringify2.default)(json)).then(function () { + return user; + }); + }, + removeUserFromDisk: function () { + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + currentUserCacheMatchesDisk = true; + currentUserCache = null; + return _Storage2.default.removeItemAsync(path); + }, + setCurrentUser: function (user) { + currentUserCache = user; + user._cleanupAuthData(); + user._synchronizeAllAuthData(); + return DefaultController.updateUserOnDisk(user); + }, + currentUser: function () { + if (currentUserCache) { + return currentUserCache; + } + if (currentUserCacheMatchesDisk) { + return null; + } + if (_Storage2.default.async()) { + throw new Error('Cannot call currentUser() when using a platform with an async ' + 'storage system. Call currentUserAsync() instead.'); + } + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + var userData = _Storage2.default.getItem(path); + currentUserCacheMatchesDisk = true; + if (!userData) { + currentUserCache = null; + return null; + } + userData = JSON.parse(userData); + if (!userData.className) { + userData.className = '_User'; + } + if (userData._id) { + if (userData.objectId !== userData._id) { + userData.objectId = userData._id; + } + delete userData._id; + } + if (userData._sessionToken) { + userData.sessionToken = userData._sessionToken; + delete userData._sessionToken; + } + var current = _ParseObject3.default.fromJSON(userData); + currentUserCache = current; + current._synchronizeAllAuthData(); + return current; + }, + currentUserAsync: function () { + if (currentUserCache) { + return _ParsePromise2.default.as(currentUserCache); + } + if (currentUserCacheMatchesDisk) { + return _ParsePromise2.default.as(null); + } + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + return _Storage2.default.getItemAsync(path).then(function (userData) { + currentUserCacheMatchesDisk = true; + if (!userData) { + currentUserCache = null; + return _ParsePromise2.default.as(null); + } + userData = JSON.parse(userData); + if (!userData.className) { + userData.className = '_User'; + } + if (userData._id) { + if (userData.objectId !== userData._id) { + userData.objectId = userData._id; + } + delete userData._id; + } + if (userData._sessionToken) { + userData.sessionToken = userData._sessionToken; + delete userData._sessionToken; + } + var current = _ParseObject3.default.fromJSON(userData); + currentUserCache = current; + current._synchronizeAllAuthData(); + return _ParsePromise2.default.as(current); + }); + }, + signUp: function (user, attrs, options) { + var username = attrs && attrs.username || user.get('username'); + var password = attrs && attrs.password || user.get('password'); + + if (!username || !username.length) { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Cannot sign up user with an empty name.')); + } + if (!password || !password.length) { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.OTHER_CAUSE, 'Cannot sign up user with an empty password.')); + } + + return user.save(attrs, options).then(function () { + // Clear the password field + user._finishFetch({ password: undefined }); + + if (canUseCurrentUser) { + return DefaultController.setCurrentUser(user); + } + return user; + }); + }, + logIn: function (user, options) { + var RESTController = _CoreManager2.default.getRESTController(); + var stateController = _CoreManager2.default.getObjectStateController(); + var auth = { + username: user.get('username'), + password: user.get('password') + }; + return RESTController.request('GET', 'login', auth, options).then(function (response, status) { + user._migrateId(response.objectId); + user._setExisted(true); + stateController.setPendingOp(user._getStateIdentifier(), 'username', undefined); + stateController.setPendingOp(user._getStateIdentifier(), 'password', undefined); + response.password = undefined; + user._finishFetch(response); + if (!canUseCurrentUser) { + // We can't set the current user, so just return the one we logged in + return _ParsePromise2.default.as(user); + } + return DefaultController.setCurrentUser(user); + }); + }, + become: function (options) { + var user = new ParseUser(); + var RESTController = _CoreManager2.default.getRESTController(); + return RESTController.request('GET', 'users/me', {}, options).then(function (response, status) { + user._finishFetch(response); + user._setExisted(true); + return DefaultController.setCurrentUser(user); + }); + }, + logOut: function () { + return DefaultController.currentUserAsync().then(function (currentUser) { + var path = _Storage2.default.generatePath(CURRENT_USER_KEY); + var promise = _Storage2.default.removeItemAsync(path); + var RESTController = _CoreManager2.default.getRESTController(); + if (currentUser !== null) { + var currentSession = currentUser.getSessionToken(); + if (currentSession && (0, _isRevocableSession2.default)(currentSession)) { + promise = promise.then(function () { + return RESTController.request('POST', 'logout', {}, { sessionToken: currentSession }); + }); + } + currentUser._logOutWithAll(); + currentUser._finishFetch({ sessionToken: undefined }); + } + currentUserCacheMatchesDisk = true; + currentUserCache = null; + + return promise; + }); + }, + requestPasswordReset: function (email, options) { + var RESTController = _CoreManager2.default.getRESTController(); + return RESTController.request('POST', 'requestPasswordReset', { email: email }, options); + }, + upgradeToRevocableSession: function (user, options) { + var token = user.getSessionToken(); + if (!token) { + return _ParsePromise2.default.error(new _ParseError2.default(_ParseError2.default.SESSION_MISSING, 'Cannot upgrade a user with no session token')); + } + + options.sessionToken = token; + + var RESTController = _CoreManager2.default.getRESTController(); + return RESTController.request('POST', 'upgradeToRevocableSession', {}, options).then(function (result) { + var session = new _ParseSession2.default(); + session._finishFetch(result); + user._finishFetch({ sessionToken: session.getSessionToken() }); + if (user.isCurrent()) { + return DefaultController.setCurrentUser(user); + } + return _ParsePromise2.default.as(user); + }); + }, + linkWith: function (user, authData) { + return user.save({ authData: authData }).then(function () { + if (canUseCurrentUser) { + return DefaultController.setCurrentUser(user); + } + return user; + }); + } +}; + +_CoreManager2.default.setUserController(DefaultController); \ No newline at end of file diff --git a/lib/node/Push.js b/lib/node/Push.js new file mode 100644 index 000000000..cfb740767 --- /dev/null +++ b/lib/node/Push.js @@ -0,0 +1,98 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +exports.send = send; + +var _CoreManager = require('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +var _ParseQuery = require('./ParseQuery'); + +var _ParseQuery2 = _interopRequireDefault(_ParseQuery); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Contains functions to deal with Push in Parse. + * @class Parse.Push + * @static + */ + +/** + * Sends a push notification. + * @method send + * @param {Object} data - The data of the push notification. Valid fields + * are: + *
+ * var dimensions = {
+ * gender: 'm',
+ * source: 'web',
+ * dayType: 'weekend'
+ * };
+ * Parse.Analytics.track('signup', dimensions);
+ *
+ *
+ * There is a default limit of 8 dimensions per event tracked.
+ *
+ * @method track
+ * @param {String} name The name of the custom event to report to Parse as
+ * having happened.
+ * @param {Object} dimensions The dictionary of information by which to
+ * segment this event.
+ * @param {Object} options A Backbone-style callback object.
+ * @return {Parse.Promise} A promise that is resolved when the round-trip
+ * to the server completes.
+ */
+export function track(name, dimensions, options) {
+ name = name || '';
+ name = name.replace(/^\s*/, '');
+ name = name.replace(/\s*$/, '');
+ if (name.length === 0) {
+ throw new TypeError('A name for the custom event must be provided');
+ }
+
+ for (var key in dimensions) {
+ if (typeof key !== 'string' || typeof dimensions[key] !== 'string') {
+ throw new TypeError('track() dimensions expects keys and values of type "string".');
+ }
+ }
+
+ options = options || {};
+ return CoreManager.getAnalyticsController().track(name, dimensions)._thenRunCallbacks(options);
+}
+
+var DefaultController = {
+ track(name, dimensions) {
+ var RESTController = CoreManager.getRESTController();
+ return RESTController.request('POST', 'events/' + name, { dimensions: dimensions });
+ }
+};
+
+CoreManager.setAnalyticsController(DefaultController);
\ No newline at end of file
diff --git a/lib/react-native/Cloud.js b/lib/react-native/Cloud.js
new file mode 100644
index 000000000..604707acd
--- /dev/null
+++ b/lib/react-native/Cloud.js
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+import CoreManager from './CoreManager';
+import decode from './decode';
+import encode from './encode';
+import ParseError from './ParseError';
+import ParsePromise from './ParsePromise';
+
+/**
+ * Contains functions for calling and declaring
+ * cloud functions.
+ * + * Some functions are only available from Cloud Code. + *
+ * + * @class Parse.Cloud + * @static + */ + +/** + * Makes a call to a cloud function. + * @method run + * @param {String} name The function name. + * @param {Object} data The parameters to send to the cloud function. + * @param {Object} options A Backbone-style options object + * options.success, if set, should be a function to handle a successful + * call to a cloud function. options.error should be a function that + * handles an error running the cloud function. Both functions are + * optional. Both functions take a single argument. + * @return {Parse.Promise} A promise that will be resolved with the result + * of the function. + */ +export function run(name, data, options) { + options = options || {}; + + if (typeof name !== 'string' || name.length === 0) { + throw new TypeError('Cloud function name must be a string.'); + } + + var requestOptions = {}; + if (options.useMasterKey) { + requestOptions.useMasterKey = options.useMasterKey; + } + if (options.sessionToken) { + requestOptions.sessionToken = options.sessionToken; + } + + return CoreManager.getCloudController().run(name, data, requestOptions)._thenRunCallbacks(options); +} + +var DefaultController = { + run(name, data, options) { + var RESTController = CoreManager.getRESTController(); + + var payload = encode(data, true); + + var requestOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + requestOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + requestOptions.sessionToken = options.sessionToken; + } + + var request = RESTController.request('POST', 'functions/' + name, payload, requestOptions); + + return request.then(function (res) { + var decoded = decode(res); + if (decoded && decoded.hasOwnProperty('result')) { + return ParsePromise.as(decoded.result); + } + return ParsePromise.error(new ParseError(ParseError.INVALID_JSON, 'The server returned an invalid response.')); + })._thenRunCallbacks(options); + } +}; + +CoreManager.setCloudController(DefaultController); \ No newline at end of file diff --git a/lib/react-native/CoreManager.js b/lib/react-native/CoreManager.js new file mode 100644 index 000000000..97062e559 --- /dev/null +++ b/lib/react-native/CoreManager.js @@ -0,0 +1,188 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +var config = { + // Defaults + IS_NODE: typeof process !== 'undefined' && !!process.versions && !!process.versions.node && !process.versions.electron, + REQUEST_ATTEMPT_LIMIT: 5, + SERVER_URL: 'https://api.parse.com/1', + LIVEQUERY_SERVER_URL: null, + VERSION: 'js' + '1.9.2', + APPLICATION_ID: null, + JAVASCRIPT_KEY: null, + MASTER_KEY: null, + USE_MASTER_KEY: false, + PERFORM_USER_REWRITE: true, + FORCE_REVOCABLE_SESSION: false +}; + +function requireMethods(name, methods, controller) { + methods.forEach(func => { + if (typeof controller[func] !== 'function') { + throw new Error(`${name} must implement ${func}()`); + } + }); +} + +module.exports = { + get: function (key) { + if (config.hasOwnProperty(key)) { + return config[key]; + } + throw new Error('Configuration key not found: ' + key); + }, + + set: function (key, value) { + config[key] = value; + }, + + /* Specialized Controller Setters/Getters */ + + setAnalyticsController(controller) { + requireMethods('AnalyticsController', ['track'], controller); + config['AnalyticsController'] = controller; + }, + + getAnalyticsController() { + return config['AnalyticsController']; + }, + + setCloudController(controller) { + requireMethods('CloudController', ['run'], controller); + config['CloudController'] = controller; + }, + + getCloudController() { + return config['CloudController']; + }, + + setConfigController(controller) { + requireMethods('ConfigController', ['current', 'get'], controller); + config['ConfigController'] = controller; + }, + + getConfigController() { + return config['ConfigController']; + }, + + setFileController(controller) { + requireMethods('FileController', ['saveFile', 'saveBase64'], controller); + config['FileController'] = controller; + }, + + getFileController() { + return config['FileController']; + }, + + setInstallationController(controller) { + requireMethods('InstallationController', ['currentInstallationId'], controller); + config['InstallationController'] = controller; + }, + + getInstallationController() { + return config['InstallationController']; + }, + + setObjectController(controller) { + requireMethods('ObjectController', ['save', 'fetch', 'destroy'], controller); + config['ObjectController'] = controller; + }, + + getObjectController() { + return config['ObjectController']; + }, + + setObjectStateController(controller) { + requireMethods('ObjectStateController', ['getState', 'initializeState', 'removeState', 'getServerData', 'setServerData', 'getPendingOps', 'setPendingOp', 'pushPendingState', 'popPendingState', 'mergeFirstPendingState', 'getObjectCache', 'estimateAttribute', 'estimateAttributes', 'commitServerChanges', 'enqueueTask', 'clearAllState'], controller); + + config['ObjectStateController'] = controller; + }, + + getObjectStateController() { + return config['ObjectStateController']; + }, + + setPushController(controller) { + requireMethods('PushController', ['send'], controller); + config['PushController'] = controller; + }, + + getPushController() { + return config['PushController']; + }, + + setQueryController(controller) { + requireMethods('QueryController', ['find'], controller); + config['QueryController'] = controller; + }, + + getQueryController() { + return config['QueryController']; + }, + + setRESTController(controller) { + requireMethods('RESTController', ['request', 'ajax'], controller); + config['RESTController'] = controller; + }, + + getRESTController() { + return config['RESTController']; + }, + + setSessionController(controller) { + requireMethods('SessionController', ['getSession'], controller); + config['SessionController'] = controller; + }, + + getSessionController() { + return config['SessionController']; + }, + + setStorageController(controller) { + if (controller.async) { + requireMethods('An async StorageController', ['getItemAsync', 'setItemAsync', 'removeItemAsync'], controller); + } else { + requireMethods('A synchronous StorageController', ['getItem', 'setItem', 'removeItem'], controller); + } + config['StorageController'] = controller; + }, + + getStorageController() { + return config['StorageController']; + }, + + setUserController(controller) { + requireMethods('UserController', ['setCurrentUser', 'currentUser', 'currentUserAsync', 'signUp', 'logIn', 'become', 'logOut', 'requestPasswordReset', 'upgradeToRevocableSession', 'linkWith'], controller); + config['UserController'] = controller; + }, + + getUserController() { + return config['UserController']; + }, + + setLiveQueryController(controller) { + requireMethods('LiveQueryController', ['subscribe', 'unsubscribe', 'open', 'close'], controller); + config['LiveQueryController'] = controller; + }, + + getLiveQueryController() { + return config['LiveQueryController']; + }, + + setHooksController(controller) { + requireMethods('HooksController', ['create', 'get', 'update', 'remove'], controller); + config['HooksController'] = controller; + }, + + getHooksController() { + return config['HooksController']; + } +}; \ No newline at end of file diff --git a/lib/react-native/EventEmitter.js b/lib/react-native/EventEmitter.js new file mode 100644 index 000000000..3450b4ac6 --- /dev/null +++ b/lib/react-native/EventEmitter.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * This is a simple wrapper to unify EventEmitter implementations across platforms. + */ + +{ + const EventEmitter = require('EventEmitter'); + EventEmitter.prototype.on = EventEmitter.prototype.addListener; + module.exports = EventEmitter; +} \ No newline at end of file diff --git a/lib/react-native/FacebookUtils.js b/lib/react-native/FacebookUtils.js new file mode 100644 index 000000000..24ce10c01 --- /dev/null +++ b/lib/react-native/FacebookUtils.js @@ -0,0 +1,229 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * -weak + */ + +import parseDate from './parseDate'; +import ParseUser from './ParseUser'; + +var PUBLIC_KEY = "*"; + +var initialized = false; +var requestedPermissions; +var initOptions; +var provider = { + authenticate(options) { + if (typeof FB === 'undefined') { + options.error(this, 'Facebook SDK not found.'); + } + FB.login(response => { + if (response.authResponse) { + if (options.success) { + options.success(this, { + id: response.authResponse.userID, + access_token: response.authResponse.accessToken, + expiration_date: new Date(response.authResponse.expiresIn * 1000 + new Date().getTime()).toJSON() + }); + } + } else { + if (options.error) { + options.error(this, response); + } + } + }, { + scope: requestedPermissions + }); + }, + + restoreAuthentication(authData) { + if (authData) { + var expiration = parseDate(authData.expiration_date); + var expiresIn = expiration ? (expiration.getTime() - new Date().getTime()) / 1000 : 0; + + var authResponse = { + userID: authData.id, + accessToken: authData.access_token, + expiresIn: expiresIn + }; + var newOptions = {}; + if (initOptions) { + for (var key in initOptions) { + newOptions[key] = initOptions[key]; + } + } + newOptions.authResponse = authResponse; + + // Suppress checks for login status from the browser. + newOptions.status = false; + + // If the user doesn't match the one known by the FB SDK, log out. + // Most of the time, the users will match -- it's only in cases where + // the FB SDK knows of a different user than the one being restored + // from a Parse User that logged in with username/password. + var existingResponse = FB.getAuthResponse(); + if (existingResponse && existingResponse.userID !== authResponse.userID) { + FB.logout(); + } + + FB.init(newOptions); + } + return true; + }, + + getAuthType() { + return 'facebook'; + }, + + deauthenticate() { + this.restoreAuthentication(null); + } +}; + +/** + * Provides a set of utilities for using Parse with Facebook. + * @class Parse.FacebookUtils + * @static + */ +var FacebookUtils = { + /** + * Initializes Parse Facebook integration. Call this function after you + * have loaded the Facebook Javascript SDK with the same parameters + * as you would pass to
+ *
+ * FB.init(). Parse.FacebookUtils will invoke FB.init() for you
+ * with these arguments.
+ *
+ * @method init
+ * @param {Object} options Facebook options argument as described here:
+ *
+ * FB.init(). The status flag will be coerced to 'false' because it
+ * interferes with Parse Facebook integration. Call FB.getLoginStatus()
+ * explicitly if this behavior is required by your application.
+ */
+ init(options) {
+ if (typeof FB === 'undefined') {
+ throw new Error('The Facebook JavaScript SDK must be loaded before calling init.');
+ }
+ initOptions = {};
+ if (options) {
+ for (var key in options) {
+ initOptions[key] = options[key];
+ }
+ }
+ if (initOptions.status && typeof console !== 'undefined') {
+ var warn = console.warn || console.log || function () {};
+ warn.call(console, 'The "status" flag passed into' + ' FB.init, when set to true, can interfere with Parse Facebook' + ' integration, so it has been suppressed. Please call' + ' FB.getLoginStatus() explicitly if you require this behavior.');
+ }
+ initOptions.status = false;
+ FB.init(initOptions);
+ ParseUser._registerAuthenticationProvider(provider);
+ initialized = true;
+ },
+
+ /**
+ * Gets whether the user has their account linked to Facebook.
+ *
+ * @method isLinked
+ * @param {Parse.User} user User to check for a facebook link.
+ * The user must be logged in on this device.
+ * @return {Boolean} true if the user has their account
+ * linked to Facebook.
+ */
+ isLinked(user) {
+ return user._isLinked('facebook');
+ },
+
+ /**
+ * Logs in a user using Facebook. This method delegates to the Facebook
+ * SDK to authenticate the user, and then automatically logs in (or
+ * creates, in the case where it is a new user) a Parse.User.
+ *
+ * @method logIn
+ * @param {String, Object} permissions The permissions required for Facebook
+ * log in. This is a comma-separated string of permissions.
+ * Alternatively, supply a Facebook authData object as described in our
+ * REST API docs if you want to handle getting facebook auth tokens
+ * yourself.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ logIn(permissions, options) {
+ if (!permissions || typeof permissions === 'string') {
+ if (!initialized) {
+ throw new Error('You must initialize FacebookUtils before calling logIn.');
+ }
+ requestedPermissions = permissions;
+ return ParseUser._logInWith('facebook', options);
+ } else {
+ var newOptions = {};
+ if (options) {
+ for (var key in options) {
+ newOptions[key] = options[key];
+ }
+ }
+ newOptions.authData = permissions;
+ return ParseUser._logInWith('facebook', newOptions);
+ }
+ },
+
+ /**
+ * Links Facebook to an existing PFUser. This method delegates to the
+ * Facebook SDK to authenticate the user, and then automatically links
+ * the account to the Parse.User.
+ *
+ * @method link
+ * @param {Parse.User} user User to link to Facebook. This must be the
+ * current user.
+ * @param {String, Object} permissions The permissions required for Facebook
+ * log in. This is a comma-separated string of permissions.
+ * Alternatively, supply a Facebook authData object as described in our
+ * REST API docs if you want to handle getting facebook auth tokens
+ * yourself.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ link(user, permissions, options) {
+ if (!permissions || typeof permissions === 'string') {
+ if (!initialized) {
+ throw new Error('You must initialize FacebookUtils before calling link.');
+ }
+ requestedPermissions = permissions;
+ return user._linkWith('facebook', options);
+ } else {
+ var newOptions = {};
+ if (options) {
+ for (var key in options) {
+ newOptions[key] = options[key];
+ }
+ }
+ newOptions.authData = permissions;
+ return user._linkWith('facebook', newOptions);
+ }
+ },
+
+ /**
+ * Unlinks the Parse.User from a Facebook account.
+ *
+ * @method unlink
+ * @param {Parse.User} user User to unlink from Facebook. This must be the
+ * current user.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ unlink: function (user, options) {
+ if (!initialized) {
+ throw new Error('You must initialize FacebookUtils before calling unlink.');
+ }
+ return user._unlinkFrom('facebook', options);
+ }
+};
+
+export default FacebookUtils;
\ No newline at end of file
diff --git a/lib/react-native/InstallationController.js b/lib/react-native/InstallationController.js
new file mode 100644
index 000000000..75aad6d0b
--- /dev/null
+++ b/lib/react-native/InstallationController.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+import CoreManager from './CoreManager';
+import ParsePromise from './ParsePromise';
+import Storage from './Storage';
+
+var iidCache = null;
+
+function hexOctet() {
+ return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
+}
+
+function generateId() {
+ return hexOctet() + hexOctet() + '-' + hexOctet() + '-' + hexOctet() + '-' + hexOctet() + '-' + hexOctet() + hexOctet() + hexOctet();
+}
+
+var InstallationController = {
+ currentInstallationId() {
+ if (typeof iidCache === 'string') {
+ return ParsePromise.as(iidCache);
+ }
+ var path = Storage.generatePath('installationId');
+ return Storage.getItemAsync(path).then(iid => {
+ if (!iid) {
+ iid = generateId();
+ return Storage.setItemAsync(path, iid).then(() => {
+ iidCache = iid;
+ return iid;
+ });
+ }
+ iidCache = iid;
+ return iid;
+ });
+ },
+
+ _clearCache() {
+ iidCache = null;
+ },
+
+ _setInstallationIdCache(iid) {
+ iidCache = iid;
+ }
+};
+
+module.exports = InstallationController;
\ No newline at end of file
diff --git a/lib/react-native/LiveQueryClient.js b/lib/react-native/LiveQueryClient.js
new file mode 100644
index 000000000..0af495cb4
--- /dev/null
+++ b/lib/react-native/LiveQueryClient.js
@@ -0,0 +1,430 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+import EventEmitter from './EventEmitter';
+import ParsePromise from './ParsePromise';
+import ParseObject from './ParseObject';
+import LiveQuerySubscription from './LiveQuerySubscription';
+
+// The LiveQuery client inner state
+const CLIENT_STATE = {
+ INITIALIZED: 'initialized',
+ CONNECTING: 'connecting',
+ CONNECTED: 'connected',
+ CLOSED: 'closed',
+ RECONNECTING: 'reconnecting',
+ DISCONNECTED: 'disconnected'
+};
+
+// The event type the LiveQuery client should sent to server
+const OP_TYPES = {
+ CONNECT: 'connect',
+ SUBSCRIBE: 'subscribe',
+ UNSUBSCRIBE: 'unsubscribe',
+ ERROR: 'error'
+};
+
+// The event we get back from LiveQuery server
+const OP_EVENTS = {
+ CONNECTED: 'connected',
+ SUBSCRIBED: 'subscribed',
+ UNSUBSCRIBED: 'unsubscribed',
+ ERROR: 'error',
+ CREATE: 'create',
+ UPDATE: 'update',
+ ENTER: 'enter',
+ LEAVE: 'leave',
+ DELETE: 'delete'
+};
+
+// The event the LiveQuery client should emit
+const CLIENT_EMMITER_TYPES = {
+ CLOSE: 'close',
+ ERROR: 'error',
+ OPEN: 'open'
+};
+
+// The event the LiveQuery subscription should emit
+const SUBSCRIPTION_EMMITER_TYPES = {
+ OPEN: 'open',
+ CLOSE: 'close',
+ ERROR: 'error',
+ CREATE: 'create',
+ UPDATE: 'update',
+ ENTER: 'enter',
+ LEAVE: 'leave',
+ DELETE: 'delete'
+};
+
+let generateInterval = k => {
+ return Math.random() * Math.min(30, Math.pow(2, k) - 1) * 1000;
+};
+
+/**
+ * Creates a new LiveQueryClient.
+ * Extends events.EventEmitter
+ * cloud functions.
+ *
+ * A wrapper of a standard WebSocket client. We add several useful methods to
+ * help you connect/disconnect to LiveQueryServer, subscribe/unsubscribe a ParseQuery easily.
+ *
+ * javascriptKey and masterKey are used for verifying the LiveQueryClient when it tries
+ * to connect to the LiveQuery server
+ *
+ * @class Parse.LiveQueryClient
+ * @constructor
+ * @param {Object} options
+ * @param {string} options.applicationId - applicationId of your Parse app
+ * @param {string} options.serverURL - the URL of your LiveQuery server
+ * @param {string} options.javascriptKey (optional)
+ * @param {string} options.masterKey (optional) Your Parse Master Key. (Node.js only!)
+ * @param {string} options.sessionToken (optional)
+ *
+ *
+ * We expose three events to help you monitor the status of the LiveQueryClient.
+ *
+ *
+ * let Parse = require('parse/node');
+ * let LiveQueryClient = Parse.LiveQueryClient;
+ * let client = new LiveQueryClient({
+ * applicationId: '',
+ * serverURL: '',
+ * javascriptKey: '',
+ * masterKey: ''
+ * });
+ *
+ *
+ * Open - When we establish the WebSocket connection to the LiveQuery server, you'll get this event.
+ *
+ * client.on('open', () => {
+ *
+ * });
+ *
+ * Close - When we lose the WebSocket connection to the LiveQuery server, you'll get this event.
+ *
+ * client.on('close', () => {
+ *
+ * });
+ *
+ * Error - When some network error or LiveQuery server error happens, you'll get this event.
+ *
+ * client.on('error', (error) => {
+ *
+ * });
+ *
+ *
+ */
+export default class LiveQueryClient extends EventEmitter {
+
+ constructor({
+ applicationId,
+ serverURL,
+ javascriptKey,
+ masterKey,
+ sessionToken
+ }) {
+ super();
+
+ if (!serverURL || serverURL.indexOf('ws') !== 0) {
+ throw new Error('You need to set a proper Parse LiveQuery server url before using LiveQueryClient');
+ }
+
+ this.reconnectHandle = null;
+ this.attempts = 1;;
+ this.id = 0;
+ this.requestId = 1;
+ this.serverURL = serverURL;
+ this.applicationId = applicationId;
+ this.javascriptKey = javascriptKey;
+ this.masterKey = masterKey;
+ this.sessionToken = sessionToken;
+ this.connectPromise = new ParsePromise();
+ this.subscriptions = new Map();
+ this.state = CLIENT_STATE.INITIALIZED;
+ }
+
+ shouldOpen() {
+ return this.state === CLIENT_STATE.INITIALIZED || this.state === CLIENT_STATE.DISCONNECTED;
+ }
+
+ /**
+ * Subscribes to a ParseQuery
+ *
+ * If you provide the sessionToken, when the LiveQuery server gets ParseObject's
+ * updates from parse server, it'll try to check whether the sessionToken fulfills
+ * the ParseObject's ACL. The LiveQuery server will only send updates to clients whose
+ * sessionToken is fit for the ParseObject's ACL. You can check the LiveQuery protocol
+ * here for more details. The subscription you get is the same subscription you get
+ * from our Standard API.
+ *
+ * @method subscribe
+ * @param {Object} query - the ParseQuery you want to subscribe to
+ * @param {string} sessionToken (optional)
+ * @return {Object} subscription
+ */
+ subscribe(query, sessionToken) {
+ if (!query) {
+ return;
+ }
+ let where = query.toJSON().where;
+ let className = query.className;
+ let subscribeRequest = {
+ op: OP_TYPES.SUBSCRIBE,
+ requestId: this.requestId,
+ query: {
+ className,
+ where
+ }
+ };
+
+ if (sessionToken) {
+ subscribeRequest.sessionToken = sessionToken;
+ }
+
+ let subscription = new LiveQuerySubscription(this.requestId, query, sessionToken);
+ this.subscriptions.set(this.requestId, subscription);
+ this.requestId += 1;
+ this.connectPromise.then(() => {
+ this.socket.send(JSON.stringify(subscribeRequest));
+ });
+
+ // adding listener so process does not crash
+ // best practice is for developer to register their own listener
+ subscription.on('error', () => {});
+
+ return subscription;
+ }
+
+ /**
+ * After calling unsubscribe you'll stop receiving events from the subscription object.
+ *
+ * @method unsubscribe
+ * @param {Object} subscription - subscription you would like to unsubscribe from.
+ */
+ unsubscribe(subscription) {
+ if (!subscription) {
+ return;
+ }
+
+ this.subscriptions.delete(subscription.id);
+ let unsubscribeRequest = {
+ op: OP_TYPES.UNSUBSCRIBE,
+ requestId: subscription.id
+ };
+ this.connectPromise.then(() => {
+ this.socket.send(JSON.stringify(unsubscribeRequest));
+ });
+ }
+
+ /**
+ * After open is called, the LiveQueryClient will try to send a connect request
+ * to the LiveQuery server.
+ *
+ * @method open
+ */
+ open() {
+ let WebSocketImplementation = this._getWebSocketImplementation();
+ if (!WebSocketImplementation) {
+ this.emit(CLIENT_EMMITER_TYPES.ERROR, 'Can not find WebSocket implementation');
+ return;
+ }
+
+ if (this.state !== CLIENT_STATE.RECONNECTING) {
+ this.state = CLIENT_STATE.CONNECTING;
+ }
+
+ // Get WebSocket implementation
+ this.socket = new WebSocketImplementation(this.serverURL);
+
+ // Bind WebSocket callbacks
+ this.socket.onopen = () => {
+ this._handleWebSocketOpen();
+ };
+
+ this.socket.onmessage = event => {
+ this._handleWebSocketMessage(event);
+ };
+
+ this.socket.onclose = () => {
+ this._handleWebSocketClose();
+ };
+
+ this.socket.onerror = error => {
+ this._handleWebSocketError(error);
+ };
+ }
+
+ resubscribe() {
+ this.subscriptions.forEach((subscription, requestId) => {
+ let query = subscription.query;
+ let where = query.toJSON().where;
+ let className = query.className;
+ let sessionToken = subscription.sessionToken;
+ let subscribeRequest = {
+ op: OP_TYPES.SUBSCRIBE,
+ requestId,
+ query: {
+ className,
+ where
+ }
+ };
+
+ if (sessionToken) {
+ subscribeRequest.sessionToken = sessionToken;
+ }
+
+ this.connectPromise.then(() => {
+ this.socket.send(JSON.stringify(subscribeRequest));
+ });
+ });
+ }
+
+ /**
+ * This method will close the WebSocket connection to this LiveQueryClient,
+ * cancel the auto reconnect and unsubscribe all subscriptions based on it.
+ *
+ * @method close
+ */
+ close() {
+ if (this.state === CLIENT_STATE.INITIALIZED || this.state === CLIENT_STATE.DISCONNECTED) {
+ return;
+ }
+ this.state = CLIENT_STATE.DISCONNECTED;
+ this.socket.close();
+ // Notify each subscription about the close
+ for (let subscription of this.subscriptions.values()) {
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.CLOSE);
+ }
+ this._handleReset();
+ this.emit(CLIENT_EMMITER_TYPES.CLOSE);
+ }
+
+ _getWebSocketImplementation() {
+ return WebSocket;
+ }
+
+ // ensure we start with valid state if connect is called again after close
+ _handleReset() {
+ this.attempts = 1;;
+ this.id = 0;
+ this.requestId = 1;
+ this.connectPromise = new ParsePromise();
+ this.subscriptions = new Map();
+ }
+
+ _handleWebSocketOpen() {
+ this.attempts = 1;
+ let connectRequest = {
+ op: OP_TYPES.CONNECT,
+ applicationId: this.applicationId,
+ javascriptKey: this.javascriptKey,
+ masterKey: this.masterKey,
+ sessionToken: this.sessionToken
+ };
+ this.socket.send(JSON.stringify(connectRequest));
+ }
+
+ _handleWebSocketMessage(event) {
+ let data = event.data;
+ if (typeof data === 'string') {
+ data = JSON.parse(data);
+ }
+ let subscription = null;
+ if (data.requestId) {
+ subscription = this.subscriptions.get(data.requestId);
+ }
+ switch (data.op) {
+ case OP_EVENTS.CONNECTED:
+ if (this.state === CLIENT_STATE.RECONNECTING) {
+ this.resubscribe();
+ }
+ this.emit(CLIENT_EMMITER_TYPES.OPEN);
+ this.id = data.clientId;
+ this.connectPromise.resolve();
+ this.state = CLIENT_STATE.CONNECTED;
+ break;
+ case OP_EVENTS.SUBSCRIBED:
+ if (subscription) {
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.OPEN);
+ }
+ break;
+ case OP_EVENTS.ERROR:
+ if (data.requestId) {
+ if (subscription) {
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.ERROR, data.error);
+ }
+ } else {
+ this.emit(CLIENT_EMMITER_TYPES.ERROR, data.error);
+ }
+ break;
+ case OP_EVENTS.UNSUBSCRIBED:
+ // We have already deleted subscription in unsubscribe(), do nothing here
+ break;
+ default:
+ // create, update, enter, leave, delete cases
+ let className = data.object.className;
+ // Delete the extrea __type and className fields during transfer to full JSON
+ delete data.object.__type;
+ delete data.object.className;
+ let parseObject = new ParseObject(className);
+ parseObject._finishFetch(data.object);
+ if (!subscription) {
+ break;
+ }
+ subscription.emit(data.op, parseObject);
+ }
+ }
+
+ _handleWebSocketClose() {
+ if (this.state === CLIENT_STATE.DISCONNECTED) {
+ return;
+ }
+ this.state = CLIENT_STATE.CLOSED;
+ this.emit(CLIENT_EMMITER_TYPES.CLOSE);
+ // Notify each subscription about the close
+ for (let subscription of this.subscriptions.values()) {
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.CLOSE);
+ }
+ this._handleReconnect();
+ }
+
+ _handleWebSocketError(error) {
+ this.emit(CLIENT_EMMITER_TYPES.ERROR, error);
+ for (let subscription of this.subscriptions.values()) {
+ subscription.emit(SUBSCRIPTION_EMMITER_TYPES.ERROR);
+ }
+ this._handleReconnect();
+ }
+
+ _handleReconnect() {
+ // if closed or currently reconnecting we stop attempting to reconnect
+ if (this.state === CLIENT_STATE.DISCONNECTED) {
+ return;
+ }
+
+ this.state = CLIENT_STATE.RECONNECTING;
+ let time = generateInterval(this.attempts);
+
+ // handle case when both close/error occur at frequent rates we ensure we do not reconnect unnecessarily.
+ // we're unable to distinguish different between close/error when we're unable to reconnect therefore
+ // we try to reonnect in both cases
+ // server side ws and browser WebSocket behave differently in when close/error get triggered
+
+ if (this.reconnectHandle) {
+ clearTimeout(this.reconnectHandle);
+ }
+
+ this.reconnectHandle = setTimeout((() => {
+ this.attempts++;
+ this.connectPromise = new ParsePromise();
+ this.open();
+ }).bind(this), time);
+ }
+}
\ No newline at end of file
diff --git a/lib/react-native/LiveQuerySubscription.js b/lib/react-native/LiveQuerySubscription.js
new file mode 100644
index 000000000..4f19b2b6e
--- /dev/null
+++ b/lib/react-native/LiveQuerySubscription.js
@@ -0,0 +1,112 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+import EventEmitter from './EventEmitter';
+import CoreManager from './CoreManager';
+
+/**
+ * Creates a new LiveQuery Subscription.
+ * Extends events.EventEmitter
+ * cloud functions.
+ *
+ * @constructor
+ * @param {string} id - subscription id
+ * @param {string} query - query to subscribe to
+ * @param {string} sessionToken - optional session token
+ *
+ * Open Event - When you call query.subscribe(), we send a subscribe request to + * the LiveQuery server, when we get the confirmation from the LiveQuery server, + * this event will be emitted. When the client loses WebSocket connection to the + * LiveQuery server, we will try to auto reconnect the LiveQuery server. If we + * reconnect the LiveQuery server and successfully resubscribe the ParseQuery, + * you'll also get this event. + * + *
+ * subscription.on('open', () => {
+ *
+ * });
+ *
+ * Create Event - When a new ParseObject is created and it fulfills the ParseQuery you subscribe, + * you'll get this event. The object is the ParseObject which is created. + * + *
+ * subscription.on('create', (object) => {
+ *
+ * });
+ *
+ * Update Event - When an existing ParseObject which fulfills the ParseQuery you subscribe + * is updated (The ParseObject fulfills the ParseQuery before and after changes), + * you'll get this event. The object is the ParseObject which is updated. + * Its content is the latest value of the ParseObject. + * + *
+ * subscription.on('update', (object) => {
+ *
+ * });
+ *
+ * Enter Event - When an existing ParseObject's old value doesn't fulfill the ParseQuery + * but its new value fulfills the ParseQuery, you'll get this event. The object is the + * ParseObject which enters the ParseQuery. Its content is the latest value of the ParseObject. + * + *
+ * subscription.on('enter', (object) => {
+ *
+ * });
+ *
+ *
+ * Update Event - When an existing ParseObject's old value fulfills the ParseQuery but its new value + * doesn't fulfill the ParseQuery, you'll get this event. The object is the ParseObject + * which leaves the ParseQuery. Its content is the latest value of the ParseObject. + * + *
+ * subscription.on('leave', (object) => {
+ *
+ * });
+ *
+ *
+ * Delete Event - When an existing ParseObject which fulfills the ParseQuery is deleted, you'll + * get this event. The object is the ParseObject which is deleted. + * + *
+ * subscription.on('delete', (object) => {
+ *
+ * });
+ *
+ *
+ * Close Event - When the client loses the WebSocket connection to the LiveQuery + * server and we stop receiving events, you'll get this event. + * + *
+ * subscription.on('close', () => {
+ *
+ * });
+ *
+ *
+ */
+export default class Subscription extends EventEmitter {
+ constructor(id, query, sessionToken) {
+ super();
+ this.id = id;
+ this.query = query;
+ this.sessionToken = sessionToken;
+ }
+
+ /**
+ * @method unsubscribe
+ */
+ unsubscribe() {
+ let _this = this;
+ CoreManager.getLiveQueryController().getDefaultLiveQueryClient().then(liveQueryClient => {
+ liveQueryClient.unsubscribe(_this);
+ _this.emit('close');
+ this.resolve();
+ });
+ }
+}
\ No newline at end of file
diff --git a/lib/react-native/ObjectStateMutations.js b/lib/react-native/ObjectStateMutations.js
new file mode 100644
index 000000000..349dd150b
--- /dev/null
+++ b/lib/react-native/ObjectStateMutations.js
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+import encode from './encode';
+import ParseFile from './ParseFile';
+import ParseObject from './ParseObject';
+import ParsePromise from './ParsePromise';
+import ParseRelation from './ParseRelation';
+import TaskQueue from './TaskQueue';
+import { RelationOp } from './ParseOp';
+
+export function defaultState() {
+ return {
+ serverData: {},
+ pendingOps: [{}],
+ objectCache: {},
+ tasks: new TaskQueue(),
+ existed: false
+ };
+}
+
+export function setServerData(serverData, attributes) {
+ for (let attr in attributes) {
+ if (typeof attributes[attr] !== 'undefined') {
+ serverData[attr] = attributes[attr];
+ } else {
+ delete serverData[attr];
+ }
+ }
+}
+
+export function setPendingOp(pendingOps, attr, op) {
+ let last = pendingOps.length - 1;
+ if (op) {
+ pendingOps[last][attr] = op;
+ } else {
+ delete pendingOps[last][attr];
+ }
+}
+
+export function pushPendingState(pendingOps) {
+ pendingOps.push({});
+}
+
+export function popPendingState(pendingOps) {
+ let first = pendingOps.shift();
+ if (!pendingOps.length) {
+ pendingOps[0] = {};
+ }
+ return first;
+}
+
+export function mergeFirstPendingState(pendingOps) {
+ let first = popPendingState(pendingOps);
+ let next = pendingOps[0];
+ for (let attr in first) {
+ if (next[attr] && first[attr]) {
+ let merged = next[attr].mergeWith(first[attr]);
+ if (merged) {
+ next[attr] = merged;
+ }
+ } else {
+ next[attr] = first[attr];
+ }
+ }
+}
+
+export function estimateAttribute(serverData, pendingOps, className, id, attr) {
+ let value = serverData[attr];
+ for (let i = 0; i < pendingOps.length; i++) {
+ if (pendingOps[i][attr]) {
+ if (pendingOps[i][attr] instanceof RelationOp) {
+ if (id) {
+ value = pendingOps[i][attr].applyTo(value, { className: className, id: id }, attr);
+ }
+ } else {
+ value = pendingOps[i][attr].applyTo(value);
+ }
+ }
+ }
+ return value;
+}
+
+export function estimateAttributes(serverData, pendingOps, className, id) {
+ let data = {};
+
+ for (var attr in serverData) {
+ data[attr] = serverData[attr];
+ }
+ for (let i = 0; i < pendingOps.length; i++) {
+ for (attr in pendingOps[i]) {
+ if (pendingOps[i][attr] instanceof RelationOp) {
+ if (id) {
+ data[attr] = pendingOps[i][attr].applyTo(data[attr], { className: className, id: id }, attr);
+ }
+ } else {
+ data[attr] = pendingOps[i][attr].applyTo(data[attr]);
+ }
+ }
+ }
+ return data;
+}
+
+export function commitServerChanges(serverData, objectCache, changes) {
+ for (let attr in changes) {
+ let val = changes[attr];
+ serverData[attr] = val;
+ if (val && typeof val === 'object' && !(val instanceof ParseObject) && !(val instanceof ParseFile) && !(val instanceof ParseRelation)) {
+ let json = encode(val, false, true);
+ objectCache[attr] = JSON.stringify(json);
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/react-native/Parse.js b/lib/react-native/Parse.js
new file mode 100644
index 000000000..b95835026
--- /dev/null
+++ b/lib/react-native/Parse.js
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+import decode from './decode';
+import encode from './encode';
+import CoreManager from './CoreManager';
+import InstallationController from './InstallationController';
+import * as ParseOp from './ParseOp';
+import RESTController from './RESTController';
+
+/**
+ * Contains all Parse API classes and functions.
+ * @class Parse
+ * @static
+ */
+var Parse = {
+ /**
+ * Call this method first to set up your authentication tokens for Parse.
+ * You can get your keys from the Data Browser on parse.com.
+ * @method initialize
+ * @param {String} applicationId Your Parse Application ID.
+ * @param {String} javaScriptKey (optional) Your Parse JavaScript Key (Not needed for parse-server)
+ * @param {String} masterKey (optional) Your Parse Master Key. (Node.js only!)
+ * @static
+ */
+ initialize(applicationId, javaScriptKey) {
+ Parse._initialize(applicationId, javaScriptKey);
+ },
+
+ _initialize(applicationId, javaScriptKey, masterKey) {
+ CoreManager.set('APPLICATION_ID', applicationId);
+ CoreManager.set('JAVASCRIPT_KEY', javaScriptKey);
+ CoreManager.set('MASTER_KEY', masterKey);
+ CoreManager.set('USE_MASTER_KEY', false);
+ }
+};
+
+/** These legacy setters may eventually be deprecated **/
+Object.defineProperty(Parse, 'applicationId', {
+ get() {
+ return CoreManager.get('APPLICATION_ID');
+ },
+ set(value) {
+ CoreManager.set('APPLICATION_ID', value);
+ }
+});
+Object.defineProperty(Parse, 'javaScriptKey', {
+ get() {
+ return CoreManager.get('JAVASCRIPT_KEY');
+ },
+ set(value) {
+ CoreManager.set('JAVASCRIPT_KEY', value);
+ }
+});
+Object.defineProperty(Parse, 'masterKey', {
+ get() {
+ return CoreManager.get('MASTER_KEY');
+ },
+ set(value) {
+ CoreManager.set('MASTER_KEY', value);
+ }
+});
+Object.defineProperty(Parse, 'serverURL', {
+ get() {
+ return CoreManager.get('SERVER_URL');
+ },
+ set(value) {
+ CoreManager.set('SERVER_URL', value);
+ }
+});
+Object.defineProperty(Parse, 'liveQueryServerURL', {
+ get() {
+ return CoreManager.get('LIVEQUERY_SERVER_URL');
+ },
+ set(value) {
+ CoreManager.set('LIVEQUERY_SERVER_URL', value);
+ }
+});
+/** End setters **/
+
+Parse.ACL = require('./ParseACL').default;
+Parse.Analytics = require('./Analytics');
+Parse.Cloud = require('./Cloud');
+Parse.CoreManager = require('./CoreManager');
+Parse.Config = require('./ParseConfig').default;
+Parse.Error = require('./ParseError').default;
+Parse.FacebookUtils = require('./FacebookUtils').default;
+Parse.File = require('./ParseFile').default;
+Parse.GeoPoint = require('./ParseGeoPoint').default;
+Parse.Installation = require('./ParseInstallation').default;
+Parse.Object = require('./ParseObject').default;
+Parse.Op = {
+ Set: ParseOp.SetOp,
+ Unset: ParseOp.UnsetOp,
+ Increment: ParseOp.IncrementOp,
+ Add: ParseOp.AddOp,
+ Remove: ParseOp.RemoveOp,
+ AddUnique: ParseOp.AddUniqueOp,
+ Relation: ParseOp.RelationOp
+};
+Parse.Promise = require('./ParsePromise').default;
+Parse.Push = require('./Push');
+Parse.Query = require('./ParseQuery').default;
+Parse.Relation = require('./ParseRelation').default;
+Parse.Role = require('./ParseRole').default;
+Parse.Session = require('./ParseSession').default;
+Parse.Storage = require('./Storage');
+Parse.User = require('./ParseUser').default;
+Parse.LiveQuery = require('./ParseLiveQuery').default;
+Parse.LiveQueryClient = require('./LiveQueryClient').default;
+
+Parse._request = function (...args) {
+ return CoreManager.getRESTController().request.apply(null, args);
+};
+Parse._ajax = function (...args) {
+ return CoreManager.getRESTController().ajax.apply(null, args);
+};
+// We attempt to match the signatures of the legacy versions of these methods
+Parse._decode = function (_, value) {
+ return decode(value);
+};
+Parse._encode = function (value, _, disallowObjects) {
+ return encode(value, disallowObjects);
+};
+Parse._getInstallationId = function () {
+ return CoreManager.getInstallationController().currentInstallationId();
+};
+
+CoreManager.setInstallationController(InstallationController);
+CoreManager.setRESTController(RESTController);
+
+// For legacy requires, of the form `var Parse = require('parse').Parse`
+Parse.Parse = Parse;
+
+module.exports = Parse;
\ No newline at end of file
diff --git a/lib/react-native/ParseACL.js b/lib/react-native/ParseACL.js
new file mode 100644
index 000000000..2c28c6fa6
--- /dev/null
+++ b/lib/react-native/ParseACL.js
@@ -0,0 +1,325 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+import ParseRole from './ParseRole';
+import ParseUser from './ParseUser';
+
+var PUBLIC_KEY = '*';
+
+/**
+ * Creates a new ACL.
+ * If no argument is given, the ACL has no permissions for anyone.
+ * If the argument is a Parse.User, the ACL will have read and write
+ * permission for only that user.
+ * If the argument is any other JSON object, that object will be interpretted
+ * as a serialized ACL created with toJSON().
+ * @class Parse.ACL
+ * @constructor
+ *
+ * An ACL, or Access Control List can be added to any
+ * Parse.Object to restrict access to only a subset of users
+ * of your application.
Parse.Error.
+ * @param {String} message A detailed description of the error.
+ */
+export default class ParseError {
+ constructor(code, message) {
+ this.code = code;
+ this.message = message;
+ }
+}
+
+/**
+ * Error code indicating some error other than those enumerated here.
+ * @property OTHER_CAUSE
+ * @static
+ * @final
+ */
+ParseError.OTHER_CAUSE = -1;
+
+/**
+ * Error code indicating that something has gone wrong with the server.
+ * If you get this error code, it is Parse's fault. Contact us at
+ * https://parse.com/help
+ * @property INTERNAL_SERVER_ERROR
+ * @static
+ * @final
+ */
+ParseError.INTERNAL_SERVER_ERROR = 1;
+
+/**
+ * Error code indicating the connection to the Parse servers failed.
+ * @property CONNECTION_FAILED
+ * @static
+ * @final
+ */
+ParseError.CONNECTION_FAILED = 100;
+
+/**
+ * Error code indicating the specified object doesn't exist.
+ * @property OBJECT_NOT_FOUND
+ * @static
+ * @final
+ */
+ParseError.OBJECT_NOT_FOUND = 101;
+
+/**
+ * Error code indicating you tried to query with a datatype that doesn't
+ * support it, like exact matching an array or object.
+ * @property INVALID_QUERY
+ * @static
+ * @final
+ */
+ParseError.INVALID_QUERY = 102;
+
+/**
+ * Error code indicating a missing or invalid classname. Classnames are
+ * case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the
+ * only valid characters.
+ * @property INVALID_CLASS_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_CLASS_NAME = 103;
+
+/**
+ * Error code indicating an unspecified object id.
+ * @property MISSING_OBJECT_ID
+ * @static
+ * @final
+ */
+ParseError.MISSING_OBJECT_ID = 104;
+
+/**
+ * Error code indicating an invalid key name. Keys are case-sensitive. They
+ * must start with a letter, and a-zA-Z0-9_ are the only valid characters.
+ * @property INVALID_KEY_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_KEY_NAME = 105;
+
+/**
+ * Error code indicating a malformed pointer. You should not see this unless
+ * you have been mucking about changing internal Parse code.
+ * @property INVALID_POINTER
+ * @static
+ * @final
+ */
+ParseError.INVALID_POINTER = 106;
+
+/**
+ * Error code indicating that badly formed JSON was received upstream. This
+ * either indicates you have done something unusual with modifying how
+ * things encode to JSON, or the network is failing badly.
+ * @property INVALID_JSON
+ * @static
+ * @final
+ */
+ParseError.INVALID_JSON = 107;
+
+/**
+ * Error code indicating that the feature you tried to access is only
+ * available internally for testing purposes.
+ * @property COMMAND_UNAVAILABLE
+ * @static
+ * @final
+ */
+ParseError.COMMAND_UNAVAILABLE = 108;
+
+/**
+ * You must call Parse.initialize before using the Parse library.
+ * @property NOT_INITIALIZED
+ * @static
+ * @final
+ */
+ParseError.NOT_INITIALIZED = 109;
+
+/**
+ * Error code indicating that a field was set to an inconsistent type.
+ * @property INCORRECT_TYPE
+ * @static
+ * @final
+ */
+ParseError.INCORRECT_TYPE = 111;
+
+/**
+ * Error code indicating an invalid channel name. A channel name is either
+ * an empty string (the broadcast channel) or contains only a-zA-Z0-9_
+ * characters and starts with a letter.
+ * @property INVALID_CHANNEL_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_CHANNEL_NAME = 112;
+
+/**
+ * Error code indicating that push is misconfigured.
+ * @property PUSH_MISCONFIGURED
+ * @static
+ * @final
+ */
+ParseError.PUSH_MISCONFIGURED = 115;
+
+/**
+ * Error code indicating that the object is too large.
+ * @property OBJECT_TOO_LARGE
+ * @static
+ * @final
+ */
+ParseError.OBJECT_TOO_LARGE = 116;
+
+/**
+ * Error code indicating that the operation isn't allowed for clients.
+ * @property OPERATION_FORBIDDEN
+ * @static
+ * @final
+ */
+ParseError.OPERATION_FORBIDDEN = 119;
+
+/**
+ * Error code indicating the result was not found in the cache.
+ * @property CACHE_MISS
+ * @static
+ * @final
+ */
+ParseError.CACHE_MISS = 120;
+
+/**
+ * Error code indicating that an invalid key was used in a nested
+ * JSONObject.
+ * @property INVALID_NESTED_KEY
+ * @static
+ * @final
+ */
+ParseError.INVALID_NESTED_KEY = 121;
+
+/**
+ * Error code indicating that an invalid filename was used for ParseFile.
+ * A valid file name contains only a-zA-Z0-9_. characters and is between 1
+ * and 128 characters.
+ * @property INVALID_FILE_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_FILE_NAME = 122;
+
+/**
+ * Error code indicating an invalid ACL was provided.
+ * @property INVALID_ACL
+ * @static
+ * @final
+ */
+ParseError.INVALID_ACL = 123;
+
+/**
+ * Error code indicating that the request timed out on the server. Typically
+ * this indicates that the request is too expensive to run.
+ * @property TIMEOUT
+ * @static
+ * @final
+ */
+ParseError.TIMEOUT = 124;
+
+/**
+ * Error code indicating that the email address was invalid.
+ * @property INVALID_EMAIL_ADDRESS
+ * @static
+ * @final
+ */
+ParseError.INVALID_EMAIL_ADDRESS = 125;
+
+/**
+ * Error code indicating a missing content type.
+ * @property MISSING_CONTENT_TYPE
+ * @static
+ * @final
+ */
+ParseError.MISSING_CONTENT_TYPE = 126;
+
+/**
+ * Error code indicating a missing content length.
+ * @property MISSING_CONTENT_LENGTH
+ * @static
+ * @final
+ */
+ParseError.MISSING_CONTENT_LENGTH = 127;
+
+/**
+ * Error code indicating an invalid content length.
+ * @property INVALID_CONTENT_LENGTH
+ * @static
+ * @final
+ */
+ParseError.INVALID_CONTENT_LENGTH = 128;
+
+/**
+ * Error code indicating a file that was too large.
+ * @property FILE_TOO_LARGE
+ * @static
+ * @final
+ */
+ParseError.FILE_TOO_LARGE = 129;
+
+/**
+ * Error code indicating an error saving a file.
+ * @property FILE_SAVE_ERROR
+ * @static
+ * @final
+ */
+ParseError.FILE_SAVE_ERROR = 130;
+
+/**
+ * Error code indicating that a unique field was given a value that is
+ * already taken.
+ * @property DUPLICATE_VALUE
+ * @static
+ * @final
+ */
+ParseError.DUPLICATE_VALUE = 137;
+
+/**
+ * Error code indicating that a role's name is invalid.
+ * @property INVALID_ROLE_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_ROLE_NAME = 139;
+
+/**
+ * Error code indicating that an application quota was exceeded. Upgrade to
+ * resolve.
+ * @property EXCEEDED_QUOTA
+ * @static
+ * @final
+ */
+ParseError.EXCEEDED_QUOTA = 140;
+
+/**
+ * Error code indicating that a Cloud Code script failed.
+ * @property SCRIPT_FAILED
+ * @static
+ * @final
+ */
+ParseError.SCRIPT_FAILED = 141;
+
+/**
+ * Error code indicating that a Cloud Code validation failed.
+ * @property VALIDATION_ERROR
+ * @static
+ * @final
+ */
+ParseError.VALIDATION_ERROR = 142;
+
+/**
+ * Error code indicating that invalid image data was provided.
+ * @property INVALID_IMAGE_DATA
+ * @static
+ * @final
+ */
+ParseError.INVALID_IMAGE_DATA = 143;
+
+/**
+ * Error code indicating an unsaved file.
+ * @property UNSAVED_FILE_ERROR
+ * @static
+ * @final
+ */
+ParseError.UNSAVED_FILE_ERROR = 151;
+
+/**
+ * Error code indicating an invalid push time.
+ * @property INVALID_PUSH_TIME_ERROR
+ * @static
+ * @final
+ */
+ParseError.INVALID_PUSH_TIME_ERROR = 152;
+
+/**
+ * Error code indicating an error deleting a file.
+ * @property FILE_DELETE_ERROR
+ * @static
+ * @final
+ */
+ParseError.FILE_DELETE_ERROR = 153;
+
+/**
+ * Error code indicating that the application has exceeded its request
+ * limit.
+ * @property REQUEST_LIMIT_EXCEEDED
+ * @static
+ * @final
+ */
+ParseError.REQUEST_LIMIT_EXCEEDED = 155;
+
+/**
+ * Error code indicating an invalid event name.
+ * @property INVALID_EVENT_NAME
+ * @static
+ * @final
+ */
+ParseError.INVALID_EVENT_NAME = 160;
+
+/**
+ * Error code indicating that the username is missing or empty.
+ * @property USERNAME_MISSING
+ * @static
+ * @final
+ */
+ParseError.USERNAME_MISSING = 200;
+
+/**
+ * Error code indicating that the password is missing or empty.
+ * @property PASSWORD_MISSING
+ * @static
+ * @final
+ */
+ParseError.PASSWORD_MISSING = 201;
+
+/**
+ * Error code indicating that the username has already been taken.
+ * @property USERNAME_TAKEN
+ * @static
+ * @final
+ */
+ParseError.USERNAME_TAKEN = 202;
+
+/**
+ * Error code indicating that the email has already been taken.
+ * @property EMAIL_TAKEN
+ * @static
+ * @final
+ */
+ParseError.EMAIL_TAKEN = 203;
+
+/**
+ * Error code indicating that the email is missing, but must be specified.
+ * @property EMAIL_MISSING
+ * @static
+ * @final
+ */
+ParseError.EMAIL_MISSING = 204;
+
+/**
+ * Error code indicating that a user with the specified email was not found.
+ * @property EMAIL_NOT_FOUND
+ * @static
+ * @final
+ */
+ParseError.EMAIL_NOT_FOUND = 205;
+
+/**
+ * Error code indicating that a user object without a valid session could
+ * not be altered.
+ * @property SESSION_MISSING
+ * @static
+ * @final
+ */
+ParseError.SESSION_MISSING = 206;
+
+/**
+ * Error code indicating that a user can only be created through signup.
+ * @property MUST_CREATE_USER_THROUGH_SIGNUP
+ * @static
+ * @final
+ */
+ParseError.MUST_CREATE_USER_THROUGH_SIGNUP = 207;
+
+/**
+ * Error code indicating that an an account being linked is already linked
+ * to another user.
+ * @property ACCOUNT_ALREADY_LINKED
+ * @static
+ * @final
+ */
+ParseError.ACCOUNT_ALREADY_LINKED = 208;
+
+/**
+ * Error code indicating that the current session token is invalid.
+ * @property INVALID_SESSION_TOKEN
+ * @static
+ * @final
+ */
+ParseError.INVALID_SESSION_TOKEN = 209;
+
+/**
+ * Error code indicating that a user cannot be linked to an account because
+ * that account's id could not be found.
+ * @property LINKED_ID_MISSING
+ * @static
+ * @final
+ */
+ParseError.LINKED_ID_MISSING = 250;
+
+/**
+ * Error code indicating that a user with a linked (e.g. Facebook) account
+ * has an invalid session.
+ * @property INVALID_LINKED_SESSION
+ * @static
+ * @final
+ */
+ParseError.INVALID_LINKED_SESSION = 251;
+
+/**
+ * Error code indicating that a service being linked (e.g. Facebook or
+ * Twitter) is unsupported.
+ * @property UNSUPPORTED_SERVICE
+ * @static
+ * @final
+ */
+ParseError.UNSUPPORTED_SERVICE = 252;
+
+/**
+ * Error code indicating that there were multiple errors. Aggregate errors
+ * have an "errors" property, which is an array of error objects with more
+ * detail about each error that occurred.
+ * @property AGGREGATE_ERROR
+ * @static
+ * @final
+ */
+ParseError.AGGREGATE_ERROR = 600;
+
+/**
+ * Error code indicating the client was unable to read an input file.
+ * @property FILE_READ_ERROR
+ * @static
+ * @final
+ */
+ParseError.FILE_READ_ERROR = 601;
+
+/**
+ * Error code indicating a real error code is unavailable because
+ * we had to use an XDomainRequest object to allow CORS requests in
+ * Internet Explorer, which strips the body from HTTP responses that have
+ * a non-2XX status code.
+ * @property X_DOMAIN_REQUEST
+ * @static
+ * @final
+ */
+ParseError.X_DOMAIN_REQUEST = 602;
\ No newline at end of file
diff --git a/lib/react-native/ParseFile.js b/lib/react-native/ParseFile.js
new file mode 100644
index 000000000..0b1b448a8
--- /dev/null
+++ b/lib/react-native/ParseFile.js
@@ -0,0 +1,247 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+import CoreManager from './CoreManager';
+import ParsePromise from './ParsePromise';
+
+var dataUriRegexp = /^data:([a-zA-Z]*\/[a-zA-Z+.-]*);(charset=[a-zA-Z0-9\-\/\s]*,)?base64,/;
+
+function b64Digit(number) {
+ if (number < 26) {
+ return String.fromCharCode(65 + number);
+ }
+ if (number < 52) {
+ return String.fromCharCode(97 + (number - 26));
+ }
+ if (number < 62) {
+ return String.fromCharCode(48 + (number - 52));
+ }
+ if (number === 62) {
+ return '+';
+ }
+ if (number === 63) {
+ return '/';
+ }
+ throw new TypeError('Tried to encode large digit ' + number + ' in base64.');
+}
+
+/**
+ * A Parse.File is a local representation of a file that is saved to the Parse
+ * cloud.
+ * @class Parse.File
+ * @constructor
+ * @param name {String} The file's name. This will be prefixed by a unique
+ * value once the file has finished saving. The file name must begin with
+ * an alphanumeric character, and consist of alphanumeric characters,
+ * periods, spaces, underscores, or dashes.
+ * @param data {Array} The data for the file, as either:
+ * 1. an Array of byte value Numbers, or
+ * 2. an Object like { base64: "..." } with a base64-encoded String.
+ * 3. a File object selected with a file upload control. (3) only works
+ * in Firefox 3.6+, Safari 6.0.2+, Chrome 7+, and IE 10+.
+ * For example:
+ * var fileUploadControl = $("#profilePhotoFileUpload")[0];
+ * if (fileUploadControl.files.length > 0) {
+ * var file = fileUploadControl.files[0];
+ * var name = "photo.jpg";
+ * var parseFile = new Parse.File(name, file);
+ * parseFile.save().then(function() {
+ * // The file has been saved to Parse.
+ * }, function(error) {
+ * // The file either could not be read, or could not be saved to Parse.
+ * });
+ * }
+ * @param type {String} Optional Content-Type header to use for the file. If
+ * this is omitted, the content type will be inferred from the name's
+ * extension.
+ */
+export default class ParseFile {
+
+ constructor(name, data, type) {
+ var specifiedType = type || '';
+
+ this._name = name;
+
+ if (data !== undefined) {
+ if (Array.isArray(data)) {
+ this._source = {
+ format: 'base64',
+ base64: ParseFile.encodeBase64(data),
+ type: specifiedType
+ };
+ } else if (typeof File !== 'undefined' && data instanceof File) {
+ this._source = {
+ format: 'file',
+ file: data,
+ type: specifiedType
+ };
+ } else if (data && typeof data.base64 === 'string') {
+ const base64 = data.base64;
+ var commaIndex = base64.indexOf(',');
+
+ if (commaIndex !== -1) {
+ var matches = dataUriRegexp.exec(base64.slice(0, commaIndex + 1));
+ // if data URI with type and charset, there will be 4 matches.
+ this._source = {
+ format: 'base64',
+ base64: base64.slice(commaIndex + 1),
+ type: matches[1]
+ };
+ } else {
+ this._source = {
+ format: 'base64',
+ base64: base64,
+ type: specifiedType
+ };
+ }
+ } else {
+ throw new TypeError('Cannot create a Parse.File with that data.');
+ }
+ }
+ }
+
+ /**
+ * Gets the name of the file. Before save is called, this is the filename
+ * given by the user. After save is called, that name gets prefixed with a
+ * unique identifier.
+ * @method name
+ * @return {String}
+ */
+ name() {
+ return this._name;
+ }
+
+ /**
+ * Gets the url of the file. It is only available after you save the file or
+ * after you get the file from a Parse.Object.
+ * @method url
+ * @param {Object} options An object to specify url options
+ * @return {String}
+ */
+ url(options) {
+ options = options || {};
+ if (!this._url) {
+ return;
+ }
+ if (options.forceSecure) {
+ return this._url.replace(/^http:\/\//i, 'https://');
+ } else {
+ return this._url;
+ }
+ }
+
+ /**
+ * Saves the file to the Parse cloud.
+ * @method save
+ * @param {Object} options A Backbone-style options object.
+ * @return {Parse.Promise} Promise that is resolved when the save finishes.
+ */
+ save(options) {
+ options = options || {};
+ var controller = CoreManager.getFileController();
+ if (!this._previousSave) {
+ if (this._source.format === 'file') {
+ this._previousSave = controller.saveFile(this._name, this._source).then(res => {
+ this._name = res.name;
+ this._url = res.url;
+ return this;
+ });
+ } else {
+ this._previousSave = controller.saveBase64(this._name, this._source).then(res => {
+ this._name = res.name;
+ this._url = res.url;
+ return this;
+ });
+ }
+ }
+ if (this._previousSave) {
+ return this._previousSave._thenRunCallbacks(options);
+ }
+ }
+
+ toJSON() {
+ return {
+ __type: 'File',
+ name: this._name,
+ url: this._url
+ };
+ }
+
+ equals(other) {
+ if (this === other) {
+ return true;
+ }
+ // Unsaved Files are never equal, since they will be saved to different URLs
+ return other instanceof ParseFile && this.name() === other.name() && this.url() === other.url() && typeof this.url() !== 'undefined';
+ }
+
+ static fromJSON(obj) {
+ if (obj.__type !== 'File') {
+ throw new TypeError('JSON object does not represent a ParseFile');
+ }
+ var file = new ParseFile(obj.name);
+ file._url = obj.url;
+ return file;
+ }
+
+ static encodeBase64(bytes) {
+ var chunks = [];
+ chunks.length = Math.ceil(bytes.length / 3);
+ for (var i = 0; i < chunks.length; i++) {
+ var b1 = bytes[i * 3];
+ var b2 = bytes[i * 3 + 1] || 0;
+ var b3 = bytes[i * 3 + 2] || 0;
+
+ var has2 = i * 3 + 1 < bytes.length;
+ var has3 = i * 3 + 2 < bytes.length;
+
+ chunks[i] = [b64Digit(b1 >> 2 & 0x3F), b64Digit(b1 << 4 & 0x30 | b2 >> 4 & 0x0F), has2 ? b64Digit(b2 << 2 & 0x3C | b3 >> 6 & 0x03) : '=', has3 ? b64Digit(b3 & 0x3F) : '='].join('');
+ }
+
+ return chunks.join('');
+ }
+}
+
+var DefaultController = {
+ saveFile: function (name, source) {
+ if (source.format !== 'file') {
+ throw new Error('saveFile can only be used with File-type sources.');
+ }
+ // To directly upload a File, we use a REST-style AJAX request
+ var headers = {
+ 'X-Parse-Application-ID': CoreManager.get('APPLICATION_ID'),
+ 'X-Parse-JavaScript-Key': CoreManager.get('JAVASCRIPT_KEY'),
+ 'Content-Type': source.type || (source.file ? source.file.type : null)
+ };
+ var url = CoreManager.get('SERVER_URL');
+ if (url[url.length - 1] !== '/') {
+ url += '/';
+ }
+ url += 'files/' + name;
+ return CoreManager.getRESTController().ajax('POST', url, source.file, headers);
+ },
+
+ saveBase64: function (name, source) {
+ if (source.format !== 'base64') {
+ throw new Error('saveBase64 can only be used with Base64-type sources.');
+ }
+ var data = {
+ base64: source.base64
+ };
+ if (source.type) {
+ data._ContentType = source.type;
+ }
+
+ return CoreManager.getRESTController().request('POST', 'files/' + name, data);
+ }
+};
+
+CoreManager.setFileController(DefaultController);
\ No newline at end of file
diff --git a/lib/react-native/ParseGeoPoint.js b/lib/react-native/ParseGeoPoint.js
new file mode 100644
index 000000000..e6dcddf43
--- /dev/null
+++ b/lib/react-native/ParseGeoPoint.js
@@ -0,0 +1,186 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+import ParsePromise from './ParsePromise';
+
+/**
+ * Creates a new GeoPoint with any of the following forms:
+ * new GeoPoint(otherGeoPoint)
+ * new GeoPoint(30, 30)
+ * new GeoPoint([30, 30])
+ * new GeoPoint({latitude: 30, longitude: 30})
+ * new GeoPoint() // defaults to (0, 0)
+ *
+ * @class Parse.GeoPoint
+ * @constructor
+ *
+ * Represents a latitude / longitude point that may be associated + * with a key in a ParseObject or used as a reference point for geo queries. + * This allows proximity-based queries on the key.
+ * + *Only one key in a class may contain a GeoPoint.
+ * + *Example:
+ * var point = new Parse.GeoPoint(30.0, -20.0);
+ * var object = new Parse.Object("PlaceObject");
+ * object.set("location", point);
+ * object.save();
+ */
+export default class ParseGeoPoint {
+
+ constructor(arg1, arg2) {
+ if (Array.isArray(arg1)) {
+ ParseGeoPoint._validate(arg1[0], arg1[1]);
+ this._latitude = arg1[0];
+ this._longitude = arg1[1];
+ } else if (typeof arg1 === 'object') {
+ ParseGeoPoint._validate(arg1.latitude, arg1.longitude);
+ this._latitude = arg1.latitude;
+ this._longitude = arg1.longitude;
+ } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
+ ParseGeoPoint._validate(arg1, arg2);
+ this._latitude = arg1;
+ this._longitude = arg2;
+ } else {
+ this._latitude = 0;
+ this._longitude = 0;
+ }
+ }
+
+ /**
+ * North-south portion of the coordinate, in range [-90, 90].
+ * Throws an exception if set out of range in a modern browser.
+ * @property latitude
+ * @type Number
+ */
+ get latitude() {
+ return this._latitude;
+ }
+
+ set latitude(val) {
+ ParseGeoPoint._validate(val, this.longitude);
+ this._latitude = val;
+ }
+
+ /**
+ * East-west portion of the coordinate, in range [-180, 180].
+ * Throws if set out of range in a modern browser.
+ * @property longitude
+ * @type Number
+ */
+ get longitude() {
+ return this._longitude;
+ }
+
+ set longitude(val) {
+ ParseGeoPoint._validate(this.latitude, val);
+ this._longitude = val;
+ }
+
+ /**
+ * Returns a JSON representation of the GeoPoint, suitable for Parse.
+ * @method toJSON
+ * @return {Object}
+ */
+ toJSON() {
+ ParseGeoPoint._validate(this._latitude, this._longitude);
+ return {
+ __type: 'GeoPoint',
+ latitude: this._latitude,
+ longitude: this._longitude
+ };
+ }
+
+ equals(other) {
+ return other instanceof ParseGeoPoint && this.latitude === other.latitude && this.longitude === other.longitude;
+ }
+
+ /**
+ * Returns the distance from this GeoPoint to another in radians.
+ * @method radiansTo
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+ radiansTo(point) {
+ var d2r = Math.PI / 180.0;
+ var lat1rad = this.latitude * d2r;
+ var long1rad = this.longitude * d2r;
+ var lat2rad = point.latitude * d2r;
+ var long2rad = point.longitude * d2r;
+
+ var sinDeltaLatDiv2 = Math.sin((lat1rad - lat2rad) / 2);
+ var sinDeltaLongDiv2 = Math.sin((long1rad - long2rad) / 2);
+ // Square of half the straight line chord distance between both points.
+ var a = sinDeltaLatDiv2 * sinDeltaLatDiv2 + Math.cos(lat1rad) * Math.cos(lat2rad) * sinDeltaLongDiv2 * sinDeltaLongDiv2;
+ a = Math.min(1.0, a);
+ return 2 * Math.asin(Math.sqrt(a));
+ }
+
+ /**
+ * Returns the distance from this GeoPoint to another in kilometers.
+ * @method kilometersTo
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+ kilometersTo(point) {
+ return this.radiansTo(point) * 6371.0;
+ }
+
+ /**
+ * Returns the distance from this GeoPoint to another in miles.
+ * @method milesTo
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+ milesTo(point) {
+ return this.radiansTo(point) * 3958.8;
+ }
+
+ /**
+ * Throws an exception if the given lat-long is out of bounds.
+ */
+ static _validate(latitude, longitude) {
+ if (latitude !== latitude || longitude !== longitude) {
+ throw new TypeError('GeoPoint latitude and longitude must be valid numbers');
+ }
+ if (latitude < -90.0) {
+ throw new TypeError('GeoPoint latitude out of bounds: ' + latitude + ' < -90.0.');
+ }
+ if (latitude > 90.0) {
+ throw new TypeError('GeoPoint latitude out of bounds: ' + latitude + ' > 90.0.');
+ }
+ if (longitude < -180.0) {
+ throw new TypeError('GeoPoint longitude out of bounds: ' + longitude + ' < -180.0.');
+ }
+ if (longitude > 180.0) {
+ throw new TypeError('GeoPoint longitude out of bounds: ' + longitude + ' > 180.0.');
+ }
+ }
+
+ /**
+ * Creates a GeoPoint with the user's current location, if available.
+ * Calls options.success with a new GeoPoint instance or calls options.error.
+ * @method current
+ * @param {Object} options An object with success and error callbacks.
+ * @static
+ */
+ static current(options) {
+ var promise = new ParsePromise();
+ navigator.geolocation.getCurrentPosition(function (location) {
+ promise.resolve(new ParseGeoPoint(location.coords.latitude, location.coords.longitude));
+ }, function (error) {
+ promise.reject(error);
+ });
+
+ return promise._thenRunCallbacks(options);
+ }
+}
\ No newline at end of file
diff --git a/lib/react-native/ParseHooks.js b/lib/react-native/ParseHooks.js
new file mode 100644
index 000000000..e079fcd51
--- /dev/null
+++ b/lib/react-native/ParseHooks.js
@@ -0,0 +1,125 @@
+import CoreManager from './CoreManager';
+import decode from './decode';
+import encode from './encode';
+import ParseError from './ParseError';
+import ParsePromise from './ParsePromise';
+
+export function getFunctions() {
+ return CoreManager.getHooksController().get("functions");
+}
+
+export function getTriggers() {
+ return CoreManager.getHooksController().get("triggers");
+}
+
+export function getFunction(name) {
+ return CoreManager.getHooksController().get("functions", name);
+}
+
+export function getTrigger(className, triggerName) {
+ return CoreManager.getHooksController().get("triggers", className, triggerName);
+}
+
+export function createFunction(functionName, url) {
+ return create({ functionName: functionName, url: url });
+}
+
+export function createTrigger(className, triggerName, url) {
+ return create({ className: className, triggerName: triggerName, url: url });
+}
+
+export function create(hook) {
+ return CoreManager.getHooksController().create(hook);
+}
+
+export function updateFunction(functionName, url) {
+ return update({ functionName: functionName, url: url });
+}
+
+export function updateTrigger(className, triggerName, url) {
+ return update({ className: className, triggerName: triggerName, url: url });
+}
+
+export function update(hook) {
+ return CoreManager.getHooksController().update(hook);
+}
+
+export function removeFunction(functionName) {
+ return remove({ functionName: functionName });
+}
+
+export function removeTrigger(className, triggerName) {
+ return remove({ className: className, triggerName: triggerName });
+}
+
+export function remove(hook) {
+ return CoreManager.getHooksController().remove(hook);
+}
+
+var DefaultController = {
+
+ get(type, functionName, triggerName) {
+ var url = "/hooks/" + type;
+ if (functionName) {
+ url += "/" + functionName;
+ if (triggerName) {
+ url += "/" + triggerName;
+ }
+ }
+ return this.sendRequest("GET", url);
+ },
+
+ create(hook) {
+ var url;
+ if (hook.functionName && hook.url) {
+ url = "/hooks/functions";
+ } else if (hook.className && hook.triggerName && hook.url) {
+ url = "/hooks/triggers";
+ } else {
+ return Promise.reject({ error: 'invalid hook declaration', code: 143 });
+ }
+ return this.sendRequest("POST", url, hook);
+ },
+
+ remove(hook) {
+ var url;
+ if (hook.functionName) {
+ url = "/hooks/functions/" + hook.functionName;
+ delete hook.functionName;
+ } else if (hook.className && hook.triggerName) {
+ url = "/hooks/triggers/" + hook.className + "/" + hook.triggerName;
+ delete hook.className;
+ delete hook.triggerName;
+ } else {
+ return Promise.reject({ error: 'invalid hook declaration', code: 143 });
+ }
+ return this.sendRequest("PUT", url, { "__op": "Delete" });
+ },
+
+ update(hook) {
+ var url;
+ if (hook.functionName && hook.url) {
+ url = "/hooks/functions/" + hook.functionName;
+ delete hook.functionName;
+ } else if (hook.className && hook.triggerName && hook.url) {
+ url = "/hooks/triggers/" + hook.className + "/" + hook.triggerName;
+ delete hook.className;
+ delete hook.triggerName;
+ } else {
+ return Promise.reject({ error: 'invalid hook declaration', code: 143 });
+ }
+ return this.sendRequest('PUT', url, hook);
+ },
+
+ sendRequest(method, url, body) {
+ return CoreManager.getRESTController().request(method, url, body, { useMasterKey: true }).then(res => {
+ var decoded = decode(res);
+ if (decoded) {
+ return ParsePromise.as(decoded);
+ }
+ return ParsePromise.error(new ParseError(ParseError.INVALID_JSON, 'The server returned an invalid response.'));
+ });
+ }
+};
+
+CoreManager.setHooksController(DefaultController);
\ No newline at end of file
diff --git a/lib/react-native/ParseInstallation.js b/lib/react-native/ParseInstallation.js
new file mode 100644
index 000000000..fd29a3f91
--- /dev/null
+++ b/lib/react-native/ParseInstallation.js
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+import ParseObject from './ParseObject';
+
+export default class Installation extends ParseObject {
+ constructor(attributes) {
+ super('_Installation');
+ if (attributes && typeof attributes === 'object') {
+ if (!this.set(attributes || {})) {
+ throw new Error('Can\'t create an invalid Session');
+ }
+ }
+ }
+}
+
+ParseObject.registerSubclass('_Installation', Installation);
\ No newline at end of file
diff --git a/lib/react-native/ParseLiveQuery.js b/lib/react-native/ParseLiveQuery.js
new file mode 100644
index 000000000..3ad072a72
--- /dev/null
+++ b/lib/react-native/ParseLiveQuery.js
@@ -0,0 +1,212 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+import EventEmitter from './EventEmitter';
+import LiveQueryClient from './LiveQueryClient';
+import CoreManager from './CoreManager';
+import ParsePromise from './ParsePromise';
+
+function open() {
+ const LiveQueryController = CoreManager.getLiveQueryController();
+ LiveQueryController.open();
+}
+
+function close() {
+ const LiveQueryController = CoreManager.getLiveQueryController();
+ LiveQueryController.close();
+}
+
+/**
+ *
+ * We expose three events to help you monitor the status of the WebSocket connection:
+ *
+ * Open - When we establish the WebSocket connection to the LiveQuery server, you'll get this event. + * + *
+ * Parse.LiveQuery.on('open', () => {
+ *
+ * });
+ *
+ * Close - When we lose the WebSocket connection to the LiveQuery server, you'll get this event. + * + *
+ * Parse.LiveQuery.on('close', () => {
+ *
+ * });
+ *
+ * Error - When some network error or LiveQuery server error happens, you'll get this event. + * + *
+ * Parse.LiveQuery.on('error', (error) => {
+ *
+ * });
+ *
+ * @class Parse.LiveQuery
+ * @static
+ *
+ */
+let LiveQuery = new EventEmitter();
+
+/**
+ * After open is called, the LiveQuery will try to send a connect request
+ * to the LiveQuery server.
+ *
+ * @method open
+ */
+LiveQuery.open = open;
+
+/**
+ * When you're done using LiveQuery, you can call Parse.LiveQuery.close().
+ * This function will close the WebSocket connection to the LiveQuery server,
+ * cancel the auto reconnect, and unsubscribe all subscriptions based on it.
+ * If you call query.subscribe() after this, we'll create a new WebSocket
+ * connection to the LiveQuery server.
+ *
+ * @method close
+ */
+
+LiveQuery.close = close;
+// Register a default onError callback to make sure we do not crash on error
+LiveQuery.on('error', () => {});
+
+export default LiveQuery;
+
+function getSessionToken() {
+ const controller = CoreManager.getUserController();
+ return controller.currentUserAsync().then(currentUser => {
+ return currentUser ? currentUser.getSessionToken() : undefined;
+ });
+}
+
+function getLiveQueryClient() {
+ return CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
+}
+
+let defaultLiveQueryClient;
+const DefaultLiveQueryController = {
+ setDefaultLiveQueryClient(liveQueryClient) {
+ defaultLiveQueryClient = liveQueryClient;
+ },
+ getDefaultLiveQueryClient() {
+ if (defaultLiveQueryClient) {
+ return ParsePromise.as(defaultLiveQueryClient);
+ }
+
+ return getSessionToken().then(sessionToken => {
+ let liveQueryServerURL = CoreManager.get('LIVEQUERY_SERVER_URL');
+
+ if (liveQueryServerURL && liveQueryServerURL.indexOf('ws') !== 0) {
+ throw new Error('You need to set a proper Parse LiveQuery server url before using LiveQueryClient');
+ }
+
+ // If we can not find Parse.liveQueryServerURL, we try to extract it from Parse.serverURL
+ if (!liveQueryServerURL) {
+ const tempServerURL = CoreManager.get('SERVER_URL');
+ let protocol = 'ws://';
+ // If Parse is being served over SSL/HTTPS, ensure LiveQuery Server uses 'wss://' prefix
+ if (tempServerURL.indexOf('https') === 0) {
+ protocol = 'wss://';
+ }
+ const host = tempServerURL.replace(/^https?:\/\//, '');
+ liveQueryServerURL = protocol + host;
+ CoreManager.set('LIVEQUERY_SERVER_URL', liveQueryServerURL);
+ }
+
+ const applicationId = CoreManager.get('APPLICATION_ID');
+ const javascriptKey = CoreManager.get('JAVASCRIPT_KEY');
+ const masterKey = CoreManager.get('MASTER_KEY');
+ // Get currentUser sessionToken if possible
+ defaultLiveQueryClient = new LiveQueryClient({
+ applicationId,
+ serverURL: liveQueryServerURL,
+ javascriptKey,
+ masterKey,
+ sessionToken
+ });
+ // Register a default onError callback to make sure we do not crash on error
+ // Cannot create these events on a nested way because of EventEmiiter from React Native
+ defaultLiveQueryClient.on('error', error => {
+ LiveQuery.emit('error', error);
+ });
+ defaultLiveQueryClient.on('open', () => {
+ LiveQuery.emit('open');
+ });
+ defaultLiveQueryClient.on('close', () => {
+ LiveQuery.emit('close');
+ });
+
+ return defaultLiveQueryClient;
+ });
+ },
+ open() {
+ getLiveQueryClient().then(liveQueryClient => {
+ this.resolve(liveQueryClient.open());
+ });
+ },
+ close() {
+ getLiveQueryClient().then(liveQueryClient => {
+ this.resolve(liveQueryClient.close());
+ });
+ },
+ subscribe(query) {
+ let subscriptionWrap = new EventEmitter();
+
+ getLiveQueryClient().then(liveQueryClient => {
+ if (liveQueryClient.shouldOpen()) {
+ liveQueryClient.open();
+ }
+ let promiseSessionToken = getSessionToken();
+ // new event emitter
+ return promiseSessionToken.then(sessionToken => {
+
+ let subscription = liveQueryClient.subscribe(query, sessionToken);
+ // enter, leave create, etc
+
+ subscriptionWrap.id = subscription.id;
+ subscriptionWrap.query = subscription.query;
+ subscriptionWrap.sessionToken = subscription.sessionToken;
+ subscriptionWrap.unsubscribe = subscription.unsubscribe;
+ // Cannot create these events on a nested way because of EventEmiiter from React Native
+ subscription.on('open', () => {
+ subscriptionWrap.emit('open');
+ });
+ subscription.on('create', object => {
+ subscriptionWrap.emit('create', object);
+ });
+ subscription.on('update', object => {
+ subscriptionWrap.emit('update', object);
+ });
+ subscription.on('enter', object => {
+ subscriptionWrap.emit('enter', object);
+ });
+ subscription.on('leave', object => {
+ subscriptionWrap.emit('leave', object);
+ });
+ subscription.on('delete', object => {
+ subscriptionWrap.emit('delete', object);
+ });
+
+ this.resolve();
+ });
+ });
+ return subscriptionWrap;
+ },
+ unsubscribe(subscription) {
+ getLiveQueryClient().then(liveQueryClient => {
+ this.resolve(liveQueryClient.unsubscribe(subscription));
+ });
+ },
+ _clearCachedDefaultClient() {
+ defaultLiveQueryClient = null;
+ }
+};
+
+CoreManager.setLiveQueryController(DefaultLiveQueryController);
\ No newline at end of file
diff --git a/lib/react-native/ParseObject.js b/lib/react-native/ParseObject.js
new file mode 100644
index 000000000..e92c14e18
--- /dev/null
+++ b/lib/react-native/ParseObject.js
@@ -0,0 +1,1742 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+import CoreManager from './CoreManager';
+import canBeSerialized from './canBeSerialized';
+import decode from './decode';
+import encode from './encode';
+import equals from './equals';
+import escape from './escape';
+import ParseACL from './ParseACL';
+import parseDate from './parseDate';
+import ParseError from './ParseError';
+import ParseFile from './ParseFile';
+import { opFromJSON, Op, SetOp, UnsetOp, IncrementOp, AddOp, AddUniqueOp, RemoveOp, RelationOp } from './ParseOp';
+import ParsePromise from './ParsePromise';
+import ParseQuery from './ParseQuery';
+import ParseRelation from './ParseRelation';
+import * as SingleInstanceStateController from './SingleInstanceStateController';
+import unique from './unique';
+import * as UniqueInstanceStateController from './UniqueInstanceStateController';
+import unsavedChildren from './unsavedChildren';
+
+// Mapping of class names to constructors, so we can populate objects from the
+// server with appropriate subclasses of ParseObject
+var classMap = {};
+
+// Global counter for generating unique local Ids
+var localCount = 0;
+// Global counter for generating unique Ids for non-single-instance objects
+var objectCount = 0;
+// On web clients, objects are single-instance: any two objects with the same Id
+// will have the same attributes. However, this may be dangerous default
+// behavior in a server scenario
+var singleInstance = !CoreManager.get('IS_NODE');
+if (singleInstance) {
+ CoreManager.setObjectStateController(SingleInstanceStateController);
+} else {
+ CoreManager.setObjectStateController(UniqueInstanceStateController);
+}
+
+function getServerUrlPath() {
+ var serverUrl = CoreManager.get('SERVER_URL');
+ if (serverUrl[serverUrl.length - 1] !== '/') {
+ serverUrl += '/';
+ }
+ var url = serverUrl.replace(/https?:\/\//, '');
+ return url.substr(url.indexOf('/'));
+}
+
+/**
+ * Creates a new model with defined attributes.
+ *
+ * You won't normally call this method directly. It is recommended that
+ * you use a subclass of Parse.Object instead, created by calling
+ * extend.
However, if you don't want to use a subclass, or aren't sure which + * subclass is appropriate, you can use this form:
+ * var object = new Parse.Object("ClassName");
+ *
+ * That is basically equivalent to:
+ * var MyClass = Parse.Object.extend("ClassName");
+ * var object = new MyClass();
+ *
+ *
+ * @class Parse.Object
+ * @constructor
+ * @param {String} className The class name for the object
+ * @param {Object} attributes The initial set of data to store in the object.
+ * @param {Object} options The options for this object instance.
+ */
+export default class ParseObject {
+ /**
+ * The ID of this object, unique within its class.
+ * @property id
+ * @type String
+ */
+ constructor(className, attributes, options) {
+ // Enable legacy initializers
+ if (typeof this.initialize === 'function') {
+ this.initialize.apply(this, arguments);
+ }
+
+ var toSet = null;
+ this._objCount = objectCount++;
+ if (typeof className === 'string') {
+ this.className = className;
+ if (attributes && typeof attributes === 'object') {
+ toSet = attributes;
+ }
+ } else if (className && typeof className === 'object') {
+ this.className = className.className;
+ toSet = {};
+ for (var attr in className) {
+ if (attr !== 'className') {
+ toSet[attr] = className[attr];
+ }
+ }
+ if (attributes && typeof attributes === 'object') {
+ options = attributes;
+ }
+ }
+ if (toSet && !this.set(toSet, options)) {
+ throw new Error('Can\'t create an invalid Parse Object');
+ }
+ }
+
+ /** Prototype getters / setters **/
+
+ get attributes() {
+ let stateController = CoreManager.getObjectStateController();
+ return Object.freeze(stateController.estimateAttributes(this._getStateIdentifier()));
+ }
+
+ /**
+ * The first time this object was saved on the server.
+ * @property createdAt
+ * @type Date
+ */
+ get createdAt() {
+ return this._getServerData().createdAt;
+ }
+
+ /**
+ * The last time this object was updated on the server.
+ * @property updatedAt
+ * @type Date
+ */
+ get updatedAt() {
+ return this._getServerData().updatedAt;
+ }
+
+ /** Private methods **/
+
+ /**
+ * Returns a local or server Id used uniquely identify this object
+ */
+ _getId() {
+ if (typeof this.id === 'string') {
+ return this.id;
+ }
+ if (typeof this._localId === 'string') {
+ return this._localId;
+ }
+ var localId = 'local' + String(localCount++);
+ this._localId = localId;
+ return localId;
+ }
+
+ /**
+ * Returns a unique identifier used to pull data from the State Controller.
+ */
+ _getStateIdentifier() {
+ if (singleInstance) {
+ let id = this.id;
+ if (!id) {
+ id = this._getId();
+ }
+ return {
+ id: id,
+ className: this.className
+ };
+ } else {
+ return this;
+ }
+ }
+
+ _getServerData() {
+ let stateController = CoreManager.getObjectStateController();
+ return stateController.getServerData(this._getStateIdentifier());
+ }
+
+ _clearServerData() {
+ var serverData = this._getServerData();
+ var unset = {};
+ for (var attr in serverData) {
+ unset[attr] = undefined;
+ }
+ let stateController = CoreManager.getObjectStateController();
+ stateController.setServerData(this._getStateIdentifier(), unset);
+ }
+
+ _getPendingOps() {
+ let stateController = CoreManager.getObjectStateController();
+ return stateController.getPendingOps(this._getStateIdentifier());
+ }
+
+ _clearPendingOps() {
+ var pending = this._getPendingOps();
+ var latest = pending[pending.length - 1];
+ var keys = Object.keys(latest);
+ keys.forEach(key => {
+ delete latest[key];
+ });
+ }
+
+ _getDirtyObjectAttributes() {
+ var attributes = this.attributes;
+ var stateController = CoreManager.getObjectStateController();
+ var objectCache = stateController.getObjectCache(this._getStateIdentifier());
+ var dirty = {};
+ for (var attr in attributes) {
+ var val = attributes[attr];
+ if (val && typeof val === 'object' && !(val instanceof ParseObject) && !(val instanceof ParseFile) && !(val instanceof ParseRelation)) {
+ // Due to the way browsers construct maps, the key order will not change
+ // unless the object is changed
+ try {
+ var json = encode(val, false, true);
+ var stringified = JSON.stringify(json);
+ if (objectCache[attr] !== stringified) {
+ dirty[attr] = val;
+ }
+ } catch (e) {
+ // Error occurred, possibly by a nested unsaved pointer in a mutable container
+ // No matter how it happened, it indicates a change in the attribute
+ dirty[attr] = val;
+ }
+ }
+ }
+ return dirty;
+ }
+
+ _toFullJSON(seen) {
+ var json = this.toJSON(seen);
+ json.__type = 'Object';
+ json.className = this.className;
+ return json;
+ }
+
+ _getSaveJSON() {
+ var pending = this._getPendingOps();
+ var dirtyObjects = this._getDirtyObjectAttributes();
+ var json = {};
+
+ for (var attr in dirtyObjects) {
+ json[attr] = new SetOp(dirtyObjects[attr]).toJSON();
+ }
+ for (attr in pending[0]) {
+ json[attr] = pending[0][attr].toJSON();
+ }
+ return json;
+ }
+
+ _getSaveParams() {
+ var method = this.id ? 'PUT' : 'POST';
+ var body = this._getSaveJSON();
+ var path = 'classes/' + this.className;
+ if (this.id) {
+ path += '/' + this.id;
+ } else if (this.className === '_User') {
+ path = 'users';
+ }
+ return {
+ method,
+ body,
+ path
+ };
+ }
+
+ _finishFetch(serverData) {
+ if (!this.id && serverData.objectId) {
+ this.id = serverData.objectId;
+ }
+ let stateController = CoreManager.getObjectStateController();
+ stateController.initializeState(this._getStateIdentifier());
+ var decoded = {};
+ for (var attr in serverData) {
+ if (attr === 'ACL') {
+ decoded[attr] = new ParseACL(serverData[attr]);
+ } else if (attr !== 'objectId') {
+ decoded[attr] = decode(serverData[attr]);
+ if (decoded[attr] instanceof ParseRelation) {
+ decoded[attr]._ensureParentAndKey(this, attr);
+ }
+ }
+ }
+ if (decoded.createdAt && typeof decoded.createdAt === 'string') {
+ decoded.createdAt = parseDate(decoded.createdAt);
+ }
+ if (decoded.updatedAt && typeof decoded.updatedAt === 'string') {
+ decoded.updatedAt = parseDate(decoded.updatedAt);
+ }
+ if (!decoded.updatedAt && decoded.createdAt) {
+ decoded.updatedAt = decoded.createdAt;
+ }
+ stateController.commitServerChanges(this._getStateIdentifier(), decoded);
+ }
+
+ _setExisted(existed) {
+ let stateController = CoreManager.getObjectStateController();
+ let state = stateController.getState(this._getStateIdentifier());
+ if (state) {
+ state.existed = existed;
+ }
+ }
+
+ _migrateId(serverId) {
+ if (this._localId && serverId) {
+ if (singleInstance) {
+ let stateController = CoreManager.getObjectStateController();
+ let oldState = stateController.removeState(this._getStateIdentifier());
+ this.id = serverId;
+ delete this._localId;
+ if (oldState) {
+ stateController.initializeState(this._getStateIdentifier(), oldState);
+ }
+ } else {
+ this.id = serverId;
+ delete this._localId;
+ }
+ }
+ }
+
+ _handleSaveResponse(response, status) {
+ var changes = {};
+
+ var stateController = CoreManager.getObjectStateController();
+ var pending = stateController.popPendingState(this._getStateIdentifier());
+ for (var attr in pending) {
+ if (pending[attr] instanceof RelationOp) {
+ changes[attr] = pending[attr].applyTo(undefined, this, attr);
+ } else if (!(attr in response)) {
+ // Only SetOps and UnsetOps should not come back with results
+ changes[attr] = pending[attr].applyTo(undefined);
+ }
+ }
+ for (attr in response) {
+ if ((attr === 'createdAt' || attr === 'updatedAt') && typeof response[attr] === 'string') {
+ changes[attr] = parseDate(response[attr]);
+ } else if (attr === 'ACL') {
+ changes[attr] = new ParseACL(response[attr]);
+ } else if (attr !== 'objectId') {
+ changes[attr] = decode(response[attr]);
+ if (changes[attr] instanceof UnsetOp) {
+ changes[attr] = undefined;
+ }
+ }
+ }
+ if (changes.createdAt && !changes.updatedAt) {
+ changes.updatedAt = changes.createdAt;
+ }
+
+ this._migrateId(response.objectId);
+
+ if (status !== 201) {
+ this._setExisted(true);
+ }
+
+ stateController.commitServerChanges(this._getStateIdentifier(), changes);
+ }
+
+ _handleSaveError() {
+ this._getPendingOps();
+
+ let stateController = CoreManager.getObjectStateController();
+ stateController.mergeFirstPendingState(this._getStateIdentifier());
+ }
+
+ /** Public methods **/
+
+ initialize() {}
+ // NOOP
+
+
+ /**
+ * Returns a JSON version of the object suitable for saving to Parse.
+ * @method toJSON
+ * @return {Object}
+ */
+ toJSON(seen) {
+ var seenEntry = this.id ? this.className + ':' + this.id : this;
+ var seen = seen || [seenEntry];
+ var json = {};
+ var attrs = this.attributes;
+ for (var attr in attrs) {
+ if ((attr === 'createdAt' || attr === 'updatedAt') && attrs[attr].toJSON) {
+ json[attr] = attrs[attr].toJSON();
+ } else {
+ json[attr] = encode(attrs[attr], false, false, seen);
+ }
+ }
+ var pending = this._getPendingOps();
+ for (var attr in pending[0]) {
+ json[attr] = pending[0][attr].toJSON();
+ }
+
+ if (this.id) {
+ json.objectId = this.id;
+ }
+ return json;
+ }
+
+ /**
+ * Determines whether this ParseObject is equal to another ParseObject
+ * @method equals
+ * @return {Boolean}
+ */
+ equals(other) {
+ if (this === other) {
+ return true;
+ }
+ return other instanceof ParseObject && this.className === other.className && this.id === other.id && typeof this.id !== 'undefined';
+ }
+
+ /**
+ * Returns true if this object has been modified since its last
+ * save/refresh. If an attribute is specified, it returns true only if that
+ * particular attribute has been modified since the last save/refresh.
+ * @method dirty
+ * @param {String} attr An attribute name (optional).
+ * @return {Boolean}
+ */
+ dirty(attr) {
+ if (!this.id) {
+ return true;
+ }
+ var pendingOps = this._getPendingOps();
+ var dirtyObjects = this._getDirtyObjectAttributes();
+ if (attr) {
+ if (dirtyObjects.hasOwnProperty(attr)) {
+ return true;
+ }
+ for (var i = 0; i < pendingOps.length; i++) {
+ if (pendingOps[i].hasOwnProperty(attr)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ if (Object.keys(pendingOps[0]).length !== 0) {
+ return true;
+ }
+ if (Object.keys(dirtyObjects).length !== 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns an array of keys that have been modified since last save/refresh
+ * @method dirtyKeys
+ * @return {Array of string}
+ */
+ dirtyKeys() {
+ var pendingOps = this._getPendingOps();
+ var keys = {};
+ for (var i = 0; i < pendingOps.length; i++) {
+ for (var attr in pendingOps[i]) {
+ keys[attr] = true;
+ }
+ }
+ var dirtyObjects = this._getDirtyObjectAttributes();
+ for (var attr in dirtyObjects) {
+ keys[attr] = true;
+ }
+ return Object.keys(keys);
+ }
+
+ /**
+ * Gets a Pointer referencing this Object.
+ * @method toPointer
+ * @return {Object}
+ */
+ toPointer() {
+ if (!this.id) {
+ throw new Error('Cannot create a pointer to an unsaved ParseObject');
+ }
+ return {
+ __type: 'Pointer',
+ className: this.className,
+ objectId: this.id
+ };
+ }
+
+ /**
+ * Gets the value of an attribute.
+ * @method get
+ * @param {String} attr The string name of an attribute.
+ */
+ get(attr) {
+ return this.attributes[attr];
+ }
+
+ /**
+ * Gets a relation on the given class for the attribute.
+ * @method relation
+ * @param String attr The attribute to get the relation for.
+ */
+ relation(attr) {
+ var value = this.get(attr);
+ if (value) {
+ if (!(value instanceof ParseRelation)) {
+ throw new Error('Called relation() on non-relation field ' + attr);
+ }
+ value._ensureParentAndKey(this, attr);
+ return value;
+ }
+ return new ParseRelation(this, attr);
+ }
+
+ /**
+ * Gets the HTML-escaped value of an attribute.
+ * @method escape
+ * @param {String} attr The string name of an attribute.
+ */
+ escape(attr) {
+ var val = this.attributes[attr];
+ if (val == null) {
+ return '';
+ }
+
+ if (typeof val !== 'string') {
+ if (typeof val.toString !== 'function') {
+ return '';
+ }
+ val = val.toString();
+ }
+ return escape(val);
+ }
+
+ /**
+ * Returns true if the attribute contains a value that is not
+ * null or undefined.
+ * @method has
+ * @param {String} attr The string name of the attribute.
+ * @return {Boolean}
+ */
+ has(attr) {
+ var attributes = this.attributes;
+ if (attributes.hasOwnProperty(attr)) {
+ return attributes[attr] != null;
+ }
+ return false;
+ }
+
+ /**
+ * Sets a hash of model attributes on the object.
+ *
+ * You can call it with an object containing keys and values, or with one + * key and value. For example:
+ * gameTurn.set({
+ * player: player1,
+ * diceRoll: 2
+ * }, {
+ * error: function(gameTurnAgain, error) {
+ * // The set failed validation.
+ * }
+ * });
+ *
+ * game.set("currentPlayer", player2, {
+ * error: function(gameTurnAgain, error) {
+ * // The set failed validation.
+ * }
+ * });
+ *
+ * game.set("finished", true);
+ *
+ * @method set
+ * @param {String} key The key to set.
+ * @param {} value The value to give it.
+ * @param {Object} options A set of options for the set.
+ * The only supported option is error.
+ * @return {Boolean} true if the set succeeded.
+ */
+ set(key, value, options) {
+ var changes = {};
+ var newOps = {};
+ if (key && typeof key === 'object') {
+ changes = key;
+ options = value;
+ } else if (typeof key === 'string') {
+ changes[key] = value;
+ } else {
+ return this;
+ }
+
+ options = options || {};
+ var readonly = [];
+ if (typeof this.constructor.readOnlyAttributes === 'function') {
+ readonly = readonly.concat(this.constructor.readOnlyAttributes());
+ }
+ for (var k in changes) {
+ if (k === 'createdAt' || k === 'updatedAt') {
+ // This property is read-only, but for legacy reasons we silently
+ // ignore it
+ continue;
+ }
+ if (readonly.indexOf(k) > -1) {
+ throw new Error('Cannot modify readonly attribute: ' + k);
+ }
+ if (options.unset) {
+ newOps[k] = new UnsetOp();
+ } else if (changes[k] instanceof Op) {
+ newOps[k] = changes[k];
+ } else if (changes[k] && typeof changes[k] === 'object' && typeof changes[k].__op === 'string') {
+ newOps[k] = opFromJSON(changes[k]);
+ } else if (k === 'objectId' || k === 'id') {
+ if (typeof changes[k] === 'string') {
+ this.id = changes[k];
+ }
+ } else if (k === 'ACL' && typeof changes[k] === 'object' && !(changes[k] instanceof ParseACL)) {
+ newOps[k] = new SetOp(new ParseACL(changes[k]));
+ } else {
+ newOps[k] = new SetOp(changes[k]);
+ }
+ }
+
+ // Calculate new values
+ var currentAttributes = this.attributes;
+ var newValues = {};
+ for (var attr in newOps) {
+ if (newOps[attr] instanceof RelationOp) {
+ newValues[attr] = newOps[attr].applyTo(currentAttributes[attr], this, attr);
+ } else if (!(newOps[attr] instanceof UnsetOp)) {
+ newValues[attr] = newOps[attr].applyTo(currentAttributes[attr]);
+ }
+ }
+
+ // Validate changes
+ if (!options.ignoreValidation) {
+ var validation = this.validate(newValues);
+ if (validation) {
+ if (typeof options.error === 'function') {
+ options.error(this, validation);
+ }
+ return false;
+ }
+ }
+
+ // Consolidate Ops
+ var pendingOps = this._getPendingOps();
+ var last = pendingOps.length - 1;
+ var stateController = CoreManager.getObjectStateController();
+ for (var attr in newOps) {
+ var nextOp = newOps[attr].mergeWith(pendingOps[last][attr]);
+ stateController.setPendingOp(this._getStateIdentifier(), attr, nextOp);
+ }
+
+ return this;
+ }
+
+ /**
+ * Remove an attribute from the model. This is a noop if the attribute doesn't
+ * exist.
+ * @method unset
+ * @param {String} attr The string name of an attribute.
+ */
+ unset(attr, options) {
+ options = options || {};
+ options.unset = true;
+ return this.set(attr, null, options);
+ }
+
+ /**
+ * Atomically increments the value of the given attribute the next time the
+ * object is saved. If no amount is specified, 1 is used by default.
+ *
+ * @method increment
+ * @param attr {String} The key.
+ * @param amount {Number} The amount to increment by (optional).
+ */
+ increment(attr, amount) {
+ if (typeof amount === 'undefined') {
+ amount = 1;
+ }
+ if (typeof amount !== 'number') {
+ throw new Error('Cannot increment by a non-numeric amount.');
+ }
+ return this.set(attr, new IncrementOp(amount));
+ }
+
+ /**
+ * Atomically add an object to the end of the array associated with a given
+ * key.
+ * @method add
+ * @param attr {String} The key.
+ * @param item {} The item to add.
+ */
+ add(attr, item) {
+ return this.set(attr, new AddOp([item]));
+ }
+
+ /**
+ * Atomically add an object to the array associated with a given key, only
+ * if it is not already present in the array. The position of the insert is
+ * not guaranteed.
+ *
+ * @method addUnique
+ * @param attr {String} The key.
+ * @param item {} The object to add.
+ */
+ addUnique(attr, item) {
+ return this.set(attr, new AddUniqueOp([item]));
+ }
+
+ /**
+ * Atomically remove all instances of an object from the array associated
+ * with a given key.
+ *
+ * @method remove
+ * @param attr {String} The key.
+ * @param item {} The object to remove.
+ */
+ remove(attr, item) {
+ return this.set(attr, new RemoveOp([item]));
+ }
+
+ /**
+ * Returns an instance of a subclass of Parse.Op describing what kind of
+ * modification has been performed on this field since the last time it was
+ * saved. For example, after calling object.increment("x"), calling
+ * object.op("x") would return an instance of Parse.Op.Increment.
+ *
+ * @method op
+ * @param attr {String} The key.
+ * @returns {Parse.Op} The operation, or undefined if none.
+ */
+ op(attr) {
+ var pending = this._getPendingOps();
+ for (var i = pending.length; i--;) {
+ if (pending[i][attr]) {
+ return pending[i][attr];
+ }
+ }
+ }
+
+ /**
+ * Creates a new model with identical attributes to this one, similar to Backbone.Model's clone()
+ * @method clone
+ * @return {Parse.Object}
+ */
+ clone() {
+ let clone = new this.constructor();
+ if (!clone.className) {
+ clone.className = this.className;
+ }
+ let attributes = this.attributes;
+ if (typeof this.constructor.readOnlyAttributes === 'function') {
+ let readonly = this.constructor.readOnlyAttributes() || [];
+ // Attributes are frozen, so we have to rebuild an object,
+ // rather than delete readonly keys
+ let copy = {};
+ for (let a in attributes) {
+ if (readonly.indexOf(a) < 0) {
+ copy[a] = attributes[a];
+ }
+ }
+ attributes = copy;
+ }
+ if (clone.set) {
+ clone.set(attributes);
+ }
+ return clone;
+ }
+
+ /**
+ * Creates a new instance of this object. Not to be confused with clone()
+ * @method newInstance
+ * @return {Parse.Object}
+ */
+ newInstance() {
+ let clone = new this.constructor();
+ if (!clone.className) {
+ clone.className = this.className;
+ }
+ clone.id = this.id;
+ if (singleInstance) {
+ // Just return an object with the right id
+ return clone;
+ }
+
+ let stateController = CoreManager.getObjectStateController();
+ if (stateController) {
+ stateController.duplicateState(this._getStateIdentifier(), clone._getStateIdentifier());
+ }
+ return clone;
+ }
+
+ /**
+ * Returns true if this object has never been saved to Parse.
+ * @method isNew
+ * @return {Boolean}
+ */
+ isNew() {
+ return !this.id;
+ }
+
+ /**
+ * Returns true if this object was created by the Parse server when the
+ * object might have already been there (e.g. in the case of a Facebook
+ * login)
+ * @method existed
+ * @return {Boolean}
+ */
+ existed() {
+ if (!this.id) {
+ return false;
+ }
+ let stateController = CoreManager.getObjectStateController();
+ let state = stateController.getState(this._getStateIdentifier());
+ if (state) {
+ return state.existed;
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the model is currently in a valid state.
+ * @method isValid
+ * @return {Boolean}
+ */
+ isValid() {
+ return !this.validate(this.attributes);
+ }
+
+ /**
+ * You should not call this function directly unless you subclass
+ * Parse.Object, in which case you can override this method
+ * to provide additional validation on set and
+ * save. Your implementation should return
+ *
+ * @method validate
+ * @param {Object} attrs The current data to validate.
+ * @return {} False if the data is valid. An error object otherwise.
+ * @see Parse.Object#set
+ */
+ validate(attrs) {
+ if (attrs.hasOwnProperty('ACL') && !(attrs.ACL instanceof ParseACL)) {
+ return new ParseError(ParseError.OTHER_CAUSE, 'ACL must be a Parse ACL.');
+ }
+ for (var key in attrs) {
+ if (!/^[A-Za-z][0-9A-Za-z_]*$/.test(key)) {
+ return new ParseError(ParseError.INVALID_KEY_NAME);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the ACL for this object.
+ * @method getACL
+ * @returns {Parse.ACL} An instance of Parse.ACL.
+ * @see Parse.Object#get
+ */
+ getACL() {
+ var acl = this.get('ACL');
+ if (acl instanceof ParseACL) {
+ return acl;
+ }
+ return null;
+ }
+
+ /**
+ * Sets the ACL to be used for this object.
+ * @method setACL
+ * @param {Parse.ACL} acl An instance of Parse.ACL.
+ * @param {Object} options Optional Backbone-like options object to be
+ * passed in to set.
+ * @return {Boolean} Whether the set passed validation.
+ * @see Parse.Object#set
+ */
+ setACL(acl, options) {
+ return this.set('ACL', acl, options);
+ }
+
+ /**
+ * Clears any changes to this object made since the last call to save()
+ * @method revert
+ */
+ revert() {
+ this._clearPendingOps();
+ }
+
+ /**
+ * Clears all attributes on a model
+ * @method clear
+ */
+ clear() {
+ var attributes = this.attributes;
+ var erasable = {};
+ var readonly = ['createdAt', 'updatedAt'];
+ if (typeof this.constructor.readOnlyAttributes === 'function') {
+ readonly = readonly.concat(this.constructor.readOnlyAttributes());
+ }
+ for (var attr in attributes) {
+ if (readonly.indexOf(attr) < 0) {
+ erasable[attr] = true;
+ }
+ }
+ return this.set(erasable, { unset: true });
+ }
+
+ /**
+ * Fetch the model from the server. If the server's representation of the
+ * model differs from its current attributes, they will be overriden.
+ *
+ * @method fetch
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:+ * object.save();+ * or
+ * object.save(null, options);+ * or
+ * object.save(attrs, options);+ * or
+ * object.save(key, value, options);+ * + * For example,
+ * gameTurn.save({
+ * player: "Jake Cutter",
+ * diceRoll: 2
+ * }, {
+ * success: function(gameTurnAgain) {
+ * // The save was successful.
+ * },
+ * error: function(gameTurnAgain, error) {
+ * // The save failed. Error is an instance of Parse.Error.
+ * }
+ * });
+ * or with promises:
+ * gameTurn.save({
+ * player: "Jake Cutter",
+ * diceRoll: 2
+ * }).then(function(gameTurnAgain) {
+ * // The save was successful.
+ * }, function(error) {
+ * // The save failed. Error is an instance of Parse.Error.
+ * });
+ *
+ * @method save
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:
+ * Parse.Object.fetchAll([object1, object2, ...], {
+ * success: function(list) {
+ * // All the objects were fetched.
+ * },
+ * error: function(error) {
+ * // An error occurred while fetching one of the objects.
+ * },
+ * });
+ *
+ *
+ * @method fetchAll
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:
+ * Parse.Object.fetchAllIfNeeded([object1, ...], {
+ * success: function(list) {
+ * // Objects were fetched and updated.
+ * },
+ * error: function(error) {
+ * // An error occurred while fetching one of the objects.
+ * },
+ * });
+ *
+ *
+ * @method fetchAllIfNeeded
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:Unlike saveAll, if an error occurs while deleting an individual model, + * this method will continue trying to delete the rest of the models if + * possible, except in the case of a fatal error like a connection error. + * + *
In particular, the Parse.Error object returned in the case of error may + * be one of two types: + * + *
+ * Parse.Object.destroyAll([object1, object2, ...], {
+ * success: function() {
+ * // All the objects were deleted.
+ * },
+ * error: function(error) {
+ * // An error occurred while deleting one or more of the objects.
+ * // If this is an aggregate error, then we can inspect each error
+ * // object individually to determine the reason why a particular
+ * // object was not deleted.
+ * if (error.code === Parse.Error.AGGREGATE_ERROR) {
+ * for (var i = 0; i < error.errors.length; i++) {
+ * console.log("Couldn't delete " + error.errors[i].object.id +
+ * "due to " + error.errors[i].message);
+ * }
+ * } else {
+ * console.log("Delete aborted because of " + error.message);
+ * }
+ * },
+ * });
+ *
+ *
+ * @method destroyAll
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:
+ * Parse.Object.saveAll([object1, object2, ...], {
+ * success: function(list) {
+ * // All the objects were saved.
+ * },
+ * error: function(error) {
+ * // An error occurred while saving one of the objects.
+ * },
+ * });
+ *
+ *
+ * @method saveAll
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * @static
+ * Valid options are:A shortcut for:
+ * var Foo = Parse.Object.extend("Foo");
+ * var pointerToFoo = new Foo();
+ * pointerToFoo.id = "myObjectId";
+ *
+ *
+ * @method createWithoutData
+ * @param {String} id The ID of the object to create a reference to.
+ * @static
+ * @return {Parse.Object} A Parse.Object reference.
+ */
+ static createWithoutData(id) {
+ var obj = new this();
+ obj.id = id;
+ return obj;
+ }
+
+ /**
+ * Creates a new instance of a Parse Object from a JSON representation.
+ * @method fromJSON
+ * @param {Object} json The JSON map of the Object's data
+ * @param {boolean} override In single instance mode, all old server data
+ * is overwritten if this is set to true
+ * @static
+ * @return {Parse.Object} A Parse.Object reference
+ */
+ static fromJSON(json, override) {
+ if (!json.className) {
+ throw new Error('Cannot create an object without a className');
+ }
+ var constructor = classMap[json.className];
+ var o = constructor ? new constructor() : new ParseObject(json.className);
+ var otherAttributes = {};
+ for (var attr in json) {
+ if (attr !== 'className' && attr !== '__type') {
+ otherAttributes[attr] = json[attr];
+ }
+ }
+ if (override) {
+ // id needs to be set before clearServerData can work
+ if (otherAttributes.objectId) {
+ o.id = otherAttributes.objectId;
+ }
+ let preserved = null;
+ if (typeof o._preserveFieldsOnFetch === 'function') {
+ preserved = o._preserveFieldsOnFetch();
+ }
+ o._clearServerData();
+ if (preserved) {
+ o._finishFetch(preserved);
+ }
+ }
+ o._finishFetch(otherAttributes);
+ if (json.objectId) {
+ o._setExisted(true);
+ }
+ return o;
+ }
+
+ /**
+ * Registers a subclass of Parse.Object with a specific class name.
+ * When objects of that class are retrieved from a query, they will be
+ * instantiated with this subclass.
+ * This is only necessary when using ES6 subclassing.
+ * @method registerSubclass
+ * @param {String} className The class name of the subclass
+ * @param {Class} constructor The subclass
+ */
+ static registerSubclass(className, constructor) {
+ if (typeof className !== 'string') {
+ throw new TypeError('The first argument must be a valid class name.');
+ }
+ if (typeof constructor === 'undefined') {
+ throw new TypeError('You must supply a subclass constructor.');
+ }
+ if (typeof constructor !== 'function') {
+ throw new TypeError('You must register the subclass constructor. ' + 'Did you attempt to register an instance of the subclass?');
+ }
+ classMap[className] = constructor;
+ if (!constructor.className) {
+ constructor.className = className;
+ }
+ }
+
+ /**
+ * Creates a new subclass of Parse.Object for the given Parse class name.
+ *
+ * Every extension of a Parse class will inherit from the most recent + * previous extension of that class. When a Parse.Object is automatically + * created by parsing JSON, it will use the most recent extension of that + * class.
+ * + *You should call either:
+ * var MyClass = Parse.Object.extend("MyClass", {
+ * Instance methods,
+ * initialize: function(attrs, options) {
+ * this.someInstanceProperty = [],
+ * Other instance properties
+ * }
+ * }, {
+ * Class properties
+ * });
+ * or, for Backbone compatibility:
+ * var MyClass = Parse.Object.extend({
+ * className: "MyClass",
+ * Instance methods,
+ * initialize: function(attrs, options) {
+ * this.someInstanceProperty = [],
+ * Other instance properties
+ * }
+ * }, {
+ * Class properties
+ * });
+ *
+ * @method extend
+ * @param {String} className The name of the Parse class backing this model.
+ * @param {Object} protoProps Instance properties to add to instances of the
+ * class returned from this method.
+ * @param {Object} classProps Class properties to add the class returned from
+ * this method.
+ * @return {Class} A new subclass of Parse.Object.
+ */
+ static extend(className, protoProps, classProps) {
+ if (typeof className !== 'string') {
+ if (className && typeof className.className === 'string') {
+ return ParseObject.extend(className.className, className, protoProps);
+ } else {
+ throw new Error('Parse.Object.extend\'s first argument should be the className.');
+ }
+ }
+ var adjustedClassName = className;
+
+ if (adjustedClassName === 'User' && CoreManager.get('PERFORM_USER_REWRITE')) {
+ adjustedClassName = '_User';
+ }
+
+ var parentProto = ParseObject.prototype;
+ if (this.hasOwnProperty('__super__') && this.__super__) {
+ parentProto = this.prototype;
+ } else if (classMap[adjustedClassName]) {
+ parentProto = classMap[adjustedClassName].prototype;
+ }
+ var ParseObjectSubclass = function (attributes, options) {
+ this.className = adjustedClassName;
+ this._objCount = objectCount++;
+ // Enable legacy initializers
+ if (typeof this.initialize === 'function') {
+ this.initialize.apply(this, arguments);
+ }
+
+ if (attributes && typeof attributes === 'object') {
+ if (!this.set(attributes || {}, options)) {
+ throw new Error('Can\'t create an invalid Parse Object');
+ }
+ }
+ };
+ ParseObjectSubclass.className = adjustedClassName;
+ ParseObjectSubclass.__super__ = parentProto;
+
+ ParseObjectSubclass.prototype = Object.create(parentProto, {
+ constructor: {
+ value: ParseObjectSubclass,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+ });
+
+ if (protoProps) {
+ for (var prop in protoProps) {
+ if (prop !== 'className') {
+ Object.defineProperty(ParseObjectSubclass.prototype, prop, {
+ value: protoProps[prop],
+ enumerable: false,
+ writable: true,
+ configurable: true
+ });
+ }
+ }
+ }
+
+ if (classProps) {
+ for (var prop in classProps) {
+ if (prop !== 'className') {
+ Object.defineProperty(ParseObjectSubclass, prop, {
+ value: classProps[prop],
+ enumerable: false,
+ writable: true,
+ configurable: true
+ });
+ }
+ }
+ }
+
+ ParseObjectSubclass.extend = function (name, protoProps, classProps) {
+ if (typeof name === 'string') {
+ return ParseObject.extend.call(ParseObjectSubclass, name, protoProps, classProps);
+ }
+ return ParseObject.extend.call(ParseObjectSubclass, adjustedClassName, name, protoProps);
+ };
+ ParseObjectSubclass.createWithoutData = ParseObject.createWithoutData;
+
+ classMap[adjustedClassName] = ParseObjectSubclass;
+ return ParseObjectSubclass;
+ }
+
+ /**
+ * Enable single instance objects, where any local objects with the same Id
+ * share the same attributes, and stay synchronized with each other.
+ * This is disabled by default in server environments, since it can lead to
+ * security issues.
+ * @method enableSingleInstance
+ */
+ static enableSingleInstance() {
+ singleInstance = true;
+ CoreManager.setObjectStateController(SingleInstanceStateController);
+ }
+
+ /**
+ * Disable single instance objects, where any local objects with the same Id
+ * share the same attributes, and stay synchronized with each other.
+ * When disabled, you can have two instances of the same object in memory
+ * without them sharing attributes.
+ * @method disableSingleInstance
+ */
+ static disableSingleInstance() {
+ singleInstance = false;
+ CoreManager.setObjectStateController(UniqueInstanceStateController);
+ }
+}
+
+var DefaultController = {
+ fetch(target, forceFetch, options) {
+ if (Array.isArray(target)) {
+ if (target.length < 1) {
+ return ParsePromise.as([]);
+ }
+ var objs = [];
+ var ids = [];
+ var className = null;
+ var results = [];
+ var error = null;
+ target.forEach((el, i) => {
+ if (error) {
+ return;
+ }
+ if (!className) {
+ className = el.className;
+ }
+ if (className !== el.className) {
+ error = new ParseError(ParseError.INVALID_CLASS_NAME, 'All objects should be of the same class');
+ }
+ if (!el.id) {
+ error = new ParseError(ParseError.MISSING_OBJECT_ID, 'All objects must have an ID');
+ }
+ if (forceFetch || Object.keys(el._getServerData()).length === 0) {
+ ids.push(el.id);
+ objs.push(el);
+ }
+ results.push(el);
+ });
+ if (error) {
+ return ParsePromise.error(error);
+ }
+ var query = new ParseQuery(className);
+ query.containedIn('objectId', ids);
+ query._limit = ids.length;
+ return query.find(options).then(objects => {
+ var idMap = {};
+ objects.forEach(o => {
+ idMap[o.id] = o;
+ });
+ for (var i = 0; i < objs.length; i++) {
+ var obj = objs[i];
+ if (!obj || !obj.id || !idMap[obj.id]) {
+ if (forceFetch) {
+ return ParsePromise.error(new ParseError(ParseError.OBJECT_NOT_FOUND, 'All objects must exist on the server.'));
+ }
+ }
+ }
+ if (!singleInstance) {
+ // If single instance objects are disabled, we need to replace the
+ for (var i = 0; i < results.length; i++) {
+ var obj = results[i];
+ if (obj && obj.id && idMap[obj.id]) {
+ var id = obj.id;
+ obj._finishFetch(idMap[id].toJSON());
+ results[i] = idMap[id];
+ }
+ }
+ }
+ return ParsePromise.as(results);
+ });
+ } else {
+ var RESTController = CoreManager.getRESTController();
+ return RESTController.request('GET', 'classes/' + target.className + '/' + target._getId(), {}, options).then((response, status, xhr) => {
+ if (target instanceof ParseObject) {
+ target._clearPendingOps();
+ target._clearServerData();
+ target._finishFetch(response);
+ }
+ return target;
+ });
+ }
+ },
+
+ destroy(target, options) {
+ var RESTController = CoreManager.getRESTController();
+ if (Array.isArray(target)) {
+ if (target.length < 1) {
+ return ParsePromise.as([]);
+ }
+ var batches = [[]];
+ target.forEach(obj => {
+ if (!obj.id) {
+ return;
+ }
+ batches[batches.length - 1].push(obj);
+ if (batches[batches.length - 1].length >= 20) {
+ batches.push([]);
+ }
+ });
+ if (batches[batches.length - 1].length === 0) {
+ // If the last batch is empty, remove it
+ batches.pop();
+ }
+ var deleteCompleted = ParsePromise.as();
+ var errors = [];
+ batches.forEach(batch => {
+ deleteCompleted = deleteCompleted.then(() => {
+ return RESTController.request('POST', 'batch', {
+ requests: batch.map(obj => {
+ return {
+ method: 'DELETE',
+ path: getServerUrlPath() + 'classes/' + obj.className + '/' + obj._getId(),
+ body: {}
+ };
+ })
+ }, options).then(results => {
+ for (var i = 0; i < results.length; i++) {
+ if (results[i] && results[i].hasOwnProperty('error')) {
+ var err = new ParseError(results[i].error.code, results[i].error.error);
+ err.object = batch[i];
+ errors.push(err);
+ }
+ }
+ });
+ });
+ });
+ return deleteCompleted.then(() => {
+ if (errors.length) {
+ var aggregate = new ParseError(ParseError.AGGREGATE_ERROR);
+ aggregate.errors = errors;
+ return ParsePromise.error(aggregate);
+ }
+ return ParsePromise.as(target);
+ });
+ } else if (target instanceof ParseObject) {
+ return RESTController.request('DELETE', 'classes/' + target.className + '/' + target._getId(), {}, options).then(() => {
+ return ParsePromise.as(target);
+ });
+ }
+ return ParsePromise.as(target);
+ },
+
+ save(target, options) {
+ var RESTController = CoreManager.getRESTController();
+ var stateController = CoreManager.getObjectStateController();
+ if (Array.isArray(target)) {
+ if (target.length < 1) {
+ return ParsePromise.as([]);
+ }
+
+ var unsaved = target.concat();
+ for (var i = 0; i < target.length; i++) {
+ if (target[i] instanceof ParseObject) {
+ unsaved = unsaved.concat(unsavedChildren(target[i], true));
+ }
+ }
+ unsaved = unique(unsaved);
+
+ var filesSaved = ParsePromise.as();
+ var pending = [];
+ unsaved.forEach(el => {
+ if (el instanceof ParseFile) {
+ filesSaved = filesSaved.then(() => {
+ return el.save();
+ });
+ } else if (el instanceof ParseObject) {
+ pending.push(el);
+ }
+ });
+
+ return filesSaved.then(() => {
+ var objectError = null;
+ return ParsePromise._continueWhile(() => {
+ return pending.length > 0;
+ }, () => {
+ var batch = [];
+ var nextPending = [];
+ pending.forEach(el => {
+ if (batch.length < 20 && canBeSerialized(el)) {
+ batch.push(el);
+ } else {
+ nextPending.push(el);
+ }
+ });
+ pending = nextPending;
+ if (batch.length < 1) {
+ return ParsePromise.error(new ParseError(ParseError.OTHER_CAUSE, 'Tried to save a batch with a cycle.'));
+ }
+
+ // Queue up tasks for each object in the batch.
+ // When every task is ready, the API request will execute
+ var batchReturned = new ParsePromise();
+ var batchReady = [];
+ var batchTasks = [];
+ batch.forEach((obj, index) => {
+ var ready = new ParsePromise();
+ batchReady.push(ready);
+
+ stateController.pushPendingState(obj._getStateIdentifier());
+ batchTasks.push(stateController.enqueueTask(obj._getStateIdentifier(), function () {
+ ready.resolve();
+ return batchReturned.then((responses, status) => {
+ if (responses[index].hasOwnProperty('success')) {
+ obj._handleSaveResponse(responses[index].success, status);
+ } else {
+ if (!objectError && responses[index].hasOwnProperty('error')) {
+ var serverError = responses[index].error;
+ objectError = new ParseError(serverError.code, serverError.error);
+ // Cancel the rest of the save
+ pending = [];
+ }
+ obj._handleSaveError();
+ }
+ });
+ }));
+ });
+
+ ParsePromise.when(batchReady).then(() => {
+ // Kick off the batch request
+ return RESTController.request('POST', 'batch', {
+ requests: batch.map(obj => {
+ var params = obj._getSaveParams();
+ params.path = getServerUrlPath() + params.path;
+ return params;
+ })
+ }, options);
+ }).then((response, status, xhr) => {
+ batchReturned.resolve(response, status);
+ });
+
+ return ParsePromise.when(batchTasks);
+ }).then(() => {
+ if (objectError) {
+ return ParsePromise.error(objectError);
+ }
+ return ParsePromise.as(target);
+ });
+ });
+ } else if (target instanceof ParseObject) {
+ // copying target lets Flow guarantee the pointer isn't modified elsewhere
+ var targetCopy = target;
+ var task = function () {
+ var params = targetCopy._getSaveParams();
+ return RESTController.request(params.method, params.path, params.body, options).then((response, status) => {
+ targetCopy._handleSaveResponse(response, status);
+ }, error => {
+ targetCopy._handleSaveError();
+ return ParsePromise.error(error);
+ });
+ };
+
+ stateController.pushPendingState(target._getStateIdentifier());
+ return stateController.enqueueTask(target._getStateIdentifier(), task).then(() => {
+ return target;
+ }, error => {
+ return ParsePromise.error(error);
+ });
+ }
+ return ParsePromise.as();
+ }
+};
+
+CoreManager.setObjectController(DefaultController);
\ No newline at end of file
diff --git a/lib/react-native/ParseOp.js b/lib/react-native/ParseOp.js
new file mode 100644
index 000000000..db70be82c
--- /dev/null
+++ b/lib/react-native/ParseOp.js
@@ -0,0 +1,434 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+import arrayContainsObject from './arrayContainsObject';
+import decode from './decode';
+import encode from './encode';
+import ParseObject from './ParseObject';
+import ParseRelation from './ParseRelation';
+import unique from './unique';
+
+export function opFromJSON(json) {
+ if (!json || !json.__op) {
+ return null;
+ }
+ switch (json.__op) {
+ case 'Delete':
+ return new UnsetOp();
+ case 'Increment':
+ return new IncrementOp(json.amount);
+ case 'Add':
+ return new AddOp(decode(json.objects));
+ case 'AddUnique':
+ return new AddUniqueOp(decode(json.objects));
+ case 'Remove':
+ return new RemoveOp(decode(json.objects));
+ case 'AddRelation':
+ var toAdd = decode(json.objects);
+ if (!Array.isArray(toAdd)) {
+ return new RelationOp([], []);
+ }
+ return new RelationOp(toAdd, []);
+ case 'RemoveRelation':
+ var toRemove = decode(json.objects);
+ if (!Array.isArray(toRemove)) {
+ return new RelationOp([], []);
+ }
+ return new RelationOp([], toRemove);
+ case 'Batch':
+ var toAdd = [];
+ var toRemove = [];
+ for (var i = 0; i < json.ops.length; i++) {
+ if (json.ops[i].__op === 'AddRelation') {
+ toAdd = toAdd.concat(decode(json.ops[i].objects));
+ } else if (json.ops[i].__op === 'RemoveRelation') {
+ toRemove = toRemove.concat(decode(json.ops[i].objects));
+ }
+ }
+ return new RelationOp(toAdd, toRemove);
+ }
+ return null;
+}
+
+export class Op {
+ // Empty parent class
+ applyTo(value) {}
+ mergeWith(previous) {}
+ toJSON() {}
+}
+
+export class SetOp extends Op {
+
+ constructor(value) {
+ super();
+ this._value = value;
+ }
+
+ applyTo(value) {
+ return this._value;
+ }
+
+ mergeWith(previous) {
+ return new SetOp(this._value);
+ }
+
+ toJSON() {
+ return encode(this._value, false, true);
+ }
+}
+
+export class UnsetOp extends Op {
+ applyTo(value) {
+ return undefined;
+ }
+
+ mergeWith(previous) {
+ return new UnsetOp();
+ }
+
+ toJSON() {
+ return { __op: 'Delete' };
+ }
+}
+
+export class IncrementOp extends Op {
+
+ constructor(amount) {
+ super();
+ if (typeof amount !== 'number') {
+ throw new TypeError('Increment Op must be initialized with a numeric amount.');
+ }
+ this._amount = amount;
+ }
+
+ applyTo(value) {
+ if (typeof value === 'undefined') {
+ return this._amount;
+ }
+ if (typeof value !== 'number') {
+ throw new TypeError('Cannot increment a non-numeric value.');
+ }
+ return this._amount + value;
+ }
+
+ mergeWith(previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new SetOp(this._amount);
+ }
+ if (previous instanceof IncrementOp) {
+ return new IncrementOp(this.applyTo(previous._amount));
+ }
+ throw new Error('Cannot merge Increment Op with the previous Op');
+ }
+
+ toJSON() {
+ return { __op: 'Increment', amount: this._amount };
+ }
+}
+
+export class AddOp extends Op {
+
+ constructor(value) {
+ super();
+ this._value = Array.isArray(value) ? value : [value];
+ }
+
+ applyTo(value) {
+ if (value == null) {
+ return this._value;
+ }
+ if (Array.isArray(value)) {
+ return value.concat(this._value);
+ }
+ throw new Error('Cannot add elements to a non-array value');
+ }
+
+ mergeWith(previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new SetOp(this._value);
+ }
+ if (previous instanceof AddOp) {
+ return new AddOp(this.applyTo(previous._value));
+ }
+ throw new Error('Cannot merge Add Op with the previous Op');
+ }
+
+ toJSON() {
+ return { __op: 'Add', objects: encode(this._value, false, true) };
+ }
+}
+
+export class AddUniqueOp extends Op {
+
+ constructor(value) {
+ super();
+ this._value = unique(Array.isArray(value) ? value : [value]);
+ }
+
+ applyTo(value) {
+ if (value == null) {
+ return this._value || [];
+ }
+ if (Array.isArray(value)) {
+ // copying value lets Flow guarantee the pointer isn't modified elsewhere
+ var valueCopy = value;
+ var toAdd = [];
+ this._value.forEach(v => {
+ if (v instanceof ParseObject) {
+ if (!arrayContainsObject(valueCopy, v)) {
+ toAdd.push(v);
+ }
+ } else {
+ if (valueCopy.indexOf(v) < 0) {
+ toAdd.push(v);
+ }
+ }
+ });
+ return value.concat(toAdd);
+ }
+ throw new Error('Cannot add elements to a non-array value');
+ }
+
+ mergeWith(previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new SetOp(this._value);
+ }
+ if (previous instanceof AddUniqueOp) {
+ return new AddUniqueOp(this.applyTo(previous._value));
+ }
+ throw new Error('Cannot merge AddUnique Op with the previous Op');
+ }
+
+ toJSON() {
+ return { __op: 'AddUnique', objects: encode(this._value, false, true) };
+ }
+}
+
+export class RemoveOp extends Op {
+
+ constructor(value) {
+ super();
+ this._value = unique(Array.isArray(value) ? value : [value]);
+ }
+
+ applyTo(value) {
+ if (value == null) {
+ return [];
+ }
+ if (Array.isArray(value)) {
+ var i = value.indexOf(this._value);
+ var removed = value.concat([]);
+ for (var i = 0; i < this._value.length; i++) {
+ var index = removed.indexOf(this._value[i]);
+ while (index > -1) {
+ removed.splice(index, 1);
+ index = removed.indexOf(this._value[i]);
+ }
+ if (this._value[i] instanceof ParseObject && this._value[i].id) {
+ for (var j = 0; j < removed.length; j++) {
+ if (removed[j] instanceof ParseObject && this._value[i].id === removed[j].id) {
+ removed.splice(j, 1);
+ j--;
+ }
+ }
+ }
+ }
+ return removed;
+ }
+ throw new Error('Cannot remove elements from a non-array value');
+ }
+
+ mergeWith(previous) {
+ if (!previous) {
+ return this;
+ }
+ if (previous instanceof SetOp) {
+ return new SetOp(this.applyTo(previous._value));
+ }
+ if (previous instanceof UnsetOp) {
+ return new UnsetOp();
+ }
+ if (previous instanceof RemoveOp) {
+ var uniques = previous._value.concat([]);
+ for (var i = 0; i < this._value.length; i++) {
+ if (this._value[i] instanceof ParseObject) {
+ if (!arrayContainsObject(uniques, this._value[i])) {
+ uniques.push(this._value[i]);
+ }
+ } else {
+ if (uniques.indexOf(this._value[i]) < 0) {
+ uniques.push(this._value[i]);
+ }
+ }
+ }
+ return new RemoveOp(uniques);
+ }
+ throw new Error('Cannot merge Remove Op with the previous Op');
+ }
+
+ toJSON() {
+ return { __op: 'Remove', objects: encode(this._value, false, true) };
+ }
+}
+
+export class RelationOp extends Op {
+
+ constructor(adds, removes) {
+ super();
+ this._targetClassName = null;
+
+ if (Array.isArray(adds)) {
+ this.relationsToAdd = unique(adds.map(this._extractId, this));
+ }
+
+ if (Array.isArray(removes)) {
+ this.relationsToRemove = unique(removes.map(this._extractId, this));
+ }
+ }
+
+ _extractId(obj) {
+ if (typeof obj === 'string') {
+ return obj;
+ }
+ if (!obj.id) {
+ throw new Error('You cannot add or remove an unsaved Parse Object from a relation');
+ }
+ if (!this._targetClassName) {
+ this._targetClassName = obj.className;
+ }
+ if (this._targetClassName !== obj.className) {
+ throw new Error('Tried to create a Relation with 2 different object types: ' + this._targetClassName + ' and ' + obj.className + '.');
+ }
+ return obj.id;
+ }
+
+ applyTo(value, object, key) {
+ if (!value) {
+ if (!object || !key) {
+ throw new Error('Cannot apply a RelationOp without either a previous value, or an object and a key');
+ }
+ var parent = new ParseObject(object.className);
+ if (object.id && object.id.indexOf('local') === 0) {
+ parent._localId = object.id;
+ } else if (object.id) {
+ parent.id = object.id;
+ }
+ var relation = new ParseRelation(parent, key);
+ relation.targetClassName = this._targetClassName;
+ return relation;
+ }
+ if (value instanceof ParseRelation) {
+ if (this._targetClassName) {
+ if (value.targetClassName) {
+ if (this._targetClassName !== value.targetClassName) {
+ throw new Error('Related object must be a ' + value.targetClassName + ', but a ' + this._targetClassName + ' was passed in.');
+ }
+ } else {
+ value.targetClassName = this._targetClassName;
+ }
+ }
+ return value;
+ } else {
+ throw new Error('Relation cannot be applied to a non-relation field');
+ }
+ }
+
+ mergeWith(previous) {
+ if (!previous) {
+ return this;
+ } else if (previous instanceof UnsetOp) {
+ throw new Error('You cannot modify a relation after deleting it.');
+ } else if (previous instanceof RelationOp) {
+ if (previous._targetClassName && previous._targetClassName !== this._targetClassName) {
+ throw new Error('Related object must be of class ' + previous._targetClassName + ', but ' + (this._targetClassName || 'null') + ' was passed in.');
+ }
+ var newAdd = previous.relationsToAdd.concat([]);
+ this.relationsToRemove.forEach(r => {
+ var index = newAdd.indexOf(r);
+ if (index > -1) {
+ newAdd.splice(index, 1);
+ }
+ });
+ this.relationsToAdd.forEach(r => {
+ var index = newAdd.indexOf(r);
+ if (index < 0) {
+ newAdd.push(r);
+ }
+ });
+
+ var newRemove = previous.relationsToRemove.concat([]);
+ this.relationsToAdd.forEach(r => {
+ var index = newRemove.indexOf(r);
+ if (index > -1) {
+ newRemove.splice(index, 1);
+ }
+ });
+ this.relationsToRemove.forEach(r => {
+ var index = newRemove.indexOf(r);
+ if (index < 0) {
+ newRemove.push(r);
+ }
+ });
+
+ var newRelation = new RelationOp(newAdd, newRemove);
+ newRelation._targetClassName = this._targetClassName;
+ return newRelation;
+ }
+ throw new Error('Cannot merge Relation Op with the previous Op');
+ }
+
+ toJSON() {
+ var idToPointer = id => {
+ return {
+ __type: 'Pointer',
+ className: this._targetClassName,
+ objectId: id
+ };
+ };
+
+ var adds = null;
+ var removes = null;
+ var pointers = null;
+
+ if (this.relationsToAdd.length > 0) {
+ pointers = this.relationsToAdd.map(idToPointer);
+ adds = { __op: 'AddRelation', objects: pointers };
+ }
+ if (this.relationsToRemove.length > 0) {
+ pointers = this.relationsToRemove.map(idToPointer);
+ removes = { __op: 'RemoveRelation', objects: pointers };
+ }
+
+ if (adds && removes) {
+ return { __op: 'Batch', ops: [adds, removes] };
+ }
+
+ return adds || removes || {};
+ }
+}
\ No newline at end of file
diff --git a/lib/react-native/ParsePromise.js b/lib/react-native/ParsePromise.js
new file mode 100644
index 000000000..ef7626b6d
--- /dev/null
+++ b/lib/react-native/ParsePromise.js
@@ -0,0 +1,575 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+var isPromisesAPlusCompliant = true;
+
+/**
+ * A Promise is returned by async methods as a hook to provide callbacks to be
+ * called when the async task is fulfilled.
+ *
+ * Typical usage would be like:
+ * query.find().then(function(results) {
+ * results[0].set("foo", "bar");
+ * return results[0].saveAsync();
+ * }).then(function(result) {
+ * console.log("Updated " + result.id);
+ * });
+ *
+ *
+ * @class Parse.Promise
+ * @constructor
+ */
+export default class ParsePromise {
+ constructor(executor) {
+ this._resolved = false;
+ this._rejected = false;
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+
+ if (typeof executor === 'function') {
+ executor(this.resolve.bind(this), this.reject.bind(this));
+ }
+ }
+
+ /**
+ * Marks this promise as fulfilled, firing any callbacks waiting on it.
+ * @method resolve
+ * @param {Object} result the result to pass to the callbacks.
+ */
+ resolve(...results) {
+ if (this._resolved || this._rejected) {
+ throw new Error('A promise was resolved even though it had already been ' + (this._resolved ? 'resolved' : 'rejected') + '.');
+ }
+ this._resolved = true;
+ this._result = results;
+ for (var i = 0; i < this._resolvedCallbacks.length; i++) {
+ this._resolvedCallbacks[i].apply(this, results);
+ }
+
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+ }
+
+ /**
+ * Marks this promise as fulfilled, firing any callbacks waiting on it.
+ * @method reject
+ * @param {Object} error the error to pass to the callbacks.
+ */
+ reject(error) {
+ if (this._resolved || this._rejected) {
+ throw new Error('A promise was rejected even though it had already been ' + (this._resolved ? 'resolved' : 'rejected') + '.');
+ }
+ this._rejected = true;
+ this._error = error;
+ for (var i = 0; i < this._rejectedCallbacks.length; i++) {
+ this._rejectedCallbacks[i](error);
+ }
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+ }
+
+ /**
+ * Adds callbacks to be called when this promise is fulfilled. Returns a new
+ * Promise that will be fulfilled when the callback is complete. It allows
+ * chaining. If the callback itself returns a Promise, then the one returned
+ * by "then" will not be fulfilled until that one returned by the callback
+ * is fulfilled.
+ * @method then
+ * @param {Function} resolvedCallback Function that is called when this
+ * Promise is resolved. Once the callback is complete, then the Promise
+ * returned by "then" will also be fulfilled.
+ * @param {Function} rejectedCallback Function that is called when this
+ * Promise is rejected with an error. Once the callback is complete, then
+ * the promise returned by "then" with be resolved successfully. If
+ * rejectedCallback is null, or it returns a rejected Promise, then the
+ * Promise returned by "then" will be rejected with that error.
+ * @return {Parse.Promise} A new Promise that will be fulfilled after this
+ * Promise is fulfilled and either callback has completed. If the callback
+ * returned a Promise, then this Promise will not be fulfilled until that
+ * one is.
+ */
+ then(resolvedCallback, rejectedCallback) {
+ var promise = new ParsePromise();
+
+ var wrappedResolvedCallback = function (...results) {
+ if (typeof resolvedCallback === 'function') {
+ if (isPromisesAPlusCompliant) {
+ try {
+ results = [resolvedCallback.apply(this, results)];
+ } catch (e) {
+ results = [ParsePromise.error(e)];
+ }
+ } else {
+ results = [resolvedCallback.apply(this, results)];
+ }
+ }
+ if (results.length === 1 && ParsePromise.is(results[0])) {
+ results[0].then(function () {
+ promise.resolve.apply(promise, arguments);
+ }, function (error) {
+ promise.reject(error);
+ });
+ } else {
+ promise.resolve.apply(promise, results);
+ }
+ };
+
+ var wrappedRejectedCallback = function (error) {
+ var result = [];
+ if (typeof rejectedCallback === 'function') {
+ if (isPromisesAPlusCompliant) {
+ try {
+ result = [rejectedCallback(error)];
+ } catch (e) {
+ result = [ParsePromise.error(e)];
+ }
+ } else {
+ result = [rejectedCallback(error)];
+ }
+ if (result.length === 1 && ParsePromise.is(result[0])) {
+ result[0].then(function () {
+ promise.resolve.apply(promise, arguments);
+ }, function (error) {
+ promise.reject(error);
+ });
+ } else {
+ if (isPromisesAPlusCompliant) {
+ promise.resolve.apply(promise, result);
+ } else {
+ promise.reject(result[0]);
+ }
+ }
+ } else {
+ promise.reject(error);
+ }
+ };
+
+ var runLater = function (fn) {
+ fn.call();
+ };
+ if (isPromisesAPlusCompliant) {
+ if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
+ runLater = function (fn) {
+ process.nextTick(fn);
+ };
+ } else if (typeof setTimeout === 'function') {
+ runLater = function (fn) {
+ setTimeout(fn, 0);
+ };
+ }
+ }
+
+ if (this._resolved) {
+ runLater(() => {
+ wrappedResolvedCallback.apply(this, this._result);
+ });
+ } else if (this._rejected) {
+ runLater(() => {
+ wrappedRejectedCallback(this._error);
+ });
+ } else {
+ this._resolvedCallbacks.push(wrappedResolvedCallback);
+ this._rejectedCallbacks.push(wrappedRejectedCallback);
+ }
+
+ return promise;
+ }
+
+ /**
+ * Add handlers to be called when the promise
+ * is either resolved or rejected
+ * @method always
+ */
+ always(callback) {
+ return this.then(callback, callback);
+ }
+
+ /**
+ * Add handlers to be called when the Promise object is resolved
+ * @method done
+ */
+ done(callback) {
+ return this.then(callback);
+ }
+
+ /**
+ * Add handlers to be called when the Promise object is rejected
+ * Alias for catch().
+ * @method fail
+ */
+ fail(callback) {
+ return this.then(null, callback);
+ }
+
+ /**
+ * Add handlers to be called when the Promise object is rejected
+ * @method catch
+ */
+ catch(callback) {
+ return this.then(null, callback);
+ }
+
+ /**
+ * Run the given callbacks after this promise is fulfilled.
+ * @method _thenRunCallbacks
+ * @param optionsOrCallback {} A Backbone-style options callback, or a
+ * callback function. If this is an options object and contains a "model"
+ * attributes, that will be passed to error callbacks as the first argument.
+ * @param model {} If truthy, this will be passed as the first result of
+ * error callbacks. This is for Backbone-compatability.
+ * @return {Parse.Promise} A promise that will be resolved after the
+ * callbacks are run, with the same result as this.
+ */
+ _thenRunCallbacks(optionsOrCallback, model) {
+ var options = {};
+ if (typeof optionsOrCallback === 'function') {
+ options.success = function (result) {
+ optionsOrCallback(result, null);
+ };
+ options.error = function (error) {
+ optionsOrCallback(null, error);
+ };
+ } else if (typeof optionsOrCallback === 'object') {
+ if (typeof optionsOrCallback.success === 'function') {
+ options.success = optionsOrCallback.success;
+ }
+ if (typeof optionsOrCallback.error === 'function') {
+ options.error = optionsOrCallback.error;
+ }
+ }
+
+ return this.then(function (...results) {
+ if (options.success) {
+ options.success.apply(this, results);
+ }
+ return ParsePromise.as.apply(ParsePromise, arguments);
+ }, function (error) {
+ if (options.error) {
+ if (typeof model !== 'undefined') {
+ options.error(model, error);
+ } else {
+ options.error(error);
+ }
+ }
+ // By explicitly returning a rejected Promise, this will work with
+ // either jQuery or Promises/A+ semantics.
+ return ParsePromise.error(error);
+ });
+ }
+
+ /**
+ * Adds a callback function that should be called regardless of whether
+ * this promise failed or succeeded. The callback will be given either the
+ * array of results for its first argument, or the error as its second,
+ * depending on whether this Promise was rejected or resolved. Returns a
+ * new Promise, like "then" would.
+ * @method _continueWith
+ * @param {Function} continuation the callback.
+ */
+ _continueWith(continuation) {
+ return this.then(function (...args) {
+ return continuation(args, null);
+ }, function (error) {
+ return continuation(null, error);
+ });
+ }
+
+ /**
+ * Returns true iff the given object fulfils the Promise interface.
+ * @method is
+ * @param {Object} promise The object to test
+ * @static
+ * @return {Boolean}
+ */
+ static is(promise) {
+ return promise != null && typeof promise.then === 'function';
+ }
+
+ /**
+ * Returns a new promise that is resolved with a given value.
+ * @method as
+ * @param value The value to resolve the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+ static as(...values) {
+ var promise = new ParsePromise();
+ promise.resolve.apply(promise, values);
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is resolved with a given value.
+ * If that value is a thenable Promise (has a .then() prototype
+ * method), the new promise will be chained to the end of the
+ * value.
+ * @method resolve
+ * @param value The value to resolve the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+ static resolve(value) {
+ return new ParsePromise((resolve, reject) => {
+ if (ParsePromise.is(value)) {
+ value.then(resolve, reject);
+ } else {
+ resolve(value);
+ }
+ });
+ }
+
+ /**
+ * Returns a new promise that is rejected with a given error.
+ * @method error
+ * @param error The error to reject the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+ static error(...errors) {
+ var promise = new ParsePromise();
+ promise.reject.apply(promise, errors);
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is rejected with a given error.
+ * This is an alias for Parse.Promise.error, for compliance with
+ * the ES6 implementation.
+ * @method reject
+ * @param error The error to reject the promise with
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+ static reject(...errors) {
+ return ParsePromise.error.apply(null, errors);
+ }
+
+ /**
+ * Returns a new promise that is fulfilled when all of the input promises
+ * are resolved. If any promise in the list fails, then the returned promise
+ * will be rejected with an array containing the error from each promise.
+ * If they all succeed, then the returned promise will succeed, with the
+ * results being the results of all the input
+ * promises. For example:
+ * var p1 = Parse.Promise.as(1);
+ * var p2 = Parse.Promise.as(2);
+ * var p3 = Parse.Promise.as(3);
+ *
+ * Parse.Promise.when(p1, p2, p3).then(function(r1, r2, r3) {
+ * console.log(r1); // prints 1
+ * console.log(r2); // prints 2
+ * console.log(r3); // prints 3
+ * });
+ *
+ * The input promises can also be specified as an array:
+ * var promises = [p1, p2, p3];
+ * Parse.Promise.when(promises).then(function(results) {
+ * console.log(results); // prints [1,2,3]
+ * });
+ *
+ * @method when
+ * @param {Array} promises a list of promises to wait for.
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+ static when(promises) {
+ var objects;
+ var arrayArgument = Array.isArray(promises);
+ if (arrayArgument) {
+ objects = promises;
+ } else {
+ objects = arguments;
+ }
+
+ var total = objects.length;
+ var hadError = false;
+ var results = [];
+ var returnValue = arrayArgument ? [results] : results;
+ var errors = [];
+ results.length = objects.length;
+ errors.length = objects.length;
+
+ if (total === 0) {
+ return ParsePromise.as.apply(this, returnValue);
+ }
+
+ var promise = new ParsePromise();
+
+ var resolveOne = function () {
+ total--;
+ if (total <= 0) {
+ if (hadError) {
+ promise.reject(errors);
+ } else {
+ promise.resolve.apply(promise, returnValue);
+ }
+ }
+ };
+
+ var chain = function (object, index) {
+ if (ParsePromise.is(object)) {
+ object.then(function (result) {
+ results[index] = result;
+ resolveOne();
+ }, function (error) {
+ errors[index] = error;
+ hadError = true;
+ resolveOne();
+ });
+ } else {
+ results[i] = object;
+ resolveOne();
+ }
+ };
+ for (var i = 0; i < objects.length; i++) {
+ chain(objects[i], i);
+ }
+
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is fulfilled when all of the promises in the
+ * iterable argument are resolved. If any promise in the list fails, then
+ * the returned promise will be immediately rejected with the reason that
+ * single promise rejected. If they all succeed, then the returned promise
+ * will succeed, with the results being the results of all the input
+ * promises. If the iterable provided is empty, the returned promise will
+ * be immediately resolved.
+ *
+ * For example:
+ * var p1 = Parse.Promise.as(1);
+ * var p2 = Parse.Promise.as(2);
+ * var p3 = Parse.Promise.as(3);
+ *
+ * Parse.Promise.all([p1, p2, p3]).then(function([r1, r2, r3]) {
+ * console.log(r1); // prints 1
+ * console.log(r2); // prints 2
+ * console.log(r3); // prints 3
+ * });
+ *
+ * @method all
+ * @param {Iterable} promises an iterable of promises to wait for.
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+ static all(promises) {
+ let total = 0;
+ let objects = [];
+
+ for (let p of promises) {
+ objects[total++] = p;
+ }
+
+ if (total === 0) {
+ return ParsePromise.as([]);
+ }
+
+ let hadError = false;
+ let promise = new ParsePromise();
+ let resolved = 0;
+ let results = [];
+ objects.forEach((object, i) => {
+ if (ParsePromise.is(object)) {
+ object.then(result => {
+ if (hadError) {
+ return false;
+ }
+ results[i] = result;
+ resolved++;
+ if (resolved >= total) {
+ promise.resolve(results);
+ }
+ }, error => {
+ // Reject immediately
+ promise.reject(error);
+ hadError = true;
+ });
+ } else {
+ results[i] = object;
+ resolved++;
+ if (!hadError && resolved >= total) {
+ promise.resolve(results);
+ }
+ }
+ });
+
+ return promise;
+ }
+
+ /**
+ * Returns a new promise that is immediately fulfilled when any of the
+ * promises in the iterable argument are resolved or rejected. If the
+ * first promise to complete is resolved, the returned promise will be
+ * resolved with the same value. Likewise, if the first promise to
+ * complete is rejected, the returned promise will be rejected with the
+ * same reason.
+ *
+ * @method race
+ * @param {Iterable} promises an iterable of promises to wait for.
+ * @static
+ * @return {Parse.Promise} the new promise.
+ */
+ static race(promises) {
+ let completed = false;
+ let promise = new ParsePromise();
+ for (let p of promises) {
+ if (ParsePromise.is(p)) {
+ p.then(result => {
+ if (completed) {
+ return;
+ }
+ completed = true;
+ promise.resolve(result);
+ }, error => {
+ if (completed) {
+ return;
+ }
+ completed = true;
+ promise.reject(error);
+ });
+ } else if (!completed) {
+ completed = true;
+ promise.resolve(p);
+ }
+ }
+
+ return promise;
+ }
+
+ /**
+ * Runs the given asyncFunction repeatedly, as long as the predicate
+ * function returns a truthy value. Stops repeating if asyncFunction returns
+ * a rejected promise.
+ * @method _continueWhile
+ * @param {Function} predicate should return false when ready to stop.
+ * @param {Function} asyncFunction should return a Promise.
+ * @static
+ */
+ static _continueWhile(predicate, asyncFunction) {
+ if (predicate()) {
+ return asyncFunction().then(function () {
+ return ParsePromise._continueWhile(predicate, asyncFunction);
+ });
+ }
+ return ParsePromise.as();
+ }
+
+ static isPromisesAPlusCompliant() {
+ return isPromisesAPlusCompliant;
+ }
+
+ static enableAPlusCompliant() {
+ isPromisesAPlusCompliant = true;
+ }
+
+ static disableAPlusCompliant() {
+ isPromisesAPlusCompliant = false;
+ }
+}
\ No newline at end of file
diff --git a/lib/react-native/ParseQuery.js b/lib/react-native/ParseQuery.js
new file mode 100644
index 000000000..a32275dc1
--- /dev/null
+++ b/lib/react-native/ParseQuery.js
@@ -0,0 +1,1113 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ *
+ */
+
+import CoreManager from './CoreManager';
+import encode from './encode';
+import ParseError from './ParseError';
+import ParseGeoPoint from './ParseGeoPoint';
+import ParseObject from './ParseObject';
+import ParsePromise from './ParsePromise';
+
+/**
+ * Converts a string into a regex that matches it.
+ * Surrounding with \Q .. \E does this, we just need to escape any \E's in
+ * the text separately.
+ */
+function quote(s) {
+ return '\\Q' + s.replace('\\E', '\\E\\\\E\\Q') + '\\E';
+}
+
+/**
+ * Handles pre-populating the result data of a query with select fields,
+ * making sure that the data object contains keys for all objects that have
+ * been requested with a select, so that our cached state updates correctly.
+ */
+function handleSelectResult(data, select) {
+ var serverDataMask = {};
+
+ select.forEach(field => {
+ let hasSubObjectSelect = field.indexOf(".") !== -1;
+ if (!hasSubObjectSelect && !data.hasOwnProperty(field)) {
+ // this field was selected, but is missing from the retrieved data
+ data[field] = undefined;
+ } else if (hasSubObjectSelect) {
+ // this field references a sub-object,
+ // so we need to walk down the path components
+ let pathComponents = field.split(".");
+ var obj = data;
+ var serverMask = serverDataMask;
+
+ pathComponents.forEach((component, index, arr) => {
+ // add keys if the expected data is missing
+ if (!obj[component]) {
+ obj[component] = index == arr.length - 1 ? undefined : {};
+ }
+ obj = obj[component];
+
+ //add this path component to the server mask so we can fill it in later if needed
+ if (index < arr.length - 1) {
+ if (!serverMask[component]) {
+ serverMask[component] = {};
+ }
+ }
+ });
+ }
+ });
+
+ if (Object.keys(serverDataMask).length > 0) {
+ // When selecting from sub-objects, we don't want to blow away the missing
+ // information that we may have retrieved before. We've already added any
+ // missing selected keys to sub-objects, but we still need to add in the
+ // data for any previously retrieved sub-objects that were not selected.
+
+ let serverData = CoreManager.getObjectStateController().getServerData({ id: data.objectId, className: data.className });
+
+ function copyMissingDataWithMask(src, dest, mask, copyThisLevel) {
+ //copy missing elements at this level
+ if (copyThisLevel) {
+ for (var key in src) {
+ if (src.hasOwnProperty(key) && !dest.hasOwnProperty(key)) {
+ dest[key] = src[key];
+ }
+ }
+ }
+ for (var key in mask) {
+ //traverse into objects as needed
+ copyMissingDataWithMask(src[key], dest[key], mask[key], true);
+ }
+ }
+
+ copyMissingDataWithMask(serverData, data, serverDataMask, false);
+ }
+}
+
+/**
+ * Creates a new parse Parse.Query for the given Parse.Object subclass.
+ * @class Parse.Query
+ * @constructor
+ * @param {} objectClass An instance of a subclass of Parse.Object, or a Parse className string.
+ *
+ * Parse.Query defines a query that is used to fetch Parse.Objects. The
+ * most common use case is finding all objects that match a query through the
+ * find method. For example, this sample code fetches all objects
+ * of class MyClass. It calls a different function depending on
+ * whether the fetch succeeded or not.
+ *
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.find({
+ * success: function(results) {
+ * // results is an array of Parse.Object.
+ * },
+ *
+ * error: function(error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ *
+ * A Parse.Query can also be used to retrieve a single object whose id is
+ * known, through the get method. For example, this sample code fetches an
+ * object of class MyClass and id myId. It calls a
+ * different function depending on whether the fetch succeeded or not.
+ *
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.get(myId, {
+ * success: function(object) {
+ * // object is an instance of Parse.Object.
+ * },
+ *
+ * error: function(object, error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ *
+ * A Parse.Query can also be used to count the number of objects that match
+ * the query without retrieving all of those objects. For example, this
+ * sample code counts the number of objects of the class MyClass
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.count({
+ * success: function(number) {
+ * // There are number instances of MyClass.
+ * },
+ *
+ * error: function(error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ */
+export default class ParseQuery {
+
+ constructor(objectClass) {
+ if (typeof objectClass === 'string') {
+ if (objectClass === 'User' && CoreManager.get('PERFORM_USER_REWRITE')) {
+ this.className = '_User';
+ } else {
+ this.className = objectClass;
+ }
+ } else if (objectClass instanceof ParseObject) {
+ this.className = objectClass.className;
+ } else if (typeof objectClass === 'function') {
+ if (typeof objectClass.className === 'string') {
+ this.className = objectClass.className;
+ } else {
+ var obj = new objectClass();
+ this.className = obj.className;
+ }
+ } else {
+ throw new TypeError('A ParseQuery must be constructed with a ParseObject or class name.');
+ }
+
+ this._where = {};
+ this._include = [];
+ this._limit = -1; // negative limit is not sent in the server request
+ this._skip = 0;
+ this._extraOptions = {};
+ }
+
+ /**
+ * Adds constraint that at least one of the passed in queries matches.
+ * @method _orQuery
+ * @param {Array} queries
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ _orQuery(queries) {
+ var queryJSON = queries.map(q => {
+ return q.toJSON().where;
+ });
+
+ this._where.$or = queryJSON;
+ return this;
+ }
+
+ /**
+ * Helper for condition queries
+ */
+ _addCondition(key, condition, value) {
+ if (!this._where[key] || typeof this._where[key] === 'string') {
+ this._where[key] = {};
+ }
+ this._where[key][condition] = encode(value, false, true);
+ return this;
+ }
+
+ /**
+ * Converts string for regular expression at the beginning
+ */
+ _regexStartWith(string) {
+ return '^' + quote(string);
+ }
+
+ /**
+ * Returns a JSON representation of this query.
+ * @method toJSON
+ * @return {Object} The JSON representation of the query.
+ */
+ toJSON() {
+ var params = {
+ where: this._where
+ };
+
+ if (this._include.length) {
+ params.include = this._include.join(',');
+ }
+ if (this._select) {
+ params.keys = this._select.join(',');
+ }
+ if (this._limit >= 0) {
+ params.limit = this._limit;
+ }
+ if (this._skip > 0) {
+ params.skip = this._skip;
+ }
+ if (this._order) {
+ params.order = this._order.join(',');
+ }
+ for (var key in this._extraOptions) {
+ params[key] = this._extraOptions[key];
+ }
+
+ return params;
+ }
+
+ /**
+ * Constructs a Parse.Object whose id is already known by fetching data from
+ * the server. Either options.success or options.error is called when the
+ * find completes.
+ *
+ * @method get
+ * @param {String} objectId The id of the object to be fetched.
+ * @param {Object} options A Backbone-style options object.
+ * Valid options are:var compoundQuery = Parse.Query.or(query1, query2, query3);+ * + * will create a compoundQuery that is an or of the query1, query2, and + * query3. + * @method or + * @param {...Parse.Query} var_args The list of queries to OR. + * @static + * @return {Parse.Query} The query that is the OR of the passed in queries. + */ + static or(...queries) { + var className = null; + queries.forEach(q => { + if (!className) { + className = q.className; + } + + if (className !== q.className) { + throw new Error('All queries must be for the same class.'); + } + }); + + var query = new ParseQuery(className); + query._orQuery(queries); + return query; + } +} + +var DefaultController = { + find(className, params, options) { + var RESTController = CoreManager.getRESTController(); + + return RESTController.request('GET', 'classes/' + className, params, options); + } +}; + +CoreManager.setQueryController(DefaultController); \ No newline at end of file diff --git a/lib/react-native/ParseRelation.js b/lib/react-native/ParseRelation.js new file mode 100644 index 000000000..c07b48cc1 --- /dev/null +++ b/lib/react-native/ParseRelation.js @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +import { RelationOp } from './ParseOp'; +import ParseObject from './ParseObject'; +import ParseQuery from './ParseQuery'; + +/** + * Creates a new Relation for the given parent object and key. This + * constructor should rarely be used directly, but rather created by + * Parse.Object.relation. + * @class Parse.Relation + * @constructor + * @param {Parse.Object} parent The parent of this relation. + * @param {String} key The key for this relation on the parent. + * + *
+ * A class that is used to access all of the children of a many-to-many + * relationship. Each instance of Parse.Relation is associated with a + * particular parent object and key. + *
+ */ +export default class ParseRelation { + + constructor(parent, key) { + this.parent = parent; + this.key = key; + this.targetClassName = null; + } + + /** + * Makes sure that this relation has the right parent and key. + */ + _ensureParentAndKey(parent, key) { + this.key = this.key || key; + if (this.key !== key) { + throw new Error('Internal Error. Relation retrieved from two different keys.'); + } + if (this.parent) { + if (this.parent.className !== parent.className) { + throw new Error('Internal Error. Relation retrieved from two different Objects.'); + } + if (this.parent.id) { + if (this.parent.id !== parent.id) { + throw new Error('Internal Error. Relation retrieved from two different Objects.'); + } + } else if (parent.id) { + this.parent = parent; + } + } else { + this.parent = parent; + } + } + + /** + * Adds a Parse.Object or an array of Parse.Objects to the relation. + * @method add + * @param {} objects The item or items to add. + */ + add(objects) { + if (!Array.isArray(objects)) { + objects = [objects]; + } + + var change = new RelationOp(objects, []); + var parent = this.parent; + if (!parent) { + throw new Error('Cannot add to a Relation without a parent'); + } + parent.set(this.key, change); + this.targetClassName = change._targetClassName; + return parent; + } + + /** + * Removes a Parse.Object or an array of Parse.Objects from this relation. + * @method remove + * @param {} objects The item or items to remove. + */ + remove(objects) { + if (!Array.isArray(objects)) { + objects = [objects]; + } + + var change = new RelationOp([], objects); + if (!this.parent) { + throw new Error('Cannot remove from a Relation without a parent'); + } + this.parent.set(this.key, change); + this.targetClassName = change._targetClassName; + } + + /** + * Returns a JSON version of the object suitable for saving to disk. + * @method toJSON + * @return {Object} + */ + toJSON() { + return { + __type: 'Relation', + className: this.targetClassName + }; + } + + /** + * Returns a Parse.Query that is limited to objects in this + * relation. + * @method query + * @return {Parse.Query} + */ + query() { + var query; + var parent = this.parent; + if (!parent) { + throw new Error('Cannot construct a query for a Relation without a parent'); + } + if (!this.targetClassName) { + query = new ParseQuery(parent.className); + query._extraOptions.redirectClassNameForKey = this.key; + } else { + query = new ParseQuery(this.targetClassName); + } + query._addCondition('$relatedTo', 'object', { + __type: 'Pointer', + className: parent.className, + objectId: parent.id + }); + query._addCondition('$relatedTo', 'key', this.key); + + return query; + } +} \ No newline at end of file diff --git a/lib/react-native/ParseRole.js b/lib/react-native/ParseRole.js new file mode 100644 index 000000000..04164b2e4 --- /dev/null +++ b/lib/react-native/ParseRole.js @@ -0,0 +1,133 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +import ParseACL from './ParseACL'; +import ParseError from './ParseError'; +import ParseObject from './ParseObject'; + +/** + * Represents a Role on the Parse server. Roles represent groupings of + * Users for the purposes of granting permissions (e.g. specifying an ACL + * for an Object). Roles are specified by their sets of child users and + * child roles, all of which are granted any permissions that the parent + * role has. + * + *Roles must have a name (which cannot be changed after creation of the + * role), and must specify an ACL.
+ * @class Parse.Role + * @constructor + * @param {String} name The name of the Role to create. + * @param {Parse.ACL} acl The ACL for this role. Roles must have an ACL. + * A Parse.Role is a local representation of a role persisted to the Parse + * cloud. + */ +export default class ParseRole extends ParseObject { + constructor(name, acl) { + super('_Role'); + if (typeof name === 'string' && acl instanceof ParseACL) { + this.setName(name); + this.setACL(acl); + } + } + + /** + * Gets the name of the role. You can alternatively call role.get("name") + * + * @method getName + * @return {String} the name of the role. + */ + getName() { + const name = this.get('name'); + if (name == null || typeof name === 'string') { + return name; + } + return ''; + } + + /** + * Sets the name for a role. This value must be set before the role has + * been saved to the server, and cannot be set once the role has been + * saved. + * + *+ * A role's name can only contain alphanumeric characters, _, -, and + * spaces. + *
+ * + *This is equivalent to calling role.set("name", name)
+ * + * @method setName + * @param {String} name The name of the role. + * @param {Object} options Standard options object with success and error + * callbacks. + */ + setName(name, options) { + return this.set('name', name, options); + } + + /** + * Gets the Parse.Relation for the Parse.Users that are direct + * children of this role. These users are granted any privileges that this + * role has been granted (e.g. read or write access through ACLs). You can + * add or remove users from the role through this relation. + * + *This is equivalent to calling role.relation("users")
+ * + * @method getUsers + * @return {Parse.Relation} the relation for the users belonging to this + * role. + */ + getUsers() { + return this.relation('users'); + } + + /** + * Gets the Parse.Relation for the Parse.Roles that are direct + * children of this role. These roles' users are granted any privileges that + * this role has been granted (e.g. read or write access through ACLs). You + * can add or remove child roles from this role through this relation. + * + *This is equivalent to calling role.relation("roles")
+ * + * @method getRoles + * @return {Parse.Relation} the relation for the roles belonging to this + * role. + */ + getRoles() { + return this.relation('roles'); + } + + validate(attrs, options) { + var isInvalid = super.validate(attrs, options); + if (isInvalid) { + return isInvalid; + } + + if ('name' in attrs && attrs.name !== this.getName()) { + var newName = attrs.name; + if (this.id && this.id !== attrs.objectId) { + // Check to see if the objectId being set matches this.id + // This happens during a fetch -- the id is set before calling fetch + // Let the name be set in this case + return new ParseError(ParseError.OTHER_CAUSE, 'A role\'s name can only be set before it has been saved.'); + } + if (typeof newName !== 'string') { + return new ParseError(ParseError.OTHER_CAUSE, 'A role\'s name must be a String.'); + } + if (!/^[0-9a-zA-Z\-_ ]+$/.test(newName)) { + return new ParseError(ParseError.OTHER_CAUSE, 'A role\'s name can be only contain alphanumeric characters, _, ' + '-, and spaces.'); + } + } + return false; + } +} + +ParseObject.registerSubclass('_Role', ParseRole); \ No newline at end of file diff --git a/lib/react-native/ParseSession.js b/lib/react-native/ParseSession.js new file mode 100644 index 000000000..f1554fc9a --- /dev/null +++ b/lib/react-native/ParseSession.js @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +import CoreManager from './CoreManager'; +import isRevocableSession from './isRevocableSession'; +import ParseObject from './ParseObject'; +import ParsePromise from './ParsePromise'; +import ParseUser from './ParseUser'; + +/** + * @class Parse.Session + * @constructor + * + *A Parse.Session object is a local representation of a revocable session. + * This class is a subclass of a Parse.Object, and retains the same + * functionality of a Parse.Object.
+ */ +export default class ParseSession extends ParseObject { + constructor(attributes) { + super('_Session'); + if (attributes && typeof attributes === 'object') { + if (!this.set(attributes || {})) { + throw new Error('Can\'t create an invalid Session'); + } + } + } + + /** + * Returns the session token string. + * @method getSessionToken + * @return {String} + */ + getSessionToken() { + const token = this.get('sessionToken'); + if (typeof token === 'string') { + return token; + } + return ''; + } + + static readOnlyAttributes() { + return ['createdWith', 'expiresAt', 'installationId', 'restricted', 'sessionToken', 'user']; + } + + /** + * Retrieves the Session object for the currently logged in session. + * @method current + * @static + * @return {Parse.Promise} A promise that is resolved with the Parse.Session + * object after it has been fetched. If there is no current user, the + * promise will be rejected. + */ + static current(options) { + options = options || {}; + var controller = CoreManager.getSessionController(); + + var sessionOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + sessionOptions.useMasterKey = options.useMasterKey; + } + return ParseUser.currentAsync().then(user => { + if (!user) { + return ParsePromise.error('There is no current user.'); + } + user.getSessionToken(); + + sessionOptions.sessionToken = user.getSessionToken(); + return controller.getSession(sessionOptions); + }); + } + + /** + * Determines whether the current session token is revocable. + * This method is useful for migrating Express.js or Node.js web apps to + * use revocable sessions. If you are migrating an app that uses the Parse + * SDK in the browser only, please use Parse.User.enableRevocableSession() + * instead, so that sessions can be automatically upgraded. + * @method isCurrentSessionRevocable + * @static + * @return {Boolean} + */ + static isCurrentSessionRevocable() { + var currentUser = ParseUser.current(); + if (currentUser) { + return isRevocableSession(currentUser.getSessionToken() || ''); + } + return false; + } +} + +ParseObject.registerSubclass('_Session', ParseSession); + +var DefaultController = { + getSession(options) { + var RESTController = CoreManager.getRESTController(); + var session = new ParseSession(); + + return RESTController.request('GET', 'sessions/me', {}, options).then(sessionData => { + session._finishFetch(sessionData); + session._setExisted(true); + return session; + }); + } +}; + +CoreManager.setSessionController(DefaultController); \ No newline at end of file diff --git a/lib/react-native/ParseUser.js b/lib/react-native/ParseUser.js new file mode 100644 index 000000000..129c4cfbb --- /dev/null +++ b/lib/react-native/ParseUser.js @@ -0,0 +1,952 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +import CoreManager from './CoreManager'; +import isRevocableSession from './isRevocableSession'; +import ParseError from './ParseError'; +import ParseObject from './ParseObject'; +import ParsePromise from './ParsePromise'; +import ParseSession from './ParseSession'; +import Storage from './Storage'; + +var CURRENT_USER_KEY = 'currentUser'; +var canUseCurrentUser = !CoreManager.get('IS_NODE'); +var currentUserCacheMatchesDisk = false; +var currentUserCache = null; + +var authProviders = {}; + +/** + * @class Parse.User + * @constructor + * + *A Parse.User object is a local representation of a user persisted to the + * Parse cloud. This class is a subclass of a Parse.Object, and retains the + * same functionality of a Parse.Object, but also extends it with various + * user specific methods, like authentication, signing up, and validation of + * uniqueness.
+ */ +export default class ParseUser extends ParseObject { + constructor(attributes) { + super('_User'); + if (attributes && typeof attributes === 'object') { + if (!this.set(attributes || {})) { + throw new Error('Can\'t create an invalid Parse User'); + } + } + } + + /** + * Request a revocable session token to replace the older style of token. + * @method _upgradeToRevocableSession + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is resolved when the replacement + * token has been fetched. + */ + _upgradeToRevocableSession(options) { + options = options || {}; + + var upgradeOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + upgradeOptions.useMasterKey = options.useMasterKey; + } + + var controller = CoreManager.getUserController(); + return controller.upgradeToRevocableSession(this, upgradeOptions)._thenRunCallbacks(options); + } + + /** + * Unlike in the Android/iOS SDKs, logInWith is unnecessary, since you can + * call linkWith on the user (even if it doesn't exist yet on the server). + * @method _linkWith + */ + _linkWith(provider, options) { + var authType; + if (typeof provider === 'string') { + authType = provider; + provider = authProviders[provider]; + } else { + authType = provider.getAuthType(); + } + if (options && options.hasOwnProperty('authData')) { + var authData = this.get('authData') || {}; + if (typeof authData !== 'object') { + throw new Error('Invalid type: authData field should be an object'); + } + authData[authType] = options.authData; + + var controller = CoreManager.getUserController(); + return controller.linkWith(this, authData)._thenRunCallbacks(options, this); + } else { + var promise = new ParsePromise(); + provider.authenticate({ + success: (provider, result) => { + var opts = {}; + opts.authData = result; + if (options.success) { + opts.success = options.success; + } + if (options.error) { + opts.error = options.error; + } + this._linkWith(provider, opts).then(() => { + promise.resolve(this); + }, error => { + promise.reject(error); + }); + }, + error: (provider, error) => { + if (typeof options.error === 'function') { + options.error(this, error); + } + promise.reject(error); + } + }); + return promise; + } + } + + /** + * Synchronizes auth data for a provider (e.g. puts the access token in the + * right place to be used by the Facebook SDK). + * @method _synchronizeAuthData + */ + _synchronizeAuthData(provider) { + if (!this.isCurrent() || !provider) { + return; + } + var authType; + if (typeof provider === 'string') { + authType = provider; + provider = authProviders[authType]; + } else { + authType = provider.getAuthType(); + } + var authData = this.get('authData'); + if (!provider || !authData || typeof authData !== 'object') { + return; + } + var success = provider.restoreAuthentication(authData[authType]); + if (!success) { + this._unlinkFrom(provider); + } + } + + /** + * Synchronizes authData for all providers. + * @method _synchronizeAllAuthData + */ + _synchronizeAllAuthData() { + var authData = this.get('authData'); + if (typeof authData !== 'object') { + return; + } + + for (var key in authData) { + this._synchronizeAuthData(key); + } + } + + /** + * Removes null values from authData (which exist temporarily for + * unlinking) + * @method _cleanupAuthData + */ + _cleanupAuthData() { + if (!this.isCurrent()) { + return; + } + var authData = this.get('authData'); + if (typeof authData !== 'object') { + return; + } + + for (var key in authData) { + if (!authData[key]) { + delete authData[key]; + } + } + } + + /** + * Unlinks a user from a service. + * @method _unlinkFrom + */ + _unlinkFrom(provider, options) { + if (typeof provider === 'string') { + provider = authProviders[provider]; + } else { + provider.getAuthType(); + } + return this._linkWith(provider, { authData: null }).then(() => { + this._synchronizeAuthData(provider); + return ParsePromise.as(this); + })._thenRunCallbacks(options); + } + + /** + * Checks whether a user is linked to a service. + * @method _isLinked + */ + _isLinked(provider) { + var authType; + if (typeof provider === 'string') { + authType = provider; + } else { + authType = provider.getAuthType(); + } + var authData = this.get('authData') || {}; + if (typeof authData !== 'object') { + return false; + } + return !!authData[authType]; + } + + /** + * Deauthenticates all providers. + * @method _logOutWithAll + */ + _logOutWithAll() { + var authData = this.get('authData'); + if (typeof authData !== 'object') { + return; + } + + for (var key in authData) { + this._logOutWith(key); + } + } + + /** + * Deauthenticates a single provider (e.g. removing access tokens from the + * Facebook SDK). + * @method _logOutWith + */ + _logOutWith(provider) { + if (!this.isCurrent()) { + return; + } + if (typeof provider === 'string') { + provider = authProviders[provider]; + } + if (provider && provider.deauthenticate) { + provider.deauthenticate(); + } + } + + /** + * Class instance method used to maintain specific keys when a fetch occurs. + * Used to ensure that the session token is not lost. + */ + _preserveFieldsOnFetch() { + return { + sessionToken: this.get('sessionToken') + }; + } + + /** + * Returns true ifcurrent would return this user.
+ * @method isCurrent
+ * @return {Boolean}
+ */
+ isCurrent() {
+ var current = ParseUser.current();
+ return !!current && current.id === this.id;
+ }
+
+ /**
+ * Returns get("username").
+ * @method getUsername
+ * @return {String}
+ */
+ getUsername() {
+ const username = this.get('username');
+ if (username == null || typeof username === 'string') {
+ return username;
+ }
+ return '';
+ }
+
+ /**
+ * Calls set("username", username, options) and returns the result.
+ * @method setUsername
+ * @param {String} username
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ */
+ setUsername(username) {
+ // Strip anonymity, even we do not support anonymous user in js SDK, we may
+ // encounter anonymous user created by android/iOS in cloud code.
+ var authData = this.get('authData');
+ if (authData && typeof authData === 'object' && authData.hasOwnProperty('anonymous')) {
+ // We need to set anonymous to null instead of deleting it in order to remove it from Parse.
+ authData.anonymous = null;
+ }
+ this.set('username', username);
+ }
+
+ /**
+ * Calls set("password", password, options) and returns the result.
+ * @method setPassword
+ * @param {String} password
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ */
+ setPassword(password) {
+ this.set('password', password);
+ }
+
+ /**
+ * Returns get("email").
+ * @method getEmail
+ * @return {String}
+ */
+ getEmail() {
+ const email = this.get('email');
+ if (email == null || typeof email === 'string') {
+ return email;
+ }
+ return '';
+ }
+
+ /**
+ * Calls set("email", email, options) and returns the result.
+ * @method setEmail
+ * @param {String} email
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ */
+ setEmail(email) {
+ this.set('email', email);
+ }
+
+ /**
+ * Returns the session token for this user, if the user has been logged in,
+ * or if it is the result of a query with the master key. Otherwise, returns
+ * undefined.
+ * @method getSessionToken
+ * @return {String} the session token, or undefined
+ */
+ getSessionToken() {
+ const token = this.get('sessionToken');
+ if (token == null || typeof token === 'string') {
+ return token;
+ }
+ return '';
+ }
+
+ /**
+ * Checks whether this user is the current user and has been authenticated.
+ * @method authenticated
+ * @return (Boolean) whether this user is the current user and is logged in.
+ */
+ authenticated() {
+ var current = ParseUser.current();
+ return !!this.get('sessionToken') && !!current && current.id === this.id;
+ }
+
+ /**
+ * Signs up a new user. You should call this instead of save for
+ * new Parse.Users. This will create a new Parse.User on the server, and
+ * also persist the session on disk so that you can access the user using
+ * current.
+ *
+ * A username and password must be set before calling signUp.
+ * + *Calls options.success or options.error on completion.
+ * + * @method signUp + * @param {Object} attrs Extra fields to set on the new user, or null. + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is fulfilled when the signup + * finishes. + */ + signUp(attrs, options) { + options = options || {}; + + var signupOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + signupOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('installationId')) { + signupOptions.installationId = options.installationId; + } + + var controller = CoreManager.getUserController(); + return controller.signUp(this, attrs, signupOptions)._thenRunCallbacks(options, this); + } + + /** + * Logs in a Parse.User. On success, this saves the session to disk, + * so you can retrieve the currently logged in user using + *current.
+ *
+ * A username and password must be set before calling logIn.
+ * + *Calls options.success or options.error on completion.
+ * + * @method logIn + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login is complete. + */ + logIn(options) { + options = options || {}; + + var loginOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + loginOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('installationId')) { + loginOptions.installationId = options.installationId; + } + + var controller = CoreManager.getUserController(); + return controller.logIn(this, loginOptions)._thenRunCallbacks(options, this); + } + + /** + * Wrap the default save behavior with functionality to save to local + * storage if this is current user. + */ + save(...args) { + return super.save.apply(this, args).then(() => { + if (this.isCurrent()) { + return CoreManager.getUserController().updateUserOnDisk(this); + } + return this; + }); + } + + /** + * Wrap the default destroy behavior with functionality that logs out + * the current user when it is destroyed + */ + destroy(...args) { + return super.destroy.apply(this, args).then(() => { + if (this.isCurrent()) { + return CoreManager.getUserController().removeUserFromDisk(); + } + return this; + }); + } + + /** + * Wrap the default fetch behavior with functionality to save to local + * storage if this is current user. + */ + fetch(...args) { + return super.fetch.apply(this, args).then(() => { + if (this.isCurrent()) { + return CoreManager.getUserController().updateUserOnDisk(this); + } + return this; + }); + } + + static readOnlyAttributes() { + return ['sessionToken']; + } + + /** + * Adds functionality to the existing Parse.User class + * @method extend + * @param {Object} protoProps A set of properties to add to the prototype + * @param {Object} classProps A set of static properties to add to the class + * @static + * @return {Class} The newly extended Parse.User class + */ + static extend(protoProps, classProps) { + if (protoProps) { + for (var prop in protoProps) { + if (prop !== 'className') { + Object.defineProperty(ParseUser.prototype, prop, { + value: protoProps[prop], + enumerable: false, + writable: true, + configurable: true + }); + } + } + } + + if (classProps) { + for (var prop in classProps) { + if (prop !== 'className') { + Object.defineProperty(ParseUser, prop, { + value: classProps[prop], + enumerable: false, + writable: true, + configurable: true + }); + } + } + } + + return ParseUser; + } + + /** + * Retrieves the currently logged in ParseUser with a valid session, + * either from memory or localStorage, if necessary. + * @method current + * @static + * @return {Parse.Object} The currently logged in Parse.User. + */ + static current() { + if (!canUseCurrentUser) { + return null; + } + var controller = CoreManager.getUserController(); + return controller.currentUser(); + } + + /** + * Retrieves the currently logged in ParseUser from asynchronous Storage. + * @method currentAsync + * @static + * @return {Parse.Promise} A Promise that is resolved with the currently + * logged in Parse User + */ + static currentAsync() { + if (!canUseCurrentUser) { + return ParsePromise.as(null); + } + var controller = CoreManager.getUserController(); + return controller.currentUserAsync(); + } + + /** + * Signs up a new user with a username (or email) and password. + * This will create a new Parse.User on the server, and also persist the + * session in localStorage so that you can access the user using + * {@link #current}. + * + *Calls options.success or options.error on completion.
+ * + * @method signUp + * @param {String} username The username (or email) to sign up with. + * @param {String} password The password to sign up with. + * @param {Object} attrs Extra fields to set on the new user. + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the signup completes. + */ + static signUp(username, password, attrs, options) { + attrs = attrs || {}; + attrs.username = username; + attrs.password = password; + var user = new ParseUser(attrs); + return user.signUp({}, options); + } + + /** + * Logs in a user with a username (or email) and password. On success, this + * saves the session to disk, so you can retrieve the currently logged in + * user usingcurrent.
+ *
+ * Calls options.success or options.error on completion.
+ * + * @method logIn + * @param {String} username The username (or email) to log in with. + * @param {String} password The password to log in with. + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login completes. + */ + static logIn(username, password, options) { + if (typeof username !== 'string') { + return ParsePromise.error(new ParseError(ParseError.OTHER_CAUSE, 'Username must be a string.')); + } else if (typeof password !== 'string') { + return ParsePromise.error(new ParseError(ParseError.OTHER_CAUSE, 'Password must be a string.')); + } + var user = new ParseUser(); + user._finishFetch({ username: username, password: password }); + return user.logIn(options); + } + + /** + * Logs in a user with a session token. On success, this saves the session + * to disk, so you can retrieve the currently logged in user using + *current.
+ *
+ * Calls options.success or options.error on completion.
+ * + * @method become + * @param {String} sessionToken The sessionToken to log in with. + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login completes. + */ + static become(sessionToken, options) { + if (!canUseCurrentUser) { + throw new Error('It is not memory-safe to become a user in a server environment'); + } + options = options || {}; + + var becomeOptions = { + sessionToken: sessionToken + }; + if (options.hasOwnProperty('useMasterKey')) { + becomeOptions.useMasterKey = options.useMasterKey; + } + + var controller = CoreManager.getUserController(); + return controller.become(becomeOptions)._thenRunCallbacks(options); + } + + static logInWith(provider, options) { + return ParseUser._logInWith(provider, options); + } + + /** + * Logs out the currently logged in user session. This will remove the + * session from disk, log out of linked services, and future calls to + *current will return null.
+ * @method logOut
+ * @static
+ * @return {Parse.Promise} A promise that is resolved when the session is
+ * destroyed on the server.
+ */
+ static logOut() {
+ if (!canUseCurrentUser) {
+ throw new Error('There is no current user user on a node.js server environment.');
+ }
+
+ var controller = CoreManager.getUserController();
+ return controller.logOut();
+ }
+
+ /**
+ * Requests a password reset email to be sent to the specified email address
+ * associated with the user account. This email allows the user to securely
+ * reset their password on the Parse site.
+ *
+ * Calls options.success or options.error on completion.
+ * + * @method requestPasswordReset + * @param {String} email The email address associated with the user that + * forgot their password. + * @param {Object} options A Backbone-style options object. + * @static + */ + static requestPasswordReset(email, options) { + options = options || {}; + + var requestOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + requestOptions.useMasterKey = options.useMasterKey; + } + + var controller = CoreManager.getUserController(); + return controller.requestPasswordReset(email, requestOptions)._thenRunCallbacks(options); + } + + /** + * Allow someone to define a custom User class without className + * being rewritten to _User. The default behavior is to rewrite + * User to _User for legacy reasons. This allows developers to + * override that behavior. + * + * @method allowCustomUserClass + * @param {Boolean} isAllowed Whether or not to allow custom User class + * @static + */ + static allowCustomUserClass(isAllowed) { + CoreManager.set('PERFORM_USER_REWRITE', !isAllowed); + } + + /** + * Allows a legacy application to start using revocable sessions. If the + * current session token is not revocable, a request will be made for a new, + * revocable session. + * It is not necessary to call this method from cloud code unless you are + * handling user signup or login from the server side. In a cloud code call, + * this function will not attempt to upgrade the current token. + * @method enableRevocableSession + * @param {Object} options A Backbone-style options object. + * @static + * @return {Parse.Promise} A promise that is resolved when the process has + * completed. If a replacement session token is requested, the promise + * will be resolved after a new token has been fetched. + */ + static enableRevocableSession(options) { + options = options || {}; + CoreManager.set('FORCE_REVOCABLE_SESSION', true); + if (canUseCurrentUser) { + var current = ParseUser.current(); + if (current) { + return current._upgradeToRevocableSession(options); + } + } + return ParsePromise.as()._thenRunCallbacks(options); + } + + /** + * Enables the use of become or the current user in a server + * environment. These features are disabled by default, since they depend on + * global objects that are not memory-safe for most servers. + * @method enableUnsafeCurrentUser + * @static + */ + static enableUnsafeCurrentUser() { + canUseCurrentUser = true; + } + + /** + * Disables the use of become or the current user in any environment. + * These features are disabled on servers by default, since they depend on + * global objects that are not memory-safe for most servers. + * @method disableUnsafeCurrentUser + * @static + */ + static disableUnsafeCurrentUser() { + canUseCurrentUser = false; + } + + static _registerAuthenticationProvider(provider) { + authProviders[provider.getAuthType()] = provider; + // Synchronize the current user with the auth provider. + ParseUser.currentAsync().then(current => { + if (current) { + current._synchronizeAuthData(provider.getAuthType()); + } + }); + } + + static _logInWith(provider, options) { + var user = new ParseUser(); + return user._linkWith(provider, options); + } + + static _clearCache() { + currentUserCache = null; + currentUserCacheMatchesDisk = false; + } + + static _setCurrentUserCache(user) { + currentUserCache = user; + } +} + +ParseObject.registerSubclass('_User', ParseUser); + +var DefaultController = { + updateUserOnDisk(user) { + var path = Storage.generatePath(CURRENT_USER_KEY); + var json = user.toJSON(); + json.className = '_User'; + return Storage.setItemAsync(path, JSON.stringify(json)).then(() => { + return user; + }); + }, + + removeUserFromDisk() { + let path = Storage.generatePath(CURRENT_USER_KEY); + currentUserCacheMatchesDisk = true; + currentUserCache = null; + return Storage.removeItemAsync(path); + }, + + setCurrentUser(user) { + currentUserCache = user; + user._cleanupAuthData(); + user._synchronizeAllAuthData(); + return DefaultController.updateUserOnDisk(user); + }, + + currentUser() { + if (currentUserCache) { + return currentUserCache; + } + if (currentUserCacheMatchesDisk) { + return null; + } + if (Storage.async()) { + throw new Error('Cannot call currentUser() when using a platform with an async ' + 'storage system. Call currentUserAsync() instead.'); + } + var path = Storage.generatePath(CURRENT_USER_KEY); + var userData = Storage.getItem(path); + currentUserCacheMatchesDisk = true; + if (!userData) { + currentUserCache = null; + return null; + } + userData = JSON.parse(userData); + if (!userData.className) { + userData.className = '_User'; + } + if (userData._id) { + if (userData.objectId !== userData._id) { + userData.objectId = userData._id; + } + delete userData._id; + } + if (userData._sessionToken) { + userData.sessionToken = userData._sessionToken; + delete userData._sessionToken; + } + var current = ParseObject.fromJSON(userData); + currentUserCache = current; + current._synchronizeAllAuthData(); + return current; + }, + + currentUserAsync() { + if (currentUserCache) { + return ParsePromise.as(currentUserCache); + } + if (currentUserCacheMatchesDisk) { + return ParsePromise.as(null); + } + var path = Storage.generatePath(CURRENT_USER_KEY); + return Storage.getItemAsync(path).then(userData => { + currentUserCacheMatchesDisk = true; + if (!userData) { + currentUserCache = null; + return ParsePromise.as(null); + } + userData = JSON.parse(userData); + if (!userData.className) { + userData.className = '_User'; + } + if (userData._id) { + if (userData.objectId !== userData._id) { + userData.objectId = userData._id; + } + delete userData._id; + } + if (userData._sessionToken) { + userData.sessionToken = userData._sessionToken; + delete userData._sessionToken; + } + var current = ParseObject.fromJSON(userData); + currentUserCache = current; + current._synchronizeAllAuthData(); + return ParsePromise.as(current); + }); + }, + + signUp(user, attrs, options) { + var username = attrs && attrs.username || user.get('username'); + var password = attrs && attrs.password || user.get('password'); + + if (!username || !username.length) { + return ParsePromise.error(new ParseError(ParseError.OTHER_CAUSE, 'Cannot sign up user with an empty name.')); + } + if (!password || !password.length) { + return ParsePromise.error(new ParseError(ParseError.OTHER_CAUSE, 'Cannot sign up user with an empty password.')); + } + + return user.save(attrs, options).then(() => { + // Clear the password field + user._finishFetch({ password: undefined }); + + if (canUseCurrentUser) { + return DefaultController.setCurrentUser(user); + } + return user; + }); + }, + + logIn(user, options) { + var RESTController = CoreManager.getRESTController(); + var stateController = CoreManager.getObjectStateController(); + var auth = { + username: user.get('username'), + password: user.get('password') + }; + return RESTController.request('GET', 'login', auth, options).then((response, status) => { + user._migrateId(response.objectId); + user._setExisted(true); + stateController.setPendingOp(user._getStateIdentifier(), 'username', undefined); + stateController.setPendingOp(user._getStateIdentifier(), 'password', undefined); + response.password = undefined; + user._finishFetch(response); + if (!canUseCurrentUser) { + // We can't set the current user, so just return the one we logged in + return ParsePromise.as(user); + } + return DefaultController.setCurrentUser(user); + }); + }, + + become(options) { + var user = new ParseUser(); + var RESTController = CoreManager.getRESTController(); + return RESTController.request('GET', 'users/me', {}, options).then((response, status) => { + user._finishFetch(response); + user._setExisted(true); + return DefaultController.setCurrentUser(user); + }); + }, + + logOut() { + return DefaultController.currentUserAsync().then(currentUser => { + var path = Storage.generatePath(CURRENT_USER_KEY); + var promise = Storage.removeItemAsync(path); + var RESTController = CoreManager.getRESTController(); + if (currentUser !== null) { + var currentSession = currentUser.getSessionToken(); + if (currentSession && isRevocableSession(currentSession)) { + promise = promise.then(() => { + return RESTController.request('POST', 'logout', {}, { sessionToken: currentSession }); + }); + } + currentUser._logOutWithAll(); + currentUser._finishFetch({ sessionToken: undefined }); + } + currentUserCacheMatchesDisk = true; + currentUserCache = null; + + return promise; + }); + }, + + requestPasswordReset(email, options) { + var RESTController = CoreManager.getRESTController(); + return RESTController.request('POST', 'requestPasswordReset', { email: email }, options); + }, + + upgradeToRevocableSession(user, options) { + var token = user.getSessionToken(); + if (!token) { + return ParsePromise.error(new ParseError(ParseError.SESSION_MISSING, 'Cannot upgrade a user with no session token')); + } + + options.sessionToken = token; + + var RESTController = CoreManager.getRESTController(); + return RESTController.request('POST', 'upgradeToRevocableSession', {}, options).then(result => { + var session = new ParseSession(); + session._finishFetch(result); + user._finishFetch({ sessionToken: session.getSessionToken() }); + if (user.isCurrent()) { + return DefaultController.setCurrentUser(user); + } + return ParsePromise.as(user); + }); + }, + + linkWith(user, authData) { + return user.save({ authData }).then(() => { + if (canUseCurrentUser) { + return DefaultController.setCurrentUser(user); + } + return user; + }); + } +}; + +CoreManager.setUserController(DefaultController); \ No newline at end of file diff --git a/lib/react-native/Push.js b/lib/react-native/Push.js new file mode 100644 index 000000000..2710abe4d --- /dev/null +++ b/lib/react-native/Push.js @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +import CoreManager from './CoreManager'; +import ParseQuery from './ParseQuery'; + +/** + * Contains functions to deal with Push in Parse. + * @class Parse.Push + * @static + */ + +/** + * Sends a push notification. + * @method send + * @param {Object} data - The data of the push notification. Valid fields + * are: + *