diff --git a/.gitignore b/.gitignore index fcef25c5e..16a5fb13e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ coverage -dist -lib +#dist +#lib logs node_modules test_output diff --git a/dist/parse.js b/dist/parse.js new file mode 100644 index 000000000..1e049cfeb --- /dev/null +++ b/dist/parse.js @@ -0,0 +1,13937 @@ +/** + * Parse JavaScript SDK v1.9.2 + * + * The source tree of this library can be found at + * https://github.com/ParsePlatform/Parse-SDK-JS + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Parse = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o + * 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); +},{"./CoreManager":3}],2:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.run = run; + +var _CoreManager = _dereq_('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +var _decode = _dereq_('./decode'); + +var _decode2 = _interopRequireDefault(_decode); + +var _encode = _dereq_('./encode'); + +var _encode2 = _interopRequireDefault(_encode); + +var _ParseError = _dereq_('./ParseError'); + +var _ParseError2 = _interopRequireDefault(_ParseError); + +var _ParsePromise = _dereq_('./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); +},{"./CoreManager":3,"./ParseError":13,"./ParsePromise":20,"./decode":35,"./encode":36}],3:[function(_dereq_,module,exports){ +(function (process){ +'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']; + } +}; +}).call(this,_dereq_('_process')) +},{"_process":168}],4:[function(_dereq_,module,exports){ +'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 = _dereq_('events').EventEmitter; +var EventEmitter; +},{"events":169}],5:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _parseDate = _dereq_('./parseDate'); + +var _parseDate2 = _interopRequireDefault(_parseDate); + +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. + * + * -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; +},{"./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 + * + *

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; +},{"./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.

+ */ + +var ParseACL = function () { + function ParseACL(arg1) { + (0, _classCallCheck3.default)(this, ParseACL); + + this.permissionsById = {}; + if (arg1 && (typeof arg1 === 'undefined' ? 'undefined' : (0, _typeof3.default)(arg1)) === 'object') { + if (arg1 instanceof _ParseUser2.default) { + this.setReadAccess(arg1, true); + this.setWriteAccess(arg1, true); + } else { + for (var userId in arg1) { + var accessList = arg1[userId]; + if (typeof userId !== 'string') { + throw new TypeError('Tried to create an ACL with an invalid user id.'); + } + this.permissionsById[userId] = {}; + for (var permission in accessList) { + var allowed = accessList[permission]; + if (permission !== 'read' && permission !== 'write') { + throw new TypeError('Tried to create an ACL with an invalid permission type.'); + } + if (typeof allowed !== 'boolean') { + throw new TypeError('Tried to create an ACL with an invalid permission value.'); + } + this.permissionsById[userId][permission] = allowed; + } + } + } + } else if (typeof arg1 === 'function') { + throw new TypeError('ParseACL constructed with a function. Did you forget ()?'); + } + } + + /** + * Returns a JSON-encoded version of the ACL. + * @method toJSON + * @return {Object} + */ + + (0, _createClass3.default)(ParseACL, [{ + key: 'toJSON', + value: function () { + var permissions = {}; + for (var p in this.permissionsById) { + permissions[p] = this.permissionsById[p]; + } + return permissions; + } + + /** + * Returns whether this ACL is equal to another object + * @method equals + * @param other The other object to compare to + * @return {Boolean} + */ + + }, { + key: 'equals', + value: function (other) { + if (!(other instanceof ParseACL)) { + return false; + } + var users = (0, _keys2.default)(this.permissionsById); + var otherUsers = (0, _keys2.default)(other.permissionsById); + if (users.length !== otherUsers.length) { + return false; + } + for (var u in this.permissionsById) { + if (!other.permissionsById[u]) { + return false; + } + if (this.permissionsById[u].read !== other.permissionsById[u].read) { + return false; + } + if (this.permissionsById[u].write !== other.permissionsById[u].write) { + return false; + } + } + return true; + } + }, { + key: '_setAccess', + value: function (accessType, userId, allowed) { + if (userId instanceof _ParseUser2.default) { + userId = userId.id; + } else if (userId instanceof _ParseRole2.default) { + var name = userId.getName(); + if (!name) { + throw new TypeError('Role must have a name'); + } + userId = 'role:' + name; + } + if (typeof userId !== 'string') { + throw new TypeError('userId must be a string.'); + } + if (typeof allowed !== 'boolean') { + throw new TypeError('allowed must be either true or false.'); + } + var permissions = this.permissionsById[userId]; + if (!permissions) { + if (!allowed) { + // The user already doesn't have this permission, so no action is needed + return; + } else { + permissions = {}; + this.permissionsById[userId] = permissions; + } + } + + if (allowed) { + this.permissionsById[userId][accessType] = true; + } else { + delete permissions[accessType]; + if ((0, _keys2.default)(permissions).length === 0) { + delete this.permissionsById[userId]; + } + } + } + }, { + key: '_getAccess', + value: function (accessType, userId) { + if (userId instanceof _ParseUser2.default) { + userId = userId.id; + if (!userId) { + throw new Error('Cannot get access for a ParseUser without an ID'); + } + } else if (userId instanceof _ParseRole2.default) { + var name = userId.getName(); + if (!name) { + throw new TypeError('Role must have a name'); + } + userId = 'role:' + name; + } + var permissions = this.permissionsById[userId]; + if (!permissions) { + return false; + } + return !!permissions[accessType]; + } + + /** + * Sets whether the given user is allowed to read this object. + * @method setReadAccess + * @param userId An instance of Parse.User or its objectId. + * @param {Boolean} allowed Whether that user should have read access. + */ + + }, { + key: 'setReadAccess', + value: function (userId, allowed) { + this._setAccess('read', userId, allowed); + } + + /** + * Get whether the given user id is *explicitly* allowed to read this object. + * Even if this returns false, the user may still be able to access it if + * getPublicReadAccess returns true or a role that the user belongs to has + * write access. + * @method getReadAccess + * @param userId An instance of Parse.User or its objectId, or a Parse.Role. + * @return {Boolean} + */ + + }, { + key: 'getReadAccess', + value: function (userId) { + return this._getAccess('read', userId); + } + + /** + * Sets whether the given user id is allowed to write this object. + * @method setWriteAccess + * @param userId An instance of Parse.User or its objectId, or a Parse.Role.. + * @param {Boolean} allowed Whether that user should have write access. + */ + + }, { + key: 'setWriteAccess', + value: function (userId, allowed) { + this._setAccess('write', userId, allowed); + } + + /** + * Gets whether the given user id is *explicitly* allowed to write this object. + * Even if this returns false, the user may still be able to write it if + * getPublicWriteAccess returns true or a role that the user belongs to has + * write access. + * @method getWriteAccess + * @param userId An instance of Parse.User or its objectId, or a Parse.Role. + * @return {Boolean} + */ + + }, { + key: 'getWriteAccess', + value: function (userId) { + return this._getAccess('write', userId); + } + + /** + * Sets whether the public is allowed to read this object. + * @method setPublicReadAccess + * @param {Boolean} allowed + */ + + }, { + key: 'setPublicReadAccess', + value: function (allowed) { + this.setReadAccess(PUBLIC_KEY, allowed); + } + + /** + * Gets whether the public is allowed to read this object. + * @method getPublicReadAccess + * @return {Boolean} + */ + + }, { + key: 'getPublicReadAccess', + value: function () { + return this.getReadAccess(PUBLIC_KEY); + } + + /** + * Sets whether the public is allowed to write this object. + * @method setPublicWriteAccess + * @param {Boolean} allowed + */ + + }, { + key: 'setPublicWriteAccess', + value: function (allowed) { + this.setWriteAccess(PUBLIC_KEY, allowed); + } + + /** + * Gets whether the public is allowed to write this object. + * @method getPublicWriteAccess + * @return {Boolean} + */ + + }, { + key: 'getPublicWriteAccess', + value: function () { + return this.getWriteAccess(PUBLIC_KEY); + } + + /** + * Gets whether users belonging to the given role are allowed + * to read this object. Even if this returns false, the role may + * still be able to write it if a parent role has read access. + * + * @method getRoleReadAccess + * @param role The name of the role, or a Parse.Role object. + * @return {Boolean} true if the role has read access. false otherwise. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + + }, { + key: 'getRoleReadAccess', + value: function (role) { + if (role instanceof _ParseRole2.default) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + return this.getReadAccess('role:' + role); + } + + /** + * Gets whether users belonging to the given role are allowed + * to write this object. Even if this returns false, the role may + * still be able to write it if a parent role has write access. + * + * @method getRoleWriteAccess + * @param role The name of the role, or a Parse.Role object. + * @return {Boolean} true if the role has write access. false otherwise. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + + }, { + key: 'getRoleWriteAccess', + value: function (role) { + if (role instanceof _ParseRole2.default) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + return this.getWriteAccess('role:' + role); + } + + /** + * Sets whether users belonging to the given role are allowed + * to read this object. + * + * @method setRoleReadAccess + * @param role The name of the role, or a Parse.Role object. + * @param {Boolean} allowed Whether the given role can read this object. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + + }, { + key: 'setRoleReadAccess', + value: function (role, allowed) { + if (role instanceof _ParseRole2.default) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + this.setReadAccess('role:' + role, allowed); + } + + /** + * Sets whether users belonging to the given role are allowed + * to write this object. + * + * @method setRoleWriteAccess + * @param role The name of the role, or a Parse.Role object. + * @param {Boolean} allowed Whether the given role can write this object. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + + }, { + key: 'setRoleWriteAccess', + value: function (role, allowed) { + if (role instanceof _ParseRole2.default) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + this.setWriteAccess('role:' + role, allowed); + } + }]); + return ParseACL; +}(); + +exports.default = ParseACL; +},{"./ParseRole":23,"./ParseUser":25,"babel-runtime/core-js/object/keys":51,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57,"babel-runtime/helpers/typeof":61}],12:[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); + +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 _decode = _dereq_('./decode'); + +var _decode2 = _interopRequireDefault(_decode); + +var _encode = _dereq_('./encode'); + +var _encode2 = _interopRequireDefault(_encode); + +var _escape2 = _dereq_('./escape'); + +var _escape3 = _interopRequireDefault(_escape2); + +var _ParseError = _dereq_('./ParseError'); + +var _ParseError2 = _interopRequireDefault(_ParseError); + +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 }; +} + +/** + * Parse.Config is a local representation of configuration data that + * can be set from the Parse dashboard. + * + * @class Parse.Config + * @constructor + */ + +var ParseConfig = function () { + function ParseConfig() { + (0, _classCallCheck3.default)(this, ParseConfig); + + this.attributes = {}; + this._escapedAttributes = {}; + } + + /** + * Gets the value of an attribute. + * @method get + * @param {String} attr The name of an attribute. + */ + + (0, _createClass3.default)(ParseConfig, [{ + key: 'get', + value: function (attr) { + return this.attributes[attr]; + } + + /** + * Gets the HTML-escaped value of an attribute. + * @method escape + * @param {String} attr The name of an attribute. + */ + + }, { + key: 'escape', + value: function (attr) { + var html = this._escapedAttributes[attr]; + if (html) { + return html; + } + var val = this.attributes[attr]; + var escaped = ''; + if (val != null) { + escaped = (0, _escape3.default)(val.toString()); + } + this._escapedAttributes[attr] = escaped; + return escaped; + } + + /** + * Retrieves the most recently-fetched configuration object, either from + * memory or from local storage if necessary. + * + * @method current + * @static + * @return {Config} The most recently-fetched Parse.Config if it + * exists, else an empty Parse.Config. + */ + + }], [{ + key: 'current', + value: function () { + var controller = _CoreManager2.default.getConfigController(); + return controller.current(); + } + + /** + * Gets a new configuration object from the server. + * @method get + * @static + * @param {Object} options A Backbone-style options object. + * Valid options are: + * @return {Parse.Promise} A promise that is resolved with a newly-created + * configuration object when the get completes. + */ + + }, { + key: 'get', + value: function (options) { + options = options || {}; + + var controller = _CoreManager2.default.getConfigController(); + return controller.get()._thenRunCallbacks(options); + } + }]); + return ParseConfig; +}(); /** + * 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 = ParseConfig; + +var currentConfig = null; + +var CURRENT_CONFIG_KEY = 'currentConfig'; + +function decodePayload(data) { + try { + var json = JSON.parse(data); + if (json && (typeof json === 'undefined' ? 'undefined' : (0, _typeof3.default)(json)) === 'object') { + return (0, _decode2.default)(json); + } + } catch (e) { + return null; + } +} + +var DefaultController = { + current: function () { + if (currentConfig) { + return currentConfig; + } + + var config = new ParseConfig(); + var storagePath = _Storage2.default.generatePath(CURRENT_CONFIG_KEY); + var configData; + if (!_Storage2.default.async()) { + configData = _Storage2.default.getItem(storagePath); + + if (configData) { + var attributes = decodePayload(configData); + if (attributes) { + config.attributes = attributes; + currentConfig = config; + } + } + return config; + } + // Return a promise for async storage controllers + return _Storage2.default.getItemAsync(storagePath).then(function (configData) { + if (configData) { + var attributes = decodePayload(configData); + if (attributes) { + config.attributes = attributes; + currentConfig = config; + } + } + return config; + }); + }, + get: function () { + var RESTController = _CoreManager2.default.getRESTController(); + + return RESTController.request('GET', 'config', {}, {}).then(function (response) { + if (!response || !response.params) { + var error = new _ParseError2.default(_ParseError2.default.INVALID_JSON, 'Config JSON response invalid.'); + return _ParsePromise2.default.error(error); + } + + var config = new ParseConfig(); + config.attributes = {}; + for (var attr in response.params) { + config.attributes[attr] = (0, _decode2.default)(response.params[attr]); + } + currentConfig = config; + return _Storage2.default.setItemAsync(_Storage2.default.generatePath(CURRENT_CONFIG_KEY), (0, _stringify2.default)(response.params)).then(function () { + return config; + }); + }); + } +}; + +_CoreManager2.default.setConfigController(DefaultController); +},{"./CoreManager":3,"./ParseError":13,"./ParsePromise":20,"./Storage":29,"./decode":35,"./encode":36,"./escape":38,"babel-runtime/core-js/json/stringify":44,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57,"babel-runtime/helpers/typeof":61}],13:[function(_dereq_,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _classCallCheck2 = _dereq_("babel-runtime/helpers/classCallCheck"); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +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. + */ + +/** + * Constructs a new Parse.Error object with the given code and message. + * @class Parse.Error + * @constructor + * @param {Number} code An error code constant from 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: + * @return {Parse.Promise} A promise that is fulfilled when the fetch + * completes. + */ + + }, { + key: 'fetch', + value: function (options) { + options = options || {}; + var fetchOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + fetchOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + fetchOptions.sessionToken = options.sessionToken; + } + var controller = _CoreManager2.default.getObjectController(); + return controller.fetch(this, true, fetchOptions)._thenRunCallbacks(options); + } + + /** + * Set a hash of model attributes, and save the model to the server. + * updatedAt will be updated when the request returns. + * You can either call it as:
+     *   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: + * @return {Parse.Promise} A promise that is fulfilled when the save + * completes. + */ + + }, { + key: 'save', + value: function (arg1, arg2, arg3) { + var _this = this; + + var attrs; + var options; + if ((typeof arg1 === 'undefined' ? 'undefined' : (0, _typeof3.default)(arg1)) === 'object' || typeof arg1 === 'undefined') { + attrs = arg1; + if ((typeof arg2 === 'undefined' ? 'undefined' : (0, _typeof3.default)(arg2)) === 'object') { + options = arg2; + } + } else { + attrs = {}; + attrs[arg1] = arg2; + options = arg3; + } + + // Support save({ success: function() {}, error: function() {} }) + if (!options && attrs) { + options = {}; + if (typeof attrs.success === 'function') { + options.success = attrs.success; + delete attrs.success; + } + if (typeof attrs.error === 'function') { + options.error = attrs.error; + delete attrs.error; + } + } + + if (attrs) { + var validation = this.validate(attrs); + if (validation) { + if (options && typeof options.error === 'function') { + options.error(this, validation); + } + return _ParsePromise2.default.error(validation); + } + this.set(attrs, options); + } + + options = options || {}; + var saveOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + saveOptions.useMasterKey = !!options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken') && typeof options.sessionToken === 'string') { + saveOptions.sessionToken = options.sessionToken; + } + + var controller = _CoreManager2.default.getObjectController(); + var unsaved = (0, _unsavedChildren2.default)(this); + return controller.save(unsaved, saveOptions).then(function () { + return controller.save(_this, saveOptions); + })._thenRunCallbacks(options, this); + } + + /** + * Destroy this model on the server if it was already persisted. + * If `wait: true` is passed, waits for the server to respond + * before removal. + * + * @method destroy + * @param {Object} options A Backbone-style callback object. + * Valid options are: + * @return {Parse.Promise} A promise that is fulfilled when the destroy + * completes. + */ + + }, { + key: 'destroy', + value: function (options) { + options = options || {}; + var destroyOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + destroyOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + destroyOptions.sessionToken = options.sessionToken; + } + if (!this.id) { + return _ParsePromise2.default.as()._thenRunCallbacks(options); + } + return _CoreManager2.default.getObjectController().destroy(this, destroyOptions)._thenRunCallbacks(options); + } + + /** Static methods **/ + + }, { + key: 'attributes', + get: function () { + var stateController = _CoreManager2.default.getObjectStateController(); + return (0, _freeze2.default)(stateController.estimateAttributes(this._getStateIdentifier())); + } + + /** + * The first time this object was saved on the server. + * @property createdAt + * @type Date + */ + + }, { + key: 'createdAt', + get: function () { + return this._getServerData().createdAt; + } + + /** + * The last time this object was updated on the server. + * @property updatedAt + * @type Date + */ + + }, { + key: 'updatedAt', + get: function () { + return this._getServerData().updatedAt; + } + }], [{ + key: '_clearAllState', + value: function () { + var stateController = _CoreManager2.default.getObjectStateController(); + stateController.clearAllState(); + } + + /** + * Fetches the given list of Parse.Object. + * If any error is encountered, stops and calls the error handler. + * + *
+     *   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: + */ + + }, { + key: 'fetchAll', + value: function (list, options) { + var options = options || {}; + + var queryOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + queryOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + queryOptions.sessionToken = options.sessionToken; + } + return _CoreManager2.default.getObjectController().fetch(list, true, queryOptions)._thenRunCallbacks(options); + } + + /** + * Fetches the given list of Parse.Object if needed. + * If any error is encountered, stops and calls the error handler. + * + *
+     *   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: + */ + + }, { + key: 'fetchAllIfNeeded', + value: function (list, options) { + var options = options || {}; + + var queryOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + queryOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + queryOptions.sessionToken = options.sessionToken; + } + return _CoreManager2.default.getObjectController().fetch(list, false, queryOptions)._thenRunCallbacks(options); + } + + /** + * Destroy the given list of models on the server if it was already persisted. + * + *

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: + * @return {Parse.Promise} A promise that is fulfilled when the destroyAll + * completes. + */ + + }, { + key: 'destroyAll', + value: function (list, options) { + var options = options || {}; + + var destroyOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + destroyOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + destroyOptions.sessionToken = options.sessionToken; + } + return _CoreManager2.default.getObjectController().destroy(list, destroyOptions)._thenRunCallbacks(options); + } + + /** + * Saves the given list of Parse.Object. + * If any error is encountered, stops and calls the error handler. + * + *
+     *   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: + */ + + }, { + key: 'saveAll', + value: function (list, options) { + var options = options || {}; + + var saveOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + saveOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + saveOptions.sessionToken = options.sessionToken; + } + return _CoreManager2.default.getObjectController().save(list, saveOptions)._thenRunCallbacks(options); + } + + /** + * Creates a reference to a subclass of Parse.Object with the given id. This + * does not exist on Parse.Object, only on subclasses. + * + *

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: + * + * @return {Parse.Promise} A promise that is resolved with the result when + * the query completes. + */ + + }, { + key: 'get', + value: function (objectId, options) { + this.equalTo('objectId', objectId); + + var firstOptions = {}; + if (options && options.hasOwnProperty('useMasterKey')) { + firstOptions.useMasterKey = options.useMasterKey; + } + if (options && options.hasOwnProperty('sessionToken')) { + firstOptions.sessionToken = options.sessionToken; + } + + return this.first(firstOptions).then(function (response) { + if (response) { + return response; + } + + var errorObject = new _ParseError2.default(_ParseError2.default.OBJECT_NOT_FOUND, 'Object not found.'); + return _ParsePromise2.default.error(errorObject); + })._thenRunCallbacks(options, null); + } + + /** + * Retrieves a list of ParseObjects that satisfy this query. + * Either options.success or options.error is called when the find + * completes. + * + * @method find + * @param {Object} options A Backbone-style options object. Valid options + * are: + * + * @return {Parse.Promise} A promise that is resolved with the results when + * the query completes. + */ + + }, { + key: 'find', + value: function (options) { + var _this2 = this; + + options = options || {}; + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var controller = _CoreManager2.default.getQueryController(); + + var select = this._select; + + return controller.find(this.className, this.toJSON(), findOptions).then(function (response) { + return response.results.map(function (data) { + // In cases of relations, the server may send back a className + // on the top level of the payload + var override = response.className || _this2.className; + if (!data.className) { + data.className = override; + } + + // Make sure the data object contains keys for all objects that + // have been requested with a select, so that our cached state + // updates correctly. + if (select) { + handleSelectResult(data, select); + } + + return _ParseObject2.default.fromJSON(data, !select); + }); + })._thenRunCallbacks(options); + } + + /** + * Counts the number of objects that match this query. + * Either options.success or options.error is called when the count + * completes. + * + * @method count + * @param {Object} options A Backbone-style options object. Valid options + * are: + * + * @return {Parse.Promise} A promise that is resolved with the count when + * the query completes. + */ + + }, { + key: 'count', + value: function (options) { + options = options || {}; + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var controller = _CoreManager2.default.getQueryController(); + + var params = this.toJSON(); + params.limit = 0; + params.count = 1; + + return controller.find(this.className, params, findOptions).then(function (result) { + return result.count; + })._thenRunCallbacks(options); + } + + /** + * Retrieves at most one Parse.Object that satisfies this query. + * + * Either options.success or options.error is called when it completes. + * success is passed the object if there is one. otherwise, undefined. + * + * @method first + * @param {Object} options A Backbone-style options object. Valid options + * are: + * + * @return {Parse.Promise} A promise that is resolved with the object when + * the query completes. + */ + + }, { + key: 'first', + value: function (options) { + var _this3 = this; + + options = options || {}; + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var controller = _CoreManager2.default.getQueryController(); + + var params = this.toJSON(); + params.limit = 1; + + var select = this._select; + + return controller.find(this.className, params, findOptions).then(function (response) { + var objects = response.results; + if (!objects[0]) { + return undefined; + } + if (!objects[0].className) { + objects[0].className = _this3.className; + } + + // Make sure the data object contains keys for all objects that + // have been requested with a select, so that our cached state + // updates correctly. + if (select) { + handleSelectResult(objects[0], select); + } + + return _ParseObject2.default.fromJSON(objects[0], !select); + })._thenRunCallbacks(options); + } + + /** + * Iterates over each result of a query, calling a callback for each one. If + * the callback returns a promise, the iteration will not continue until + * that promise has been fulfilled. If the callback returns a rejected + * promise, then iteration will stop with that error. The items are + * processed in an unspecified order. The query may not have any sort order, + * and may not use limit or skip. + * @method each + * @param {Function} callback Callback that will be called with each result + * of the query. + * @param {Object} options A Backbone-style options object. Valid options + * are: + * @return {Parse.Promise} A promise that will be fulfilled once the + * iteration has completed. + */ + + }, { + key: 'each', + value: function (callback, options) { + options = options || {}; + + if (this._order || this._skip || this._limit >= 0) { + return _ParsePromise2.default.error('Cannot iterate on a query with sort, skip, or limit.')._thenRunCallbacks(options); + } + + new _ParsePromise2.default(); + + + var query = new ParseQuery(this.className); + // We can override the batch size from the options. + // This is undocumented, but useful for testing. + query._limit = options.batchSize || 100; + query._include = this._include.map(function (i) { + return i; + }); + if (this._select) { + query._select = this._select.map(function (s) { + return s; + }); + } + + query._where = {}; + for (var attr in this._where) { + var val = this._where[attr]; + if (Array.isArray(val)) { + query._where[attr] = val.map(function (v) { + return v; + }); + } else if (val && (typeof val === 'undefined' ? 'undefined' : (0, _typeof3.default)(val)) === 'object') { + var conditionMap = {}; + query._where[attr] = conditionMap; + for (var cond in val) { + conditionMap[cond] = val[cond]; + } + } else { + query._where[attr] = val; + } + } + + query.ascending('objectId'); + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var finished = false; + return _ParsePromise2.default._continueWhile(function () { + return !finished; + }, function () { + return query.find(findOptions).then(function (results) { + var callbacksDone = _ParsePromise2.default.as(); + results.forEach(function (result) { + callbacksDone = callbacksDone.then(function () { + return callback(result); + }); + }); + + return callbacksDone.then(function () { + if (results.length >= query._limit) { + query.greaterThan('objectId', results[results.length - 1].id); + } else { + finished = true; + } + }); + }); + })._thenRunCallbacks(options); + } + + /** Query Conditions **/ + + /** + * Adds a constraint to the query that requires a particular key's value to + * be equal to the provided value. + * @method equalTo + * @param {String} key The key to check. + * @param value The value that the Parse.Object must contain. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'equalTo', + value: function (key, value) { + if (typeof value === 'undefined') { + return this.doesNotExist(key); + } + + this._where[key] = (0, _encode2.default)(value, false, true); + return this; + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be not equal to the provided value. + * @method notEqualTo + * @param {String} key The key to check. + * @param value The value that must not be equalled. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'notEqualTo', + value: function (key, value) { + return this._addCondition(key, '$ne', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be less than the provided value. + * @method lessThan + * @param {String} key The key to check. + * @param value The value that provides an upper bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'lessThan', + value: function (key, value) { + return this._addCondition(key, '$lt', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be greater than the provided value. + * @method greaterThan + * @param {String} key The key to check. + * @param value The value that provides an lower bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'greaterThan', + value: function (key, value) { + return this._addCondition(key, '$gt', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be less than or equal to the provided value. + * @method lessThanOrEqualTo + * @param {String} key The key to check. + * @param value The value that provides an upper bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'lessThanOrEqualTo', + value: function (key, value) { + return this._addCondition(key, '$lte', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be greater than or equal to the provided value. + * @method greaterThanOrEqualTo + * @param {String} key The key to check. + * @param value The value that provides an lower bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'greaterThanOrEqualTo', + value: function (key, value) { + return this._addCondition(key, '$gte', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be contained in the provided list of values. + * @method containedIn + * @param {String} key The key to check. + * @param {Array} values The values that will match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'containedIn', + value: function (key, value) { + return this._addCondition(key, '$in', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * not be contained in the provided list of values. + * @method notContainedIn + * @param {String} key The key to check. + * @param {Array} values The values that will not match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'notContainedIn', + value: function (key, value) { + return this._addCondition(key, '$nin', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * contain each one of the provided list of values. + * @method containsAll + * @param {String} key The key to check. This key's value must be an array. + * @param {Array} values The values that will match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'containsAll', + value: function (key, values) { + return this._addCondition(key, '$all', values); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * contain each one of the provided list of values starting with given strings. + * @method containsAllStartingWith + * @param {String} key The key to check. This key's value must be an array. + * @param {Array} values The string values that will match as starting string. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'containsAllStartingWith', + value: function (key, values) { + var _this = this; + if (!Array.isArray(values)) { + values = [values]; + } + + values = values.map(function (value) { + return { "$regex": _this._regexStartWith(value) }; + }); + + return this.containsAll(key, values); + } + + /** + * Adds a constraint for finding objects that contain the given key. + * @method exists + * @param {String} key The key that should exist. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'exists', + value: function (key) { + return this._addCondition(key, '$exists', true); + } + + /** + * Adds a constraint for finding objects that do not contain a given key. + * @method doesNotExist + * @param {String} key The key that should not exist + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'doesNotExist', + value: function (key) { + return this._addCondition(key, '$exists', false); + } + + /** + * Adds a regular expression constraint for finding string values that match + * the provided regular expression. + * This may be slow for large datasets. + * @method matches + * @param {String} key The key that the string to match is stored in. + * @param {RegExp} regex The regular expression pattern to match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'matches', + value: function (key, regex, modifiers) { + this._addCondition(key, '$regex', regex); + if (!modifiers) { + modifiers = ''; + } + if (regex.ignoreCase) { + modifiers += 'i'; + } + if (regex.multiline) { + modifiers += 'm'; + } + if (modifiers.length) { + this._addCondition(key, '$options', modifiers); + } + return this; + } + + /** + * Adds a constraint that requires that a key's value matches a Parse.Query + * constraint. + * @method matchesQuery + * @param {String} key The key that the contains the object to match the + * query. + * @param {Parse.Query} query The query that should match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'matchesQuery', + value: function (key, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$inQuery', queryJSON); + } + + /** + * Adds a constraint that requires that a key's value not matches a + * Parse.Query constraint. + * @method doesNotMatchQuery + * @param {String} key The key that the contains the object to match the + * query. + * @param {Parse.Query} query The query that should not match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'doesNotMatchQuery', + value: function (key, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$notInQuery', queryJSON); + } + + /** + * Adds a constraint that requires that a key's value matches a value in + * an object returned by a different Parse.Query. + * @method matchesKeyInQuery + * @param {String} key The key that contains the value that is being + * matched. + * @param {String} queryKey The key in the objects returned by the query to + * match against. + * @param {Parse.Query} query The query to run. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'matchesKeyInQuery', + value: function (key, queryKey, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$select', { + key: queryKey, + query: queryJSON + }); + } + + /** + * Adds a constraint that requires that a key's value not match a value in + * an object returned by a different Parse.Query. + * @method doesNotMatchKeyInQuery + * @param {String} key The key that contains the value that is being + * excluded. + * @param {String} queryKey The key in the objects returned by the query to + * match against. + * @param {Parse.Query} query The query to run. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'doesNotMatchKeyInQuery', + value: function (key, queryKey, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$dontSelect', { + key: queryKey, + query: queryJSON + }); + } + + /** + * Adds a constraint for finding string values that contain a provided + * string. This may be slow for large datasets. + * @method contains + * @param {String} key The key that the string to match is stored in. + * @param {String} substring The substring that the value must contain. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'contains', + value: function (key, value) { + if (typeof value !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + return this._addCondition(key, '$regex', quote(value)); + } + + /** + * Adds a constraint for finding string values that start with a provided + * string. This query will use the backend index, so it will be fast even + * for large datasets. + * @method startsWith + * @param {String} key The key that the string to match is stored in. + * @param {String} prefix The substring that the value must start with. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'startsWith', + value: function (key, value) { + if (typeof value !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + return this._addCondition(key, '$regex', this._regexStartWith(value)); + } + + /** + * Adds a constraint for finding string values that end with a provided + * string. This will be slow for large datasets. + * @method endsWith + * @param {String} key The key that the string to match is stored in. + * @param {String} suffix The substring that the value must end with. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'endsWith', + value: function (key, value) { + if (typeof value !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + return this._addCondition(key, '$regex', quote(value) + '$'); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given. + * @method near + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'near', + value: function (key, point) { + if (!(point instanceof _ParseGeoPoint2.default)) { + // Try to cast it as a GeoPoint + point = new _ParseGeoPoint2.default(point); + } + return this._addCondition(key, '$nearSphere', point); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * @method withinRadians + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in radians) of results to + * return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'withinRadians', + value: function (key, point, distance) { + this.near(key, point); + return this._addCondition(key, '$maxDistance', distance); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * Radius of earth used is 3958.8 miles. + * @method withinMiles + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in miles) of results to + * return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'withinMiles', + value: function (key, point, distance) { + return this.withinRadians(key, point, distance / 3958.8); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * Radius of earth used is 6371.0 kilometers. + * @method withinKilometers + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in kilometers) of results + * to return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'withinKilometers', + value: function (key, point, distance) { + return this.withinRadians(key, point, distance / 6371.0); + } + + /** + * Adds a constraint to the query that requires a particular key's + * coordinates be contained within a given rectangular geographic bounding + * box. + * @method withinGeoBox + * @param {String} key The key to be constrained. + * @param {Parse.GeoPoint} southwest + * The lower-left inclusive corner of the box. + * @param {Parse.GeoPoint} northeast + * The upper-right inclusive corner of the box. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'withinGeoBox', + value: function (key, southwest, northeast) { + if (!(southwest instanceof _ParseGeoPoint2.default)) { + southwest = new _ParseGeoPoint2.default(southwest); + } + if (!(northeast instanceof _ParseGeoPoint2.default)) { + northeast = new _ParseGeoPoint2.default(northeast); + } + this._addCondition(key, '$within', { '$box': [southwest, northeast] }); + return this; + } + + /** Query Orderings **/ + + /** + * Sorts the results in ascending order by the given key. + * + * @method ascending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'ascending', + value: function () { + this._order = []; + + for (var _len = arguments.length, keys = Array(_len), _key = 0; _key < _len; _key++) { + keys[_key] = arguments[_key]; + } + + return this.addAscending.apply(this, keys); + } + + /** + * Sorts the results in ascending order by the given key, + * but can also add secondary sort descriptors without overwriting _order. + * + * @method addAscending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'addAscending', + value: function () { + var _this4 = this; + + if (!this._order) { + this._order = []; + } + + for (var _len2 = arguments.length, keys = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + keys[_key2] = arguments[_key2]; + } + + keys.forEach(function (key) { + if (Array.isArray(key)) { + key = key.join(); + } + _this4._order = _this4._order.concat(key.replace(/\s/g, '').split(',')); + }); + + return this; + } + + /** + * Sorts the results in descending order by the given key. + * + * @method descending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'descending', + value: function () { + this._order = []; + + for (var _len3 = arguments.length, keys = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + keys[_key3] = arguments[_key3]; + } + + return this.addDescending.apply(this, keys); + } + + /** + * Sorts the results in descending order by the given key, + * but can also add secondary sort descriptors without overwriting _order. + * + * @method addDescending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'addDescending', + value: function () { + var _this5 = this; + + if (!this._order) { + this._order = []; + } + + for (var _len4 = arguments.length, keys = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { + keys[_key4] = arguments[_key4]; + } + + keys.forEach(function (key) { + if (Array.isArray(key)) { + key = key.join(); + } + _this5._order = _this5._order.concat(key.replace(/\s/g, '').split(',').map(function (k) { + return '-' + k; + })); + }); + + return this; + } + + /** Query Options **/ + + /** + * Sets the number of results to skip before returning any results. + * This is useful for pagination. + * Default is to skip zero results. + * @method skip + * @param {Number} n the number of results to skip. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'skip', + value: function (n) { + if (typeof n !== 'number' || n < 0) { + throw new Error('You can only skip by a positive number'); + } + this._skip = n; + return this; + } + + /** + * Sets the limit of the number of results to return. The default limit is + * 100, with a maximum of 1000 results being returned at a time. + * @method limit + * @param {Number} n the number of results to limit to. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'limit', + value: function (n) { + if (typeof n !== 'number') { + throw new Error('You can only set the limit to a numeric value'); + } + this._limit = n; + return this; + } + + /** + * Includes nested Parse.Objects for the provided key. You can use dot + * notation to specify which fields in the included object are also fetched. + * @method include + * @param {String} key The name of the key to include. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'include', + value: function () { + var _this6 = this; + + for (var _len5 = arguments.length, keys = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { + keys[_key5] = arguments[_key5]; + } + + keys.forEach(function (key) { + if (Array.isArray(key)) { + _this6._include = _this6._include.concat(key); + } else { + _this6._include.push(key); + } + }); + return this; + } + + /** + * Restricts the fields of the returned Parse.Objects to include only the + * provided keys. If this is called multiple times, then all of the keys + * specified in each of the calls will be included. + * @method select + * @param {Array} keys The names of the keys to include. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'select', + value: function () { + var _this7 = this; + + if (!this._select) { + this._select = []; + } + + for (var _len6 = arguments.length, keys = Array(_len6), _key6 = 0; _key6 < _len6; _key6++) { + keys[_key6] = arguments[_key6]; + } + + keys.forEach(function (key) { + if (Array.isArray(key)) { + _this7._select = _this7._select.concat(key); + } else { + _this7._select.push(key); + } + }); + return this; + } + + /** + * Subscribe this query to get liveQuery updates + * @method subscribe + * @return {LiveQuerySubscription} Returns the liveQuerySubscription, it's an event emitter + * which can be used to get liveQuery updates. + */ + + }, { + key: 'subscribe', + value: function () { + var controller = _CoreManager2.default.getLiveQueryController(); + return controller.subscribe(this); + } + + /** + * Constructs a Parse.Query that is the OR of the passed in queries. For + * example: + *
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 if current 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 using current. + * + *

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: + *
    + *
  1. channels - An Array of channels to push to.
  2. + *
  3. push_time - A Date object for when to send the push.
  4. + *
  5. expiration_time - A Date object for when to expire + * the push.
  6. + *
  7. expiration_interval - The seconds from now to expire the push.
  8. + *
  9. where - A Parse.Query over Parse.Installation that is used to match + * a set of installations to push to.
  10. + *
  11. data - The data to send as part of the push
  12. + *
      + * @param {Object} options An object that has an optional success function, + * that takes no arguments and will be called on a successful push, and + * an error function that takes a Parse.Error and will be called if the push + * failed. + * @return {Parse.Promise} A promise that is fulfilled when the push request + * completes. + */ +/** + * 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 send(data, options) { + options = options || {}; + + if (data.where && data.where instanceof _ParseQuery2.default) { + data.where = data.where.toJSON().where; + } + + if (data.push_time && (0, _typeof3.default)(data.push_time) === 'object') { + data.push_time = data.push_time.toJSON(); + } + + if (data.expiration_time && (0, _typeof3.default)(data.expiration_time) === 'object') { + data.expiration_time = data.expiration_time.toJSON(); + } + + if (data.expiration_time && data.expiration_interval) { + throw new Error('expiration_time and expiration_interval cannot both be set.'); + } + + return _CoreManager2.default.getPushController().send(data, { + useMasterKey: options.useMasterKey + })._thenRunCallbacks(options); +} + +var DefaultController = { + send: function (data, options) { + var RESTController = _CoreManager2.default.getRESTController(); + + var request = RESTController.request('POST', 'push', data, { useMasterKey: !!options.useMasterKey }); + + return request._thenRunCallbacks(options); + } +}; + +_CoreManager2.default.setPushController(DefaultController); +},{"./CoreManager":3,"./ParseQuery":21,"babel-runtime/helpers/typeof":61}],27:[function(_dereq_,module,exports){ +(function (process){ +'use strict'; + +var _typeof2 = _dereq_('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +var _stringify = _dereq_('babel-runtime/core-js/json/stringify'); + +var _stringify2 = _interopRequireDefault(_stringify); + +var _CoreManager = _dereq_('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +var _ParseError = _dereq_('./ParseError'); + +var _ParseError2 = _interopRequireDefault(_ParseError); + +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 }; +} + +/** + * 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 XHR = null; +if (typeof XMLHttpRequest !== 'undefined') { + XHR = XMLHttpRequest; +} + + +var useXDomainRequest = false; +if (typeof XDomainRequest !== 'undefined' && !('withCredentials' in new XMLHttpRequest())) { + useXDomainRequest = true; +} + +function ajaxIE9(method, url, data) { + var promise = new _ParsePromise2.default(); + var xdr = new XDomainRequest(); + xdr.onload = function () { + var response; + try { + response = JSON.parse(xdr.responseText); + } catch (e) { + promise.reject(e); + } + if (response) { + promise.resolve(response); + } + }; + xdr.onerror = xdr.ontimeout = function () { + // Let's fake a real error message. + var fakeResponse = { + responseText: (0, _stringify2.default)({ + code: _ParseError2.default.X_DOMAIN_REQUEST, + error: 'IE\'s XDomainRequest does not supply error info.' + }) + }; + promise.reject(fakeResponse); + }; + xdr.onprogress = function () {}; + xdr.open(method, url); + xdr.send(data); + return promise; +} + +var RESTController = { + ajax: function (method, url, data, headers) { + if (useXDomainRequest) { + return ajaxIE9(method, url, data, headers); + } + + var promise = new _ParsePromise2.default(); + var attempts = 0; + + (function dispatch() { + if (XHR == null) { + throw new Error('Cannot make a request: No definition of XMLHttpRequest was found.'); + } + var handled = false; + var xhr = new XHR(); + + xhr.onreadystatechange = function () { + if (xhr.readyState !== 4 || handled) { + return; + } + handled = true; + + if (xhr.status >= 200 && xhr.status < 300) { + var response; + try { + response = JSON.parse(xhr.responseText); + } catch (e) { + promise.reject(e.toString()); + } + if (response) { + promise.resolve(response, xhr.status, xhr); + } + } else if (xhr.status >= 500 || xhr.status === 0) { + // retry on 5XX or node-xmlhttprequest error + if (++attempts < _CoreManager2.default.get('REQUEST_ATTEMPT_LIMIT')) { + // Exponentially-growing random delay + var delay = Math.round(Math.random() * 125 * Math.pow(2, attempts)); + setTimeout(dispatch, delay); + } else if (xhr.status === 0) { + promise.reject('Unable to connect to the Parse API'); + } else { + // After the retry limit is reached, fail + promise.reject(xhr); + } + } else { + promise.reject(xhr); + } + }; + + headers = headers || {}; + if (typeof headers['Content-Type'] !== 'string') { + headers['Content-Type'] = 'text/plain'; // Avoid pre-flight + } + if (_CoreManager2.default.get('IS_NODE')) { + headers['User-Agent'] = 'Parse/' + _CoreManager2.default.get('VERSION') + ' (NodeJS ' + process.versions.node + ')'; + } + + xhr.open(method, url, true); + for (var h in headers) { + xhr.setRequestHeader(h, headers[h]); + } + xhr.send(data); + })(); + + return promise; + }, + request: function (method, path, data, options) { + options = options || {}; + var url = _CoreManager2.default.get('SERVER_URL'); + if (url[url.length - 1] !== '/') { + url += '/'; + } + url += path; + + var payload = {}; + if (data && (typeof data === 'undefined' ? 'undefined' : (0, _typeof3.default)(data)) === 'object') { + for (var k in data) { + payload[k] = data[k]; + } + } + + if (method !== 'POST') { + payload._method = method; + method = 'POST'; + } + + payload._ApplicationId = _CoreManager2.default.get('APPLICATION_ID'); + var jsKey = _CoreManager2.default.get('JAVASCRIPT_KEY'); + if (jsKey) { + payload._JavaScriptKey = jsKey; + } + payload._ClientVersion = _CoreManager2.default.get('VERSION'); + + var useMasterKey = options.useMasterKey; + if (typeof useMasterKey === 'undefined') { + useMasterKey = _CoreManager2.default.get('USE_MASTER_KEY'); + } + if (useMasterKey) { + if (_CoreManager2.default.get('MASTER_KEY')) { + delete payload._JavaScriptKey; + payload._MasterKey = _CoreManager2.default.get('MASTER_KEY'); + } else { + throw new Error('Cannot use the Master Key, it has not been provided.'); + } + } + + if (_CoreManager2.default.get('FORCE_REVOCABLE_SESSION')) { + payload._RevocableSession = '1'; + } + + var installationId = options.installationId; + var installationIdPromise; + if (installationId && typeof installationId === 'string') { + installationIdPromise = _ParsePromise2.default.as(installationId); + } else { + var installationController = _CoreManager2.default.getInstallationController(); + installationIdPromise = installationController.currentInstallationId(); + } + + return installationIdPromise.then(function (iid) { + payload._InstallationId = iid; + var userController = _CoreManager2.default.getUserController(); + if (options && typeof options.sessionToken === 'string') { + return _ParsePromise2.default.as(options.sessionToken); + } else if (userController) { + return userController.currentUserAsync().then(function (user) { + if (user) { + return _ParsePromise2.default.as(user.getSessionToken()); + } + return _ParsePromise2.default.as(null); + }); + } + return _ParsePromise2.default.as(null); + }).then(function (token) { + if (token) { + payload._SessionToken = token; + } + + var payloadString = (0, _stringify2.default)(payload); + + return RESTController.ajax(method, url, payloadString); + }).then(null, function (response) { + // Transform the error into an instance of ParseError by trying to parse + // the error string as JSON + var error; + if (response && response.responseText) { + try { + var errorJSON = JSON.parse(response.responseText); + error = new _ParseError2.default(errorJSON.code, errorJSON.error); + } catch (e) { + // If we fail to parse the error text, that's okay. + error = new _ParseError2.default(_ParseError2.default.INVALID_JSON, 'Received an error with invalid JSON from Parse: ' + response.responseText); + } + } else { + error = new _ParseError2.default(_ParseError2.default.CONNECTION_FAILED, 'XMLHttpRequest failed: ' + (0, _stringify2.default)(response)); + } + + return _ParsePromise2.default.error(error); + }); + }, + _setXHR: function (xhr) { + XHR = xhr; + } +}; + +module.exports = RESTController; +}).call(this,_dereq_('_process')) +},{"./CoreManager":3,"./ParseError":13,"./ParsePromise":20,"./Storage":29,"_process":168,"babel-runtime/core-js/json/stringify":44,"babel-runtime/helpers/typeof":61}],28:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getState = getState; +exports.initializeState = initializeState; +exports.removeState = removeState; +exports.getServerData = getServerData; +exports.setServerData = setServerData; +exports.getPendingOps = getPendingOps; +exports.setPendingOp = setPendingOp; +exports.pushPendingState = pushPendingState; +exports.popPendingState = popPendingState; +exports.mergeFirstPendingState = mergeFirstPendingState; +exports.getObjectCache = getObjectCache; +exports.estimateAttribute = estimateAttribute; +exports.estimateAttributes = estimateAttributes; +exports.commitServerChanges = commitServerChanges; +exports.enqueueTask = enqueueTask; +exports.clearAllState = clearAllState; +exports.duplicateState = duplicateState; + +var _ObjectStateMutations = _dereq_('./ObjectStateMutations'); + +var ObjectStateMutations = _interopRequireWildcard(_ObjectStateMutations); + +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; + } +} + +var objectState = {}; /** + * 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 getState(obj) { + var classData = objectState[obj.className]; + if (classData) { + return classData[obj.id] || null; + } + return null; +} + +function initializeState(obj, initial) { + var state = getState(obj); + if (state) { + return state; + } + if (!objectState[obj.className]) { + objectState[obj.className] = {}; + } + if (!initial) { + initial = ObjectStateMutations.defaultState(); + } + state = objectState[obj.className][obj.id] = initial; + return state; +} + +function removeState(obj) { + var state = getState(obj); + if (state === null) { + return null; + } + delete objectState[obj.className][obj.id]; + return state; +} + +function getServerData(obj) { + var state = getState(obj); + if (state) { + return state.serverData; + } + return {}; +} + +function setServerData(obj, attributes) { + var serverData = initializeState(obj).serverData; + ObjectStateMutations.setServerData(serverData, attributes); +} + +function getPendingOps(obj) { + var state = getState(obj); + if (state) { + return state.pendingOps; + } + return [{}]; +} + +function setPendingOp(obj, attr, op) { + var pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.setPendingOp(pendingOps, attr, op); +} + +function pushPendingState(obj) { + var pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.pushPendingState(pendingOps); +} + +function popPendingState(obj) { + var pendingOps = initializeState(obj).pendingOps; + return ObjectStateMutations.popPendingState(pendingOps); +} + +function mergeFirstPendingState(obj) { + var pendingOps = getPendingOps(obj); + ObjectStateMutations.mergeFirstPendingState(pendingOps); +} + +function getObjectCache(obj) { + var state = getState(obj); + if (state) { + return state.objectCache; + } + return {}; +} + +function estimateAttribute(obj, attr) { + var serverData = getServerData(obj); + var pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttribute(serverData, pendingOps, obj.className, obj.id, attr); +} + +function estimateAttributes(obj) { + var serverData = getServerData(obj); + var pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttributes(serverData, pendingOps, obj.className, obj.id); +} + +function commitServerChanges(obj, changes) { + var state = initializeState(obj); + ObjectStateMutations.commitServerChanges(state.serverData, state.objectCache, changes); +} + +function enqueueTask(obj, task) { + var state = initializeState(obj); + return state.tasks.enqueue(task); +} + +function clearAllState() { + objectState = {}; +} + +function duplicateState(source, dest) { + dest.id = source.id; +} +},{"./ObjectStateMutations":9}],29:[function(_dereq_,module,exports){ +'use strict'; + +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. + * + * + */ + +module.exports = { + async: function () { + var controller = _CoreManager2.default.getStorageController(); + return !!controller.async; + }, + getItem: function (path) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + throw new Error('Synchronous storage is not supported by the current storage controller'); + } + return controller.getItem(path); + }, + getItemAsync: function (path) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + return controller.getItemAsync(path); + } + return _ParsePromise2.default.as(controller.getItem(path)); + }, + setItem: function (path, value) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + throw new Error('Synchronous storage is not supported by the current storage controller'); + } + return controller.setItem(path, value); + }, + setItemAsync: function (path, value) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + return controller.setItemAsync(path, value); + } + return _ParsePromise2.default.as(controller.setItem(path, value)); + }, + removeItem: function (path) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + throw new Error('Synchronous storage is not supported by the current storage controller'); + } + return controller.removeItem(path); + }, + removeItemAsync: function (path) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + return controller.removeItemAsync(path); + } + return _ParsePromise2.default.as(controller.removeItem(path)); + }, + generatePath: function (path) { + if (!_CoreManager2.default.get('APPLICATION_ID')) { + throw new Error('You need to call Parse.initialize before using Parse.'); + } + if (typeof path !== 'string') { + throw new Error('Tried to get a Storage path that was not a String.'); + } + if (path[0] === '/') { + path = path.substr(1); + } + return 'Parse/' + _CoreManager2.default.get('APPLICATION_ID') + '/' + path; + }, + _clear: function () { + var controller = _CoreManager2.default.getStorageController(); + if (controller.hasOwnProperty('clear')) { + controller.clear(); + } + } +}; + +_CoreManager2.default.setStorageController(_dereq_('./StorageController.browser')); +},{"./CoreManager":3,"./ParsePromise":20,"./StorageController.browser":30}],30:[function(_dereq_,module,exports){ +'use strict'; + +var _ParsePromise = _dereq_('./ParsePromise'); + +var _ParsePromise2 = _interopRequireDefault(_ParsePromise); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +var StorageController = { + async: 0, + + getItem: function (path) { + return localStorage.getItem(path); + }, + setItem: function (path, value) { + try { + localStorage.setItem(path, value); + } catch (e) { + // Quota exceeded, possibly due to Safari Private Browsing mode + } + }, + removeItem: function (path) { + localStorage.removeItem(path); + }, + clear: function () { + localStorage.clear(); + } +}; /** + * 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. + * + * + */ + +module.exports = StorageController; +},{"./ParsePromise":20}],31:[function(_dereq_,module,exports){ +'use strict'; + +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 }; +} + +var TaskQueue = function () { + function TaskQueue() { + (0, _classCallCheck3.default)(this, TaskQueue); + + this.queue = []; + } + + (0, _createClass3.default)(TaskQueue, [{ + key: 'enqueue', + value: function (task) { + var _this = this; + + var taskComplete = new _ParsePromise2.default(); + this.queue.push({ + task: task, + _completion: taskComplete + }); + if (this.queue.length === 1) { + task().then(function () { + _this._dequeue(); + taskComplete.resolve(); + }, function (error) { + _this._dequeue(); + taskComplete.reject(error); + }); + } + return taskComplete; + } + }, { + key: '_dequeue', + value: function () { + var _this2 = this; + + this.queue.shift(); + if (this.queue.length) { + var next = this.queue[0]; + next.task().then(function () { + _this2._dequeue(); + next._completion.resolve(); + }, function (error) { + _this2._dequeue(); + next._completion.reject(error); + }); + } + } + }]); + return TaskQueue; +}(); /** + * 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. + * + * + */ + +module.exports = TaskQueue; +},{"./ParsePromise":20,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57}],32:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _weakMap = _dereq_('babel-runtime/core-js/weak-map'); + +var _weakMap2 = _interopRequireDefault(_weakMap); + +exports.getState = getState; +exports.initializeState = initializeState; +exports.removeState = removeState; +exports.getServerData = getServerData; +exports.setServerData = setServerData; +exports.getPendingOps = getPendingOps; +exports.setPendingOp = setPendingOp; +exports.pushPendingState = pushPendingState; +exports.popPendingState = popPendingState; +exports.mergeFirstPendingState = mergeFirstPendingState; +exports.getObjectCache = getObjectCache; +exports.estimateAttribute = estimateAttribute; +exports.estimateAttributes = estimateAttributes; +exports.commitServerChanges = commitServerChanges; +exports.enqueueTask = enqueueTask; +exports.duplicateState = duplicateState; +exports.clearAllState = clearAllState; + +var _ObjectStateMutations = _dereq_('./ObjectStateMutations'); + +var ObjectStateMutations = _interopRequireWildcard(_ObjectStateMutations); + +var _TaskQueue = _dereq_('./TaskQueue'); + +var _TaskQueue2 = _interopRequireDefault(_TaskQueue); + +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 }; +} + +/** + * 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 objectState = new _weakMap2.default(); + +function getState(obj) { + var classData = objectState.get(obj); + return classData || null; +} + +function initializeState(obj, initial) { + var state = getState(obj); + if (state) { + return state; + } + if (!initial) { + initial = { + serverData: {}, + pendingOps: [{}], + objectCache: {}, + tasks: new _TaskQueue2.default(), + existed: false + }; + } + state = initial; + objectState.set(obj, state); + return state; +} + +function removeState(obj) { + var state = getState(obj); + if (state === null) { + return null; + } + objectState.delete(obj); + return state; +} + +function getServerData(obj) { + var state = getState(obj); + if (state) { + return state.serverData; + } + return {}; +} + +function setServerData(obj, attributes) { + var serverData = initializeState(obj).serverData; + ObjectStateMutations.setServerData(serverData, attributes); +} + +function getPendingOps(obj) { + var state = getState(obj); + if (state) { + return state.pendingOps; + } + return [{}]; +} + +function setPendingOp(obj, attr, op) { + var pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.setPendingOp(pendingOps, attr, op); +} + +function pushPendingState(obj) { + var pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.pushPendingState(pendingOps); +} + +function popPendingState(obj) { + var pendingOps = initializeState(obj).pendingOps; + return ObjectStateMutations.popPendingState(pendingOps); +} + +function mergeFirstPendingState(obj) { + var pendingOps = getPendingOps(obj); + ObjectStateMutations.mergeFirstPendingState(pendingOps); +} + +function getObjectCache(obj) { + var state = getState(obj); + if (state) { + return state.objectCache; + } + return {}; +} + +function estimateAttribute(obj, attr) { + var serverData = getServerData(obj); + var pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttribute(serverData, pendingOps, obj.className, obj.id, attr); +} + +function estimateAttributes(obj) { + var serverData = getServerData(obj); + var pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttributes(serverData, pendingOps, obj.className, obj.id); +} + +function commitServerChanges(obj, changes) { + var state = initializeState(obj); + ObjectStateMutations.commitServerChanges(state.serverData, state.objectCache, changes); +} + +function enqueueTask(obj, task) { + var state = initializeState(obj); + return state.tasks.enqueue(task); +} + +function duplicateState(source, dest) { + var oldState = initializeState(source); + var newState = initializeState(dest); + for (var key in oldState.serverData) { + newState.serverData[key] = oldState.serverData[key]; + } + for (var index = 0; index < oldState.pendingOps.length; index++) { + for (var _key in oldState.pendingOps[index]) { + newState.pendingOps[index][_key] = oldState.pendingOps[index][_key]; + } + } + for (var _key2 in oldState.objectCache) { + newState.objectCache[_key2] = oldState.objectCache[_key2]; + } + newState.existed = oldState.existed; +} + +function clearAllState() { + objectState = new _weakMap2.default(); +} +},{"./ObjectStateMutations":9,"./TaskQueue":31,"babel-runtime/core-js/weak-map":55}],33:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = arrayContainsObject; + +var _ParseObject = _dereq_('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +function arrayContainsObject(array, object) { + if (array.indexOf(object) > -1) { + return true; + } + for (var i = 0; i < array.length; i++) { + if (array[i] instanceof _ParseObject2.default && array[i].className === object.className && array[i]._getId() === object._getId()) { + return true; + } + } + return 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. + * + * + */ +},{"./ParseObject":18}],34:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = _dereq_('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +exports.default = canBeSerialized; + +var _ParseFile = _dereq_('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseObject = _dereq_('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseRelation = _dereq_('./ParseRelation'); + +var _ParseRelation2 = _interopRequireDefault(_ParseRelation); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +function canBeSerialized(obj) { + if (!(obj instanceof _ParseObject2.default)) { + return true; + } + var attributes = obj.attributes; + for (var attr in attributes) { + var val = attributes[attr]; + if (!canBeSerializedHelper(val)) { + return false; + } + } + return true; +} /** + * 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 canBeSerializedHelper(value) { + if ((typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) !== 'object') { + return true; + } + if (value instanceof _ParseRelation2.default) { + return true; + } + if (value instanceof _ParseObject2.default) { + return !!value.id; + } + if (value instanceof _ParseFile2.default) { + if (value.url()) { + return true; + } + return false; + } + if (Array.isArray(value)) { + for (var i = 0; i < value.length; i++) { + if (!canBeSerializedHelper(value[i])) { + return false; + } + } + return true; + } + for (var k in value) { + if (!canBeSerializedHelper(value[k])) { + return false; + } + } + return true; +} +},{"./ParseFile":14,"./ParseObject":18,"./ParseRelation":22,"babel-runtime/helpers/typeof":61}],35:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = _dereq_('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +exports.default = decode; + +var _ParseACL = _dereq_('./ParseACL'); + +var _ParseACL2 = _interopRequireDefault(_ParseACL); + +var _ParseFile = _dereq_('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseGeoPoint = _dereq_('./ParseGeoPoint'); + +var _ParseGeoPoint2 = _interopRequireDefault(_ParseGeoPoint); + +var _ParseObject = _dereq_('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseOp = _dereq_('./ParseOp'); + +var _ParseRelation = _dereq_('./ParseRelation'); + +var _ParseRelation2 = _interopRequireDefault(_ParseRelation); + +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 decode(value) { + if (value === null || (typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) !== 'object') { + return value; + } + if (Array.isArray(value)) { + var dup = []; + value.forEach(function (v, i) { + dup[i] = decode(v); + }); + return dup; + } + if (typeof value.__op === 'string') { + return (0, _ParseOp.opFromJSON)(value); + } + if (value.__type === 'Pointer' && value.className) { + return _ParseObject2.default.fromJSON(value); + } + if (value.__type === 'Object' && value.className) { + return _ParseObject2.default.fromJSON(value); + } + if (value.__type === 'Relation') { + // The parent and key fields will be populated by the parent + var relation = new _ParseRelation2.default(null, null); + relation.targetClassName = value.className; + return relation; + } + if (value.__type === 'Date') { + return new Date(value.iso); + } + if (value.__type === 'File') { + return _ParseFile2.default.fromJSON(value); + } + if (value.__type === 'GeoPoint') { + return new _ParseGeoPoint2.default({ + latitude: value.latitude, + longitude: value.longitude + }); + } + var copy = {}; + for (var k in value) { + copy[k] = decode(value[k]); + } + return copy; +} +},{"./ParseACL":11,"./ParseFile":14,"./ParseGeoPoint":15,"./ParseObject":18,"./ParseOp":19,"./ParseRelation":22,"babel-runtime/helpers/typeof":61}],36:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = _dereq_('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +var _keys = _dereq_('babel-runtime/core-js/object/keys'); + +var _keys2 = _interopRequireDefault(_keys); + +exports.default = function (value, disallowObjects, forcePointers, seen) { + return encode(value, !!disallowObjects, !!forcePointers, seen || []); +}; + +var _ParseACL = _dereq_('./ParseACL'); + +var _ParseACL2 = _interopRequireDefault(_ParseACL); + +var _ParseFile = _dereq_('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseGeoPoint = _dereq_('./ParseGeoPoint'); + +var _ParseGeoPoint2 = _interopRequireDefault(_ParseGeoPoint); + +var _ParseObject = _dereq_('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseOp = _dereq_('./ParseOp'); + +var _ParseRelation = _dereq_('./ParseRelation'); + +var _ParseRelation2 = _interopRequireDefault(_ParseRelation); + +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 toString = Object.prototype.toString; + +function encode(value, disallowObjects, forcePointers, seen) { + if (value instanceof _ParseObject2.default) { + if (disallowObjects) { + throw new Error('Parse Objects not allowed here'); + } + var seenEntry = value.id ? value.className + ':' + value.id : value; + if (forcePointers || !seen || seen.indexOf(seenEntry) > -1 || value.dirty() || (0, _keys2.default)(value._getServerData()).length < 1) { + return value.toPointer(); + } + seen = seen.concat(seenEntry); + return value._toFullJSON(seen); + } + if (value instanceof _ParseOp.Op || value instanceof _ParseACL2.default || value instanceof _ParseGeoPoint2.default || value instanceof _ParseRelation2.default) { + return value.toJSON(); + } + if (value instanceof _ParseFile2.default) { + if (!value.url()) { + throw new Error('Tried to encode an unsaved file.'); + } + return value.toJSON(); + } + if (toString.call(value) === '[object Date]') { + if (isNaN(value)) { + throw new Error('Tried to encode an invalid date.'); + } + return { __type: 'Date', iso: value.toJSON() }; + } + if (toString.call(value) === '[object RegExp]' && typeof value.source === 'string') { + return value.source; + } + + if (Array.isArray(value)) { + return value.map(function (v) { + return encode(v, disallowObjects, forcePointers, seen); + }); + } + + if (value && (typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) === 'object') { + var output = {}; + for (var k in value) { + output[k] = encode(value[k], disallowObjects, forcePointers, seen); + } + return output; + } + + return value; +} +},{"./ParseACL":11,"./ParseFile":14,"./ParseGeoPoint":15,"./ParseObject":18,"./ParseOp":19,"./ParseRelation":22,"babel-runtime/core-js/object/keys":51,"babel-runtime/helpers/typeof":61}],37:[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); + +exports.default = equals; + +var _ParseACL = _dereq_('./ParseACL'); + +var _ParseACL2 = _interopRequireDefault(_ParseACL); + +var _ParseFile = _dereq_('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseGeoPoint = _dereq_('./ParseGeoPoint'); + +var _ParseGeoPoint2 = _interopRequireDefault(_ParseGeoPoint); + +var _ParseObject = _dereq_('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +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 equals(a, b) { + if ((typeof a === 'undefined' ? 'undefined' : (0, _typeof3.default)(a)) !== (typeof b === 'undefined' ? 'undefined' : (0, _typeof3.default)(b))) { + return false; + } + + if (!a || (typeof a === 'undefined' ? 'undefined' : (0, _typeof3.default)(a)) !== 'object') { + // a is a primitive + return a === b; + } + + if (Array.isArray(a) || Array.isArray(b)) { + if (!Array.isArray(a) || !Array.isArray(b)) { + return false; + } + if (a.length !== b.length) { + return false; + } + for (var i = a.length; i--;) { + if (!equals(a[i], b[i])) { + return false; + } + } + return true; + } + + if (a instanceof _ParseACL2.default || a instanceof _ParseFile2.default || a instanceof _ParseGeoPoint2.default || a instanceof _ParseObject2.default) { + return a.equals(b); + } + + if ((0, _keys2.default)(a).length !== (0, _keys2.default)(b).length) { + return false; + } + for (var k in a) { + if (!equals(a[k], b[k])) { + return false; + } + } + return true; +} +},{"./ParseACL":11,"./ParseFile":14,"./ParseGeoPoint":15,"./ParseObject":18,"babel-runtime/core-js/object/keys":51,"babel-runtime/helpers/typeof":61}],38:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = escape; +/** + * 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 encoded = { + '&': '&', + '<': '<', + '>': '>', + '/': '/', + '\'': ''', + '"': '"' +}; + +function escape(str) { + return str.replace(/[&<>\/'"]/g, function (char) { + return encoded[char]; + }); +} +},{}],39:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = isRevocableSession; +/** + * 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 isRevocableSession(token) { + return token.indexOf('r:') > -1; +} +},{}],40:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = parseDate; +/** + * 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 parseDate(iso8601) { + var regexp = new RegExp('^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})' + 'T' + '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})' + '(.([0-9]+))?' + 'Z$'); + var match = regexp.exec(iso8601); + if (!match) { + return null; + } + + var year = match[1] || 0; + var month = (match[2] || 1) - 1; + var day = match[3] || 0; + var hour = match[4] || 0; + var minute = match[5] || 0; + var second = match[6] || 0; + var milli = match[8] || 0; + + return new Date(Date.UTC(year, month, day, hour, minute, second, milli)); +} +},{}],41:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = unique; + +var _arrayContainsObject = _dereq_('./arrayContainsObject'); + +var _arrayContainsObject2 = _interopRequireDefault(_arrayContainsObject); + +var _ParseObject = _dereq_('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +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 unique(arr) { + var uniques = []; + arr.forEach(function (value) { + if (value instanceof _ParseObject2.default) { + if (!(0, _arrayContainsObject2.default)(uniques, value)) { + uniques.push(value); + } + } else { + if (uniques.indexOf(value) < 0) { + uniques.push(value); + } + } + }); + return uniques; +} +},{"./ParseObject":18,"./arrayContainsObject":33}],42:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = _dereq_('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +exports.default = unsavedChildren; + +var _ParseFile = _dereq_('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseObject = _dereq_('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseRelation = _dereq_('./ParseRelation'); + +var _ParseRelation2 = _interopRequireDefault(_ParseRelation); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Return an array of unsaved children, which are either Parse Objects or Files. + * If it encounters any dirty Objects without Ids, it will throw an exception. + */ +function unsavedChildren(obj, allowDeepUnsaved) { + var encountered = { + objects: {}, + files: [] + }; + var identifier = obj.className + ':' + obj._getId(); + encountered.objects[identifier] = obj.dirty() ? obj : true; + var attributes = obj.attributes; + for (var attr in attributes) { + if ((0, _typeof3.default)(attributes[attr]) === 'object') { + traverse(attributes[attr], encountered, false, !!allowDeepUnsaved); + } + } + var unsaved = []; + for (var id in encountered.objects) { + if (id !== identifier && encountered.objects[id] !== true) { + unsaved.push(encountered.objects[id]); + } + } + return unsaved.concat(encountered.files); +} /** + * 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 traverse(obj, encountered, shouldThrow, allowDeepUnsaved) { + if (obj instanceof _ParseObject2.default) { + if (!obj.id && shouldThrow) { + throw new Error('Cannot create a pointer to an unsaved Object.'); + } + var identifier = obj.className + ':' + obj._getId(); + if (!encountered.objects[identifier]) { + encountered.objects[identifier] = obj.dirty() ? obj : true; + var attributes = obj.attributes; + for (var attr in attributes) { + if ((0, _typeof3.default)(attributes[attr]) === 'object') { + traverse(attributes[attr], encountered, !allowDeepUnsaved, allowDeepUnsaved); + } + } + } + return; + } + if (obj instanceof _ParseFile2.default) { + if (!obj.url() && encountered.files.indexOf(obj) < 0) { + encountered.files.push(obj); + } + return; + } + if (obj instanceof _ParseRelation2.default) { + return; + } + if (Array.isArray(obj)) { + obj.forEach(function (el) { + if ((typeof el === 'undefined' ? 'undefined' : (0, _typeof3.default)(el)) === 'object') { + traverse(el, encountered, shouldThrow, allowDeepUnsaved); + } + }); + } + for (var k in obj) { + if ((0, _typeof3.default)(obj[k]) === 'object') { + traverse(obj[k], encountered, shouldThrow, allowDeepUnsaved); + } + } +} +},{"./ParseFile":14,"./ParseObject":18,"./ParseRelation":22,"babel-runtime/helpers/typeof":61}],43:[function(_dereq_,module,exports){ +module.exports = { "default": _dereq_("core-js/library/fn/get-iterator"), __esModule: true }; +},{"core-js/library/fn/get-iterator":62}],44:[function(_dereq_,module,exports){ +module.exports = { "default": _dereq_("core-js/library/fn/json/stringify"), __esModule: true }; +},{"core-js/library/fn/json/stringify":63}],45:[function(_dereq_,module,exports){ +module.exports = { "default": _dereq_("core-js/library/fn/map"), __esModule: true }; +},{"core-js/library/fn/map":64}],46:[function(_dereq_,module,exports){ +module.exports = { "default": _dereq_("core-js/library/fn/object/create"), __esModule: true }; +},{"core-js/library/fn/object/create":65}],47:[function(_dereq_,module,exports){ +module.exports = { "default": _dereq_("core-js/library/fn/object/define-property"), __esModule: true }; +},{"core-js/library/fn/object/define-property":66}],48:[function(_dereq_,module,exports){ +module.exports = { "default": _dereq_("core-js/library/fn/object/freeze"), __esModule: true }; +},{"core-js/library/fn/object/freeze":67}],49:[function(_dereq_,module,exports){ +module.exports = { "default": _dereq_("core-js/library/fn/object/get-own-property-descriptor"), __esModule: true }; +},{"core-js/library/fn/object/get-own-property-descriptor":68}],50:[function(_dereq_,module,exports){ +module.exports = { "default": _dereq_("core-js/library/fn/object/get-prototype-of"), __esModule: true }; +},{"core-js/library/fn/object/get-prototype-of":69}],51:[function(_dereq_,module,exports){ +module.exports = { "default": _dereq_("core-js/library/fn/object/keys"), __esModule: true }; +},{"core-js/library/fn/object/keys":70}],52:[function(_dereq_,module,exports){ +module.exports = { "default": _dereq_("core-js/library/fn/object/set-prototype-of"), __esModule: true }; +},{"core-js/library/fn/object/set-prototype-of":71}],53:[function(_dereq_,module,exports){ +module.exports = { "default": _dereq_("core-js/library/fn/symbol"), __esModule: true }; +},{"core-js/library/fn/symbol":72}],54:[function(_dereq_,module,exports){ +module.exports = { "default": _dereq_("core-js/library/fn/symbol/iterator"), __esModule: true }; +},{"core-js/library/fn/symbol/iterator":73}],55:[function(_dereq_,module,exports){ +module.exports = { "default": _dereq_("core-js/library/fn/weak-map"), __esModule: true }; +},{"core-js/library/fn/weak-map":74}],56:[function(_dereq_,module,exports){ +"use strict"; + +exports.__esModule = true; + +exports.default = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +}; +},{}],57:[function(_dereq_,module,exports){ +"use strict"; + +exports.__esModule = true; + +var _defineProperty = _dereq_("../core-js/object/define-property"); + +var _defineProperty2 = _interopRequireDefault(_defineProperty); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.default = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + (0, _defineProperty2.default)(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; +}(); +},{"../core-js/object/define-property":47}],58:[function(_dereq_,module,exports){ +"use strict"; + +exports.__esModule = true; + +var _getPrototypeOf = _dereq_("../core-js/object/get-prototype-of"); + +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +var _getOwnPropertyDescriptor = _dereq_("../core-js/object/get-own-property-descriptor"); + +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.default = function get(object, property, receiver) { + if (object === null) object = Function.prototype; + var desc = (0, _getOwnPropertyDescriptor2.default)(object, property); + + if (desc === undefined) { + var parent = (0, _getPrototypeOf2.default)(object); + + if (parent === null) { + return undefined; + } else { + return get(parent, property, receiver); + } + } else if ("value" in desc) { + return desc.value; + } else { + var getter = desc.get; + + if (getter === undefined) { + return undefined; + } + + return getter.call(receiver); + } +}; +},{"../core-js/object/get-own-property-descriptor":49,"../core-js/object/get-prototype-of":50}],59:[function(_dereq_,module,exports){ +"use strict"; + +exports.__esModule = true; + +var _setPrototypeOf = _dereq_("../core-js/object/set-prototype-of"); + +var _setPrototypeOf2 = _interopRequireDefault(_setPrototypeOf); + +var _create = _dereq_("../core-js/object/create"); + +var _create2 = _interopRequireDefault(_create); + +var _typeof2 = _dereq_("../helpers/typeof"); + +var _typeof3 = _interopRequireDefault(_typeof2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.default = function (subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === "undefined" ? "undefined" : (0, _typeof3.default)(superClass))); + } + + subClass.prototype = (0, _create2.default)(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) _setPrototypeOf2.default ? (0, _setPrototypeOf2.default)(subClass, superClass) : subClass.__proto__ = superClass; +}; +},{"../core-js/object/create":46,"../core-js/object/set-prototype-of":52,"../helpers/typeof":61}],60:[function(_dereq_,module,exports){ +"use strict"; + +exports.__esModule = true; + +var _typeof2 = _dereq_("../helpers/typeof"); + +var _typeof3 = _interopRequireDefault(_typeof2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.default = function (self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && ((typeof call === "undefined" ? "undefined" : (0, _typeof3.default)(call)) === "object" || typeof call === "function") ? call : self; +}; +},{"../helpers/typeof":61}],61:[function(_dereq_,module,exports){ +"use strict"; + +exports.__esModule = true; + +var _iterator = _dereq_("../core-js/symbol/iterator"); + +var _iterator2 = _interopRequireDefault(_iterator); + +var _symbol = _dereq_("../core-js/symbol"); + +var _symbol2 = _interopRequireDefault(_symbol); + +var _typeof = typeof _symbol2.default === "function" && typeof _iterator2.default === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof _symbol2.default === "function" && obj.constructor === _symbol2.default && obj !== _symbol2.default.prototype ? "symbol" : typeof obj; }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.default = typeof _symbol2.default === "function" && _typeof(_iterator2.default) === "symbol" ? function (obj) { + return typeof obj === "undefined" ? "undefined" : _typeof(obj); +} : function (obj) { + return obj && typeof _symbol2.default === "function" && obj.constructor === _symbol2.default && obj !== _symbol2.default.prototype ? "symbol" : typeof obj === "undefined" ? "undefined" : _typeof(obj); +}; +},{"../core-js/symbol":53,"../core-js/symbol/iterator":54}],62:[function(_dereq_,module,exports){ +_dereq_('../modules/web.dom.iterable'); +_dereq_('../modules/es6.string.iterator'); +module.exports = _dereq_('../modules/core.get-iterator'); +},{"../modules/core.get-iterator":150,"../modules/es6.string.iterator":161,"../modules/web.dom.iterable":167}],63:[function(_dereq_,module,exports){ +var core = _dereq_('../../modules/_core') + , $JSON = core.JSON || (core.JSON = {stringify: JSON.stringify}); +module.exports = function stringify(it){ // eslint-disable-line no-unused-vars + return $JSON.stringify.apply($JSON, arguments); +}; +},{"../../modules/_core":90}],64:[function(_dereq_,module,exports){ +_dereq_('../modules/es6.object.to-string'); +_dereq_('../modules/es6.string.iterator'); +_dereq_('../modules/web.dom.iterable'); +_dereq_('../modules/es6.map'); +_dereq_('../modules/es7.map.to-json'); +module.exports = _dereq_('../modules/_core').Map; +},{"../modules/_core":90,"../modules/es6.map":152,"../modules/es6.object.to-string":160,"../modules/es6.string.iterator":161,"../modules/es7.map.to-json":164,"../modules/web.dom.iterable":167}],65:[function(_dereq_,module,exports){ +_dereq_('../../modules/es6.object.create'); +var $Object = _dereq_('../../modules/_core').Object; +module.exports = function create(P, D){ + return $Object.create(P, D); +}; +},{"../../modules/_core":90,"../../modules/es6.object.create":153}],66:[function(_dereq_,module,exports){ +_dereq_('../../modules/es6.object.define-property'); +var $Object = _dereq_('../../modules/_core').Object; +module.exports = function defineProperty(it, key, desc){ + return $Object.defineProperty(it, key, desc); +}; +},{"../../modules/_core":90,"../../modules/es6.object.define-property":154}],67:[function(_dereq_,module,exports){ +_dereq_('../../modules/es6.object.freeze'); +module.exports = _dereq_('../../modules/_core').Object.freeze; +},{"../../modules/_core":90,"../../modules/es6.object.freeze":155}],68:[function(_dereq_,module,exports){ +_dereq_('../../modules/es6.object.get-own-property-descriptor'); +var $Object = _dereq_('../../modules/_core').Object; +module.exports = function getOwnPropertyDescriptor(it, key){ + return $Object.getOwnPropertyDescriptor(it, key); +}; +},{"../../modules/_core":90,"../../modules/es6.object.get-own-property-descriptor":156}],69:[function(_dereq_,module,exports){ +_dereq_('../../modules/es6.object.get-prototype-of'); +module.exports = _dereq_('../../modules/_core').Object.getPrototypeOf; +},{"../../modules/_core":90,"../../modules/es6.object.get-prototype-of":157}],70:[function(_dereq_,module,exports){ +_dereq_('../../modules/es6.object.keys'); +module.exports = _dereq_('../../modules/_core').Object.keys; +},{"../../modules/_core":90,"../../modules/es6.object.keys":158}],71:[function(_dereq_,module,exports){ +_dereq_('../../modules/es6.object.set-prototype-of'); +module.exports = _dereq_('../../modules/_core').Object.setPrototypeOf; +},{"../../modules/_core":90,"../../modules/es6.object.set-prototype-of":159}],72:[function(_dereq_,module,exports){ +_dereq_('../../modules/es6.symbol'); +_dereq_('../../modules/es6.object.to-string'); +_dereq_('../../modules/es7.symbol.async-iterator'); +_dereq_('../../modules/es7.symbol.observable'); +module.exports = _dereq_('../../modules/_core').Symbol; +},{"../../modules/_core":90,"../../modules/es6.object.to-string":160,"../../modules/es6.symbol":162,"../../modules/es7.symbol.async-iterator":165,"../../modules/es7.symbol.observable":166}],73:[function(_dereq_,module,exports){ +_dereq_('../../modules/es6.string.iterator'); +_dereq_('../../modules/web.dom.iterable'); +module.exports = _dereq_('../../modules/_wks-ext').f('iterator'); +},{"../../modules/_wks-ext":147,"../../modules/es6.string.iterator":161,"../../modules/web.dom.iterable":167}],74:[function(_dereq_,module,exports){ +_dereq_('../modules/es6.object.to-string'); +_dereq_('../modules/web.dom.iterable'); +_dereq_('../modules/es6.weak-map'); +module.exports = _dereq_('../modules/_core').WeakMap; +},{"../modules/_core":90,"../modules/es6.object.to-string":160,"../modules/es6.weak-map":163,"../modules/web.dom.iterable":167}],75:[function(_dereq_,module,exports){ +module.exports = function(it){ + if(typeof it != 'function')throw TypeError(it + ' is not a function!'); + return it; +}; +},{}],76:[function(_dereq_,module,exports){ +module.exports = function(){ /* empty */ }; +},{}],77:[function(_dereq_,module,exports){ +module.exports = function(it, Constructor, name, forbiddenField){ + if(!(it instanceof Constructor) || (forbiddenField !== undefined && forbiddenField in it)){ + throw TypeError(name + ': incorrect invocation!'); + } return it; +}; +},{}],78:[function(_dereq_,module,exports){ +var isObject = _dereq_('./_is-object'); +module.exports = function(it){ + if(!isObject(it))throw TypeError(it + ' is not an object!'); + return it; +}; +},{"./_is-object":108}],79:[function(_dereq_,module,exports){ +var forOf = _dereq_('./_for-of'); + +module.exports = function(iter, ITERATOR){ + var result = []; + forOf(iter, false, result.push, result, ITERATOR); + return result; +}; + +},{"./_for-of":99}],80:[function(_dereq_,module,exports){ +// false -> Array#indexOf +// true -> Array#includes +var toIObject = _dereq_('./_to-iobject') + , toLength = _dereq_('./_to-length') + , toIndex = _dereq_('./_to-index'); +module.exports = function(IS_INCLUDES){ + return function($this, el, fromIndex){ + var O = toIObject($this) + , length = toLength(O.length) + , index = toIndex(fromIndex, length) + , value; + // Array#includes uses SameValueZero equality algorithm + if(IS_INCLUDES && el != el)while(length > index){ + value = O[index++]; + if(value != value)return true; + // Array#toIndex ignores holes, Array#includes - not + } else for(;length > index; index++)if(IS_INCLUDES || index in O){ + if(O[index] === el)return IS_INCLUDES || index || 0; + } return !IS_INCLUDES && -1; + }; +}; +},{"./_to-index":139,"./_to-iobject":141,"./_to-length":142}],81:[function(_dereq_,module,exports){ +// 0 -> Array#forEach +// 1 -> Array#map +// 2 -> Array#filter +// 3 -> Array#some +// 4 -> Array#every +// 5 -> Array#find +// 6 -> Array#findIndex +var ctx = _dereq_('./_ctx') + , IObject = _dereq_('./_iobject') + , toObject = _dereq_('./_to-object') + , toLength = _dereq_('./_to-length') + , asc = _dereq_('./_array-species-create'); +module.exports = function(TYPE, $create){ + var IS_MAP = TYPE == 1 + , IS_FILTER = TYPE == 2 + , IS_SOME = TYPE == 3 + , IS_EVERY = TYPE == 4 + , IS_FIND_INDEX = TYPE == 6 + , NO_HOLES = TYPE == 5 || IS_FIND_INDEX + , create = $create || asc; + return function($this, callbackfn, that){ + var O = toObject($this) + , self = IObject(O) + , f = ctx(callbackfn, that, 3) + , length = toLength(self.length) + , index = 0 + , result = IS_MAP ? create($this, length) : IS_FILTER ? create($this, 0) : undefined + , val, res; + for(;length > index; index++)if(NO_HOLES || index in self){ + val = self[index]; + res = f(val, index, O); + if(TYPE){ + if(IS_MAP)result[index] = res; // map + else if(res)switch(TYPE){ + case 3: return true; // some + case 5: return val; // find + case 6: return index; // findIndex + case 2: result.push(val); // filter + } else if(IS_EVERY)return false; // every + } + } + return IS_FIND_INDEX ? -1 : IS_SOME || IS_EVERY ? IS_EVERY : result; + }; +}; +},{"./_array-species-create":83,"./_ctx":91,"./_iobject":105,"./_to-length":142,"./_to-object":143}],82:[function(_dereq_,module,exports){ +var isObject = _dereq_('./_is-object') + , isArray = _dereq_('./_is-array') + , SPECIES = _dereq_('./_wks')('species'); + +module.exports = function(original){ + var C; + if(isArray(original)){ + C = original.constructor; + // cross-realm fallback + if(typeof C == 'function' && (C === Array || isArray(C.prototype)))C = undefined; + if(isObject(C)){ + C = C[SPECIES]; + if(C === null)C = undefined; + } + } return C === undefined ? Array : C; +}; +},{"./_is-array":107,"./_is-object":108,"./_wks":148}],83:[function(_dereq_,module,exports){ +// 9.4.2.3 ArraySpeciesCreate(originalArray, length) +var speciesConstructor = _dereq_('./_array-species-constructor'); + +module.exports = function(original, length){ + return new (speciesConstructor(original))(length); +}; +},{"./_array-species-constructor":82}],84:[function(_dereq_,module,exports){ +// getting tag from 19.1.3.6 Object.prototype.toString() +var cof = _dereq_('./_cof') + , TAG = _dereq_('./_wks')('toStringTag') + // ES3 wrong here + , ARG = cof(function(){ return arguments; }()) == 'Arguments'; + +// fallback for IE11 Script Access Denied error +var tryGet = function(it, key){ + try { + return it[key]; + } catch(e){ /* empty */ } +}; + +module.exports = function(it){ + var O, T, B; + return it === undefined ? 'Undefined' : it === null ? 'Null' + // @@toStringTag case + : typeof (T = tryGet(O = Object(it), TAG)) == 'string' ? T + // builtinTag case + : ARG ? cof(O) + // ES3 arguments fallback + : (B = cof(O)) == 'Object' && typeof O.callee == 'function' ? 'Arguments' : B; +}; +},{"./_cof":85,"./_wks":148}],85:[function(_dereq_,module,exports){ +var toString = {}.toString; + +module.exports = function(it){ + return toString.call(it).slice(8, -1); +}; +},{}],86:[function(_dereq_,module,exports){ +'use strict'; +var dP = _dereq_('./_object-dp').f + , create = _dereq_('./_object-create') + , redefineAll = _dereq_('./_redefine-all') + , ctx = _dereq_('./_ctx') + , anInstance = _dereq_('./_an-instance') + , defined = _dereq_('./_defined') + , forOf = _dereq_('./_for-of') + , $iterDefine = _dereq_('./_iter-define') + , step = _dereq_('./_iter-step') + , setSpecies = _dereq_('./_set-species') + , DESCRIPTORS = _dereq_('./_descriptors') + , fastKey = _dereq_('./_meta').fastKey + , SIZE = DESCRIPTORS ? '_s' : 'size'; + +var getEntry = function(that, key){ + // fast case + var index = fastKey(key), entry; + if(index !== 'F')return that._i[index]; + // frozen object case + for(entry = that._f; entry; entry = entry.n){ + if(entry.k == key)return entry; + } +}; + +module.exports = { + getConstructor: function(wrapper, NAME, IS_MAP, ADDER){ + var C = wrapper(function(that, iterable){ + anInstance(that, C, NAME, '_i'); + that._i = create(null); // index + that._f = undefined; // first entry + that._l = undefined; // last entry + that[SIZE] = 0; // size + if(iterable != undefined)forOf(iterable, IS_MAP, that[ADDER], that); + }); + redefineAll(C.prototype, { + // 23.1.3.1 Map.prototype.clear() + // 23.2.3.2 Set.prototype.clear() + clear: function clear(){ + for(var that = this, data = that._i, entry = that._f; entry; entry = entry.n){ + entry.r = true; + if(entry.p)entry.p = entry.p.n = undefined; + delete data[entry.i]; + } + that._f = that._l = undefined; + that[SIZE] = 0; + }, + // 23.1.3.3 Map.prototype.delete(key) + // 23.2.3.4 Set.prototype.delete(value) + 'delete': function(key){ + var that = this + , entry = getEntry(that, key); + if(entry){ + var next = entry.n + , prev = entry.p; + delete that._i[entry.i]; + entry.r = true; + if(prev)prev.n = next; + if(next)next.p = prev; + if(that._f == entry)that._f = next; + if(that._l == entry)that._l = prev; + that[SIZE]--; + } return !!entry; + }, + // 23.2.3.6 Set.prototype.forEach(callbackfn, thisArg = undefined) + // 23.1.3.5 Map.prototype.forEach(callbackfn, thisArg = undefined) + forEach: function forEach(callbackfn /*, that = undefined */){ + anInstance(this, C, 'forEach'); + var f = ctx(callbackfn, arguments.length > 1 ? arguments[1] : undefined, 3) + , entry; + while(entry = entry ? entry.n : this._f){ + f(entry.v, entry.k, this); + // revert to the last existing entry + while(entry && entry.r)entry = entry.p; + } + }, + // 23.1.3.7 Map.prototype.has(key) + // 23.2.3.7 Set.prototype.has(value) + has: function has(key){ + return !!getEntry(this, key); + } + }); + if(DESCRIPTORS)dP(C.prototype, 'size', { + get: function(){ + return defined(this[SIZE]); + } + }); + return C; + }, + def: function(that, key, value){ + var entry = getEntry(that, key) + , prev, index; + // change existing entry + if(entry){ + entry.v = value; + // create new entry + } else { + that._l = entry = { + i: index = fastKey(key, true), // <- index + k: key, // <- key + v: value, // <- value + p: prev = that._l, // <- previous entry + n: undefined, // <- next entry + r: false // <- removed + }; + if(!that._f)that._f = entry; + if(prev)prev.n = entry; + that[SIZE]++; + // add to index + if(index !== 'F')that._i[index] = entry; + } return that; + }, + getEntry: getEntry, + setStrong: function(C, NAME, IS_MAP){ + // add .keys, .values, .entries, [@@iterator] + // 23.1.3.4, 23.1.3.8, 23.1.3.11, 23.1.3.12, 23.2.3.5, 23.2.3.8, 23.2.3.10, 23.2.3.11 + $iterDefine(C, NAME, function(iterated, kind){ + this._t = iterated; // target + this._k = kind; // kind + this._l = undefined; // previous + }, function(){ + var that = this + , kind = that._k + , entry = that._l; + // revert to the last existing entry + while(entry && entry.r)entry = entry.p; + // get next entry + if(!that._t || !(that._l = entry = entry ? entry.n : that._t._f)){ + // or finish the iteration + that._t = undefined; + return step(1); + } + // return step by kind + if(kind == 'keys' )return step(0, entry.k); + if(kind == 'values')return step(0, entry.v); + return step(0, [entry.k, entry.v]); + }, IS_MAP ? 'entries' : 'values' , !IS_MAP, true); + + // add [@@species], 23.1.2.2, 23.2.2.2 + setSpecies(NAME); + } +}; +},{"./_an-instance":77,"./_ctx":91,"./_defined":92,"./_descriptors":93,"./_for-of":99,"./_iter-define":111,"./_iter-step":112,"./_meta":116,"./_object-create":118,"./_object-dp":119,"./_redefine-all":131,"./_set-species":134}],87:[function(_dereq_,module,exports){ +// https://github.com/DavidBruant/Map-Set.prototype.toJSON +var classof = _dereq_('./_classof') + , from = _dereq_('./_array-from-iterable'); +module.exports = function(NAME){ + return function toJSON(){ + if(classof(this) != NAME)throw TypeError(NAME + "#toJSON isn't generic"); + return from(this); + }; +}; +},{"./_array-from-iterable":79,"./_classof":84}],88:[function(_dereq_,module,exports){ +'use strict'; +var redefineAll = _dereq_('./_redefine-all') + , getWeak = _dereq_('./_meta').getWeak + , anObject = _dereq_('./_an-object') + , isObject = _dereq_('./_is-object') + , anInstance = _dereq_('./_an-instance') + , forOf = _dereq_('./_for-of') + , createArrayMethod = _dereq_('./_array-methods') + , $has = _dereq_('./_has') + , arrayFind = createArrayMethod(5) + , arrayFindIndex = createArrayMethod(6) + , id = 0; + +// fallback for uncaught frozen keys +var uncaughtFrozenStore = function(that){ + return that._l || (that._l = new UncaughtFrozenStore); +}; +var UncaughtFrozenStore = function(){ + this.a = []; +}; +var findUncaughtFrozen = function(store, key){ + return arrayFind(store.a, function(it){ + return it[0] === key; + }); +}; +UncaughtFrozenStore.prototype = { + get: function(key){ + var entry = findUncaughtFrozen(this, key); + if(entry)return entry[1]; + }, + has: function(key){ + return !!findUncaughtFrozen(this, key); + }, + set: function(key, value){ + var entry = findUncaughtFrozen(this, key); + if(entry)entry[1] = value; + else this.a.push([key, value]); + }, + 'delete': function(key){ + var index = arrayFindIndex(this.a, function(it){ + return it[0] === key; + }); + if(~index)this.a.splice(index, 1); + return !!~index; + } +}; + +module.exports = { + getConstructor: function(wrapper, NAME, IS_MAP, ADDER){ + var C = wrapper(function(that, iterable){ + anInstance(that, C, NAME, '_i'); + that._i = id++; // collection id + that._l = undefined; // leak store for uncaught frozen objects + if(iterable != undefined)forOf(iterable, IS_MAP, that[ADDER], that); + }); + redefineAll(C.prototype, { + // 23.3.3.2 WeakMap.prototype.delete(key) + // 23.4.3.3 WeakSet.prototype.delete(value) + 'delete': function(key){ + if(!isObject(key))return false; + var data = getWeak(key); + if(data === true)return uncaughtFrozenStore(this)['delete'](key); + return data && $has(data, this._i) && delete data[this._i]; + }, + // 23.3.3.4 WeakMap.prototype.has(key) + // 23.4.3.4 WeakSet.prototype.has(value) + has: function has(key){ + if(!isObject(key))return false; + var data = getWeak(key); + if(data === true)return uncaughtFrozenStore(this).has(key); + return data && $has(data, this._i); + } + }); + return C; + }, + def: function(that, key, value){ + var data = getWeak(anObject(key), true); + if(data === true)uncaughtFrozenStore(that).set(key, value); + else data[that._i] = value; + return that; + }, + ufstore: uncaughtFrozenStore +}; +},{"./_an-instance":77,"./_an-object":78,"./_array-methods":81,"./_for-of":99,"./_has":101,"./_is-object":108,"./_meta":116,"./_redefine-all":131}],89:[function(_dereq_,module,exports){ +'use strict'; +var global = _dereq_('./_global') + , $export = _dereq_('./_export') + , meta = _dereq_('./_meta') + , fails = _dereq_('./_fails') + , hide = _dereq_('./_hide') + , redefineAll = _dereq_('./_redefine-all') + , forOf = _dereq_('./_for-of') + , anInstance = _dereq_('./_an-instance') + , isObject = _dereq_('./_is-object') + , setToStringTag = _dereq_('./_set-to-string-tag') + , dP = _dereq_('./_object-dp').f + , each = _dereq_('./_array-methods')(0) + , DESCRIPTORS = _dereq_('./_descriptors'); + +module.exports = function(NAME, wrapper, methods, common, IS_MAP, IS_WEAK){ + var Base = global[NAME] + , C = Base + , ADDER = IS_MAP ? 'set' : 'add' + , proto = C && C.prototype + , O = {}; + if(!DESCRIPTORS || typeof C != 'function' || !(IS_WEAK || proto.forEach && !fails(function(){ + new C().entries().next(); + }))){ + // create collection constructor + C = common.getConstructor(wrapper, NAME, IS_MAP, ADDER); + redefineAll(C.prototype, methods); + meta.NEED = true; + } else { + C = wrapper(function(target, iterable){ + anInstance(target, C, NAME, '_c'); + target._c = new Base; + if(iterable != undefined)forOf(iterable, IS_MAP, target[ADDER], target); + }); + each('add,clear,delete,forEach,get,has,set,keys,values,entries,toJSON'.split(','),function(KEY){ + var IS_ADDER = KEY == 'add' || KEY == 'set'; + if(KEY in proto && !(IS_WEAK && KEY == 'clear'))hide(C.prototype, KEY, function(a, b){ + anInstance(this, C, KEY); + if(!IS_ADDER && IS_WEAK && !isObject(a))return KEY == 'get' ? undefined : false; + var result = this._c[KEY](a === 0 ? 0 : a, b); + return IS_ADDER ? this : result; + }); + }); + if('size' in proto)dP(C.prototype, 'size', { + get: function(){ + return this._c.size; + } + }); + } + + setToStringTag(C, NAME); + + O[NAME] = C; + $export($export.G + $export.W + $export.F, O); + + if(!IS_WEAK)common.setStrong(C, NAME, IS_MAP); + + return C; +}; +},{"./_an-instance":77,"./_array-methods":81,"./_descriptors":93,"./_export":97,"./_fails":98,"./_for-of":99,"./_global":100,"./_hide":102,"./_is-object":108,"./_meta":116,"./_object-dp":119,"./_redefine-all":131,"./_set-to-string-tag":135}],90:[function(_dereq_,module,exports){ +var core = module.exports = {version: '2.4.0'}; +if(typeof __e == 'number')__e = core; // eslint-disable-line no-undef +},{}],91:[function(_dereq_,module,exports){ +// optional / simple context binding +var aFunction = _dereq_('./_a-function'); +module.exports = function(fn, that, length){ + aFunction(fn); + if(that === undefined)return fn; + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + case 2: return function(a, b){ + return fn.call(that, a, b); + }; + case 3: return function(a, b, c){ + return fn.call(that, a, b, c); + }; + } + return function(/* ...args */){ + return fn.apply(that, arguments); + }; +}; +},{"./_a-function":75}],92:[function(_dereq_,module,exports){ +// 7.2.1 RequireObjectCoercible(argument) +module.exports = function(it){ + if(it == undefined)throw TypeError("Can't call method on " + it); + return it; +}; +},{}],93:[function(_dereq_,module,exports){ +// Thank's IE8 for his funny defineProperty +module.exports = !_dereq_('./_fails')(function(){ + return Object.defineProperty({}, 'a', {get: function(){ return 7; }}).a != 7; +}); +},{"./_fails":98}],94:[function(_dereq_,module,exports){ +var isObject = _dereq_('./_is-object') + , document = _dereq_('./_global').document + // in old IE typeof document.createElement is 'object' + , is = isObject(document) && isObject(document.createElement); +module.exports = function(it){ + return is ? document.createElement(it) : {}; +}; +},{"./_global":100,"./_is-object":108}],95:[function(_dereq_,module,exports){ +// IE 8- don't enum bug keys +module.exports = ( + 'constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf' +).split(','); +},{}],96:[function(_dereq_,module,exports){ +// all enumerable object keys, includes symbols +var getKeys = _dereq_('./_object-keys') + , gOPS = _dereq_('./_object-gops') + , pIE = _dereq_('./_object-pie'); +module.exports = function(it){ + var result = getKeys(it) + , getSymbols = gOPS.f; + if(getSymbols){ + var symbols = getSymbols(it) + , isEnum = pIE.f + , i = 0 + , key; + while(symbols.length > i)if(isEnum.call(it, key = symbols[i++]))result.push(key); + } return result; +}; +},{"./_object-gops":124,"./_object-keys":127,"./_object-pie":128}],97:[function(_dereq_,module,exports){ +var global = _dereq_('./_global') + , core = _dereq_('./_core') + , ctx = _dereq_('./_ctx') + , hide = _dereq_('./_hide') + , PROTOTYPE = 'prototype'; + +var $export = function(type, name, source){ + var IS_FORCED = type & $export.F + , IS_GLOBAL = type & $export.G + , IS_STATIC = type & $export.S + , IS_PROTO = type & $export.P + , IS_BIND = type & $export.B + , IS_WRAP = type & $export.W + , exports = IS_GLOBAL ? core : core[name] || (core[name] = {}) + , expProto = exports[PROTOTYPE] + , target = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE] + , key, own, out; + if(IS_GLOBAL)source = name; + for(key in source){ + // contains in native + own = !IS_FORCED && target && target[key] !== undefined; + if(own && key in exports)continue; + // export native or passed + out = own ? target[key] : source[key]; + // prevent global pollution for namespaces + exports[key] = IS_GLOBAL && typeof target[key] != 'function' ? source[key] + // bind timers to global for call from export context + : IS_BIND && own ? ctx(out, global) + // wrap global constructors for prevent change them in library + : IS_WRAP && target[key] == out ? (function(C){ + var F = function(a, b, c){ + if(this instanceof C){ + switch(arguments.length){ + case 0: return new C; + case 1: return new C(a); + case 2: return new C(a, b); + } return new C(a, b, c); + } return C.apply(this, arguments); + }; + F[PROTOTYPE] = C[PROTOTYPE]; + return F; + // make static versions for prototype methods + })(out) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out; + // export proto methods to core.%CONSTRUCTOR%.methods.%NAME% + if(IS_PROTO){ + (exports.virtual || (exports.virtual = {}))[key] = out; + // export proto methods to core.%CONSTRUCTOR%.prototype.%NAME% + if(type & $export.R && expProto && !expProto[key])hide(expProto, key, out); + } + } +}; +// type bitmap +$export.F = 1; // forced +$export.G = 2; // global +$export.S = 4; // static +$export.P = 8; // proto +$export.B = 16; // bind +$export.W = 32; // wrap +$export.U = 64; // safe +$export.R = 128; // real proto method for `library` +module.exports = $export; +},{"./_core":90,"./_ctx":91,"./_global":100,"./_hide":102}],98:[function(_dereq_,module,exports){ +module.exports = function(exec){ + try { + return !!exec(); + } catch(e){ + return true; + } +}; +},{}],99:[function(_dereq_,module,exports){ +var ctx = _dereq_('./_ctx') + , call = _dereq_('./_iter-call') + , isArrayIter = _dereq_('./_is-array-iter') + , anObject = _dereq_('./_an-object') + , toLength = _dereq_('./_to-length') + , getIterFn = _dereq_('./core.get-iterator-method') + , BREAK = {} + , RETURN = {}; +var exports = module.exports = function(iterable, entries, fn, that, ITERATOR){ + var iterFn = ITERATOR ? function(){ return iterable; } : getIterFn(iterable) + , f = ctx(fn, that, entries ? 2 : 1) + , index = 0 + , length, step, iterator, result; + if(typeof iterFn != 'function')throw TypeError(iterable + ' is not iterable!'); + // fast case for arrays with default iterator + if(isArrayIter(iterFn))for(length = toLength(iterable.length); length > index; index++){ + result = entries ? f(anObject(step = iterable[index])[0], step[1]) : f(iterable[index]); + if(result === BREAK || result === RETURN)return result; + } else for(iterator = iterFn.call(iterable); !(step = iterator.next()).done; ){ + result = call(iterator, f, step.value, entries); + if(result === BREAK || result === RETURN)return result; + } +}; +exports.BREAK = BREAK; +exports.RETURN = RETURN; +},{"./_an-object":78,"./_ctx":91,"./_is-array-iter":106,"./_iter-call":109,"./_to-length":142,"./core.get-iterator-method":149}],100:[function(_dereq_,module,exports){ +// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 +var global = module.exports = typeof window != 'undefined' && window.Math == Math + ? window : typeof self != 'undefined' && self.Math == Math ? self : Function('return this')(); +if(typeof __g == 'number')__g = global; // eslint-disable-line no-undef +},{}],101:[function(_dereq_,module,exports){ +var hasOwnProperty = {}.hasOwnProperty; +module.exports = function(it, key){ + return hasOwnProperty.call(it, key); +}; +},{}],102:[function(_dereq_,module,exports){ +var dP = _dereq_('./_object-dp') + , createDesc = _dereq_('./_property-desc'); +module.exports = _dereq_('./_descriptors') ? function(object, key, value){ + return dP.f(object, key, createDesc(1, value)); +} : function(object, key, value){ + object[key] = value; + return object; +}; +},{"./_descriptors":93,"./_object-dp":119,"./_property-desc":130}],103:[function(_dereq_,module,exports){ +module.exports = _dereq_('./_global').document && document.documentElement; +},{"./_global":100}],104:[function(_dereq_,module,exports){ +module.exports = !_dereq_('./_descriptors') && !_dereq_('./_fails')(function(){ + return Object.defineProperty(_dereq_('./_dom-create')('div'), 'a', {get: function(){ return 7; }}).a != 7; +}); +},{"./_descriptors":93,"./_dom-create":94,"./_fails":98}],105:[function(_dereq_,module,exports){ +// fallback for non-array-like ES3 and non-enumerable old V8 strings +var cof = _dereq_('./_cof'); +module.exports = Object('z').propertyIsEnumerable(0) ? Object : function(it){ + return cof(it) == 'String' ? it.split('') : Object(it); +}; +},{"./_cof":85}],106:[function(_dereq_,module,exports){ +// check on default Array iterator +var Iterators = _dereq_('./_iterators') + , ITERATOR = _dereq_('./_wks')('iterator') + , ArrayProto = Array.prototype; + +module.exports = function(it){ + return it !== undefined && (Iterators.Array === it || ArrayProto[ITERATOR] === it); +}; +},{"./_iterators":113,"./_wks":148}],107:[function(_dereq_,module,exports){ +// 7.2.2 IsArray(argument) +var cof = _dereq_('./_cof'); +module.exports = Array.isArray || function isArray(arg){ + return cof(arg) == 'Array'; +}; +},{"./_cof":85}],108:[function(_dereq_,module,exports){ +module.exports = function(it){ + return typeof it === 'object' ? it !== null : typeof it === 'function'; +}; +},{}],109:[function(_dereq_,module,exports){ +// call something on iterator step with safe closing on error +var anObject = _dereq_('./_an-object'); +module.exports = function(iterator, fn, value, entries){ + try { + return entries ? fn(anObject(value)[0], value[1]) : fn(value); + // 7.4.6 IteratorClose(iterator, completion) + } catch(e){ + var ret = iterator['return']; + if(ret !== undefined)anObject(ret.call(iterator)); + throw e; + } +}; +},{"./_an-object":78}],110:[function(_dereq_,module,exports){ +'use strict'; +var create = _dereq_('./_object-create') + , descriptor = _dereq_('./_property-desc') + , setToStringTag = _dereq_('./_set-to-string-tag') + , IteratorPrototype = {}; + +// 25.1.2.1.1 %IteratorPrototype%[@@iterator]() +_dereq_('./_hide')(IteratorPrototype, _dereq_('./_wks')('iterator'), function(){ return this; }); + +module.exports = function(Constructor, NAME, next){ + Constructor.prototype = create(IteratorPrototype, {next: descriptor(1, next)}); + setToStringTag(Constructor, NAME + ' Iterator'); +}; +},{"./_hide":102,"./_object-create":118,"./_property-desc":130,"./_set-to-string-tag":135,"./_wks":148}],111:[function(_dereq_,module,exports){ +'use strict'; +var LIBRARY = _dereq_('./_library') + , $export = _dereq_('./_export') + , redefine = _dereq_('./_redefine') + , hide = _dereq_('./_hide') + , has = _dereq_('./_has') + , Iterators = _dereq_('./_iterators') + , $iterCreate = _dereq_('./_iter-create') + , setToStringTag = _dereq_('./_set-to-string-tag') + , getPrototypeOf = _dereq_('./_object-gpo') + , ITERATOR = _dereq_('./_wks')('iterator') + , BUGGY = !([].keys && 'next' in [].keys()) // Safari has buggy iterators w/o `next` + , FF_ITERATOR = '@@iterator' + , KEYS = 'keys' + , VALUES = 'values'; + +var returnThis = function(){ return this; }; + +module.exports = function(Base, NAME, Constructor, next, DEFAULT, IS_SET, FORCED){ + $iterCreate(Constructor, NAME, next); + var getMethod = function(kind){ + if(!BUGGY && kind in proto)return proto[kind]; + switch(kind){ + case KEYS: return function keys(){ return new Constructor(this, kind); }; + case VALUES: return function values(){ return new Constructor(this, kind); }; + } return function entries(){ return new Constructor(this, kind); }; + }; + var TAG = NAME + ' Iterator' + , DEF_VALUES = DEFAULT == VALUES + , VALUES_BUG = false + , proto = Base.prototype + , $native = proto[ITERATOR] || proto[FF_ITERATOR] || DEFAULT && proto[DEFAULT] + , $default = $native || getMethod(DEFAULT) + , $entries = DEFAULT ? !DEF_VALUES ? $default : getMethod('entries') : undefined + , $anyNative = NAME == 'Array' ? proto.entries || $native : $native + , methods, key, IteratorPrototype; + // Fix native + if($anyNative){ + IteratorPrototype = getPrototypeOf($anyNative.call(new Base)); + if(IteratorPrototype !== Object.prototype){ + // Set @@toStringTag to native iterators + setToStringTag(IteratorPrototype, TAG, true); + // fix for some old engines + if(!LIBRARY && !has(IteratorPrototype, ITERATOR))hide(IteratorPrototype, ITERATOR, returnThis); + } + } + // fix Array#{values, @@iterator}.name in V8 / FF + if(DEF_VALUES && $native && $native.name !== VALUES){ + VALUES_BUG = true; + $default = function values(){ return $native.call(this); }; + } + // Define iterator + if((!LIBRARY || FORCED) && (BUGGY || VALUES_BUG || !proto[ITERATOR])){ + hide(proto, ITERATOR, $default); + } + // Plug for library + Iterators[NAME] = $default; + Iterators[TAG] = returnThis; + if(DEFAULT){ + methods = { + values: DEF_VALUES ? $default : getMethod(VALUES), + keys: IS_SET ? $default : getMethod(KEYS), + entries: $entries + }; + if(FORCED)for(key in methods){ + if(!(key in proto))redefine(proto, key, methods[key]); + } else $export($export.P + $export.F * (BUGGY || VALUES_BUG), NAME, methods); + } + return methods; +}; +},{"./_export":97,"./_has":101,"./_hide":102,"./_iter-create":110,"./_iterators":113,"./_library":115,"./_object-gpo":125,"./_redefine":132,"./_set-to-string-tag":135,"./_wks":148}],112:[function(_dereq_,module,exports){ +module.exports = function(done, value){ + return {value: value, done: !!done}; +}; +},{}],113:[function(_dereq_,module,exports){ +module.exports = {}; +},{}],114:[function(_dereq_,module,exports){ +var getKeys = _dereq_('./_object-keys') + , toIObject = _dereq_('./_to-iobject'); +module.exports = function(object, el){ + var O = toIObject(object) + , keys = getKeys(O) + , length = keys.length + , index = 0 + , key; + while(length > index)if(O[key = keys[index++]] === el)return key; +}; +},{"./_object-keys":127,"./_to-iobject":141}],115:[function(_dereq_,module,exports){ +module.exports = true; +},{}],116:[function(_dereq_,module,exports){ +var META = _dereq_('./_uid')('meta') + , isObject = _dereq_('./_is-object') + , has = _dereq_('./_has') + , setDesc = _dereq_('./_object-dp').f + , id = 0; +var isExtensible = Object.isExtensible || function(){ + return true; +}; +var FREEZE = !_dereq_('./_fails')(function(){ + return isExtensible(Object.preventExtensions({})); +}); +var setMeta = function(it){ + setDesc(it, META, {value: { + i: 'O' + ++id, // object ID + w: {} // weak collections IDs + }}); +}; +var fastKey = function(it, create){ + // return primitive with prefix + if(!isObject(it))return typeof it == 'symbol' ? it : (typeof it == 'string' ? 'S' : 'P') + it; + if(!has(it, META)){ + // can't set metadata to uncaught frozen object + if(!isExtensible(it))return 'F'; + // not necessary to add metadata + if(!create)return 'E'; + // add missing metadata + setMeta(it); + // return object ID + } return it[META].i; +}; +var getWeak = function(it, create){ + if(!has(it, META)){ + // can't set metadata to uncaught frozen object + if(!isExtensible(it))return true; + // not necessary to add metadata + if(!create)return false; + // add missing metadata + setMeta(it); + // return hash weak collections IDs + } return it[META].w; +}; +// add metadata on freeze-family methods calling +var onFreeze = function(it){ + if(FREEZE && meta.NEED && isExtensible(it) && !has(it, META))setMeta(it); + return it; +}; +var meta = module.exports = { + KEY: META, + NEED: false, + fastKey: fastKey, + getWeak: getWeak, + onFreeze: onFreeze +}; +},{"./_fails":98,"./_has":101,"./_is-object":108,"./_object-dp":119,"./_uid":145}],117:[function(_dereq_,module,exports){ +'use strict'; +// 19.1.2.1 Object.assign(target, source, ...) +var getKeys = _dereq_('./_object-keys') + , gOPS = _dereq_('./_object-gops') + , pIE = _dereq_('./_object-pie') + , toObject = _dereq_('./_to-object') + , IObject = _dereq_('./_iobject') + , $assign = Object.assign; + +// should work with symbols and should have deterministic property order (V8 bug) +module.exports = !$assign || _dereq_('./_fails')(function(){ + var A = {} + , B = {} + , S = Symbol() + , K = 'abcdefghijklmnopqrst'; + A[S] = 7; + K.split('').forEach(function(k){ B[k] = k; }); + return $assign({}, A)[S] != 7 || Object.keys($assign({}, B)).join('') != K; +}) ? function assign(target, source){ // eslint-disable-line no-unused-vars + var T = toObject(target) + , aLen = arguments.length + , index = 1 + , getSymbols = gOPS.f + , isEnum = pIE.f; + while(aLen > index){ + var S = IObject(arguments[index++]) + , keys = getSymbols ? getKeys(S).concat(getSymbols(S)) : getKeys(S) + , length = keys.length + , j = 0 + , key; + while(length > j)if(isEnum.call(S, key = keys[j++]))T[key] = S[key]; + } return T; +} : $assign; +},{"./_fails":98,"./_iobject":105,"./_object-gops":124,"./_object-keys":127,"./_object-pie":128,"./_to-object":143}],118:[function(_dereq_,module,exports){ +// 19.1.2.2 / 15.2.3.5 Object.create(O [, Properties]) +var anObject = _dereq_('./_an-object') + , dPs = _dereq_('./_object-dps') + , enumBugKeys = _dereq_('./_enum-bug-keys') + , IE_PROTO = _dereq_('./_shared-key')('IE_PROTO') + , Empty = function(){ /* empty */ } + , PROTOTYPE = 'prototype'; + +// Create object with fake `null` prototype: use iframe Object with cleared prototype +var createDict = function(){ + // Thrash, waste and sodomy: IE GC bug + var iframe = _dereq_('./_dom-create')('iframe') + , i = enumBugKeys.length + , lt = '<' + , gt = '>' + , iframeDocument; + iframe.style.display = 'none'; + _dereq_('./_html').appendChild(iframe); + iframe.src = 'javascript:'; // eslint-disable-line no-script-url + // createDict = iframe.contentWindow.Object; + // html.removeChild(iframe); + iframeDocument = iframe.contentWindow.document; + iframeDocument.open(); + iframeDocument.write(lt + 'script' + gt + 'document.F=Object' + lt + '/script' + gt); + iframeDocument.close(); + createDict = iframeDocument.F; + while(i--)delete createDict[PROTOTYPE][enumBugKeys[i]]; + return createDict(); +}; + +module.exports = Object.create || function create(O, Properties){ + var result; + if(O !== null){ + Empty[PROTOTYPE] = anObject(O); + result = new Empty; + Empty[PROTOTYPE] = null; + // add "__proto__" for Object.getPrototypeOf polyfill + result[IE_PROTO] = O; + } else result = createDict(); + return Properties === undefined ? result : dPs(result, Properties); +}; + +},{"./_an-object":78,"./_dom-create":94,"./_enum-bug-keys":95,"./_html":103,"./_object-dps":120,"./_shared-key":136}],119:[function(_dereq_,module,exports){ +var anObject = _dereq_('./_an-object') + , IE8_DOM_DEFINE = _dereq_('./_ie8-dom-define') + , toPrimitive = _dereq_('./_to-primitive') + , dP = Object.defineProperty; + +exports.f = _dereq_('./_descriptors') ? Object.defineProperty : function defineProperty(O, P, Attributes){ + anObject(O); + P = toPrimitive(P, true); + anObject(Attributes); + if(IE8_DOM_DEFINE)try { + return dP(O, P, Attributes); + } catch(e){ /* empty */ } + if('get' in Attributes || 'set' in Attributes)throw TypeError('Accessors not supported!'); + if('value' in Attributes)O[P] = Attributes.value; + return O; +}; +},{"./_an-object":78,"./_descriptors":93,"./_ie8-dom-define":104,"./_to-primitive":144}],120:[function(_dereq_,module,exports){ +var dP = _dereq_('./_object-dp') + , anObject = _dereq_('./_an-object') + , getKeys = _dereq_('./_object-keys'); + +module.exports = _dereq_('./_descriptors') ? Object.defineProperties : function defineProperties(O, Properties){ + anObject(O); + var keys = getKeys(Properties) + , length = keys.length + , i = 0 + , P; + while(length > i)dP.f(O, P = keys[i++], Properties[P]); + return O; +}; +},{"./_an-object":78,"./_descriptors":93,"./_object-dp":119,"./_object-keys":127}],121:[function(_dereq_,module,exports){ +var pIE = _dereq_('./_object-pie') + , createDesc = _dereq_('./_property-desc') + , toIObject = _dereq_('./_to-iobject') + , toPrimitive = _dereq_('./_to-primitive') + , has = _dereq_('./_has') + , IE8_DOM_DEFINE = _dereq_('./_ie8-dom-define') + , gOPD = Object.getOwnPropertyDescriptor; + +exports.f = _dereq_('./_descriptors') ? gOPD : function getOwnPropertyDescriptor(O, P){ + O = toIObject(O); + P = toPrimitive(P, true); + if(IE8_DOM_DEFINE)try { + return gOPD(O, P); + } catch(e){ /* empty */ } + if(has(O, P))return createDesc(!pIE.f.call(O, P), O[P]); +}; +},{"./_descriptors":93,"./_has":101,"./_ie8-dom-define":104,"./_object-pie":128,"./_property-desc":130,"./_to-iobject":141,"./_to-primitive":144}],122:[function(_dereq_,module,exports){ +// fallback for IE11 buggy Object.getOwnPropertyNames with iframe and window +var toIObject = _dereq_('./_to-iobject') + , gOPN = _dereq_('./_object-gopn').f + , toString = {}.toString; + +var windowNames = typeof window == 'object' && window && Object.getOwnPropertyNames + ? Object.getOwnPropertyNames(window) : []; + +var getWindowNames = function(it){ + try { + return gOPN(it); + } catch(e){ + return windowNames.slice(); + } +}; + +module.exports.f = function getOwnPropertyNames(it){ + return windowNames && toString.call(it) == '[object Window]' ? getWindowNames(it) : gOPN(toIObject(it)); +}; + +},{"./_object-gopn":123,"./_to-iobject":141}],123:[function(_dereq_,module,exports){ +// 19.1.2.7 / 15.2.3.4 Object.getOwnPropertyNames(O) +var $keys = _dereq_('./_object-keys-internal') + , hiddenKeys = _dereq_('./_enum-bug-keys').concat('length', 'prototype'); + +exports.f = Object.getOwnPropertyNames || function getOwnPropertyNames(O){ + return $keys(O, hiddenKeys); +}; +},{"./_enum-bug-keys":95,"./_object-keys-internal":126}],124:[function(_dereq_,module,exports){ +exports.f = Object.getOwnPropertySymbols; +},{}],125:[function(_dereq_,module,exports){ +// 19.1.2.9 / 15.2.3.2 Object.getPrototypeOf(O) +var has = _dereq_('./_has') + , toObject = _dereq_('./_to-object') + , IE_PROTO = _dereq_('./_shared-key')('IE_PROTO') + , ObjectProto = Object.prototype; + +module.exports = Object.getPrototypeOf || function(O){ + O = toObject(O); + if(has(O, IE_PROTO))return O[IE_PROTO]; + if(typeof O.constructor == 'function' && O instanceof O.constructor){ + return O.constructor.prototype; + } return O instanceof Object ? ObjectProto : null; +}; +},{"./_has":101,"./_shared-key":136,"./_to-object":143}],126:[function(_dereq_,module,exports){ +var has = _dereq_('./_has') + , toIObject = _dereq_('./_to-iobject') + , arrayIndexOf = _dereq_('./_array-includes')(false) + , IE_PROTO = _dereq_('./_shared-key')('IE_PROTO'); + +module.exports = function(object, names){ + var O = toIObject(object) + , i = 0 + , result = [] + , key; + for(key in O)if(key != IE_PROTO)has(O, key) && result.push(key); + // Don't enum bug & hidden keys + while(names.length > i)if(has(O, key = names[i++])){ + ~arrayIndexOf(result, key) || result.push(key); + } + return result; +}; +},{"./_array-includes":80,"./_has":101,"./_shared-key":136,"./_to-iobject":141}],127:[function(_dereq_,module,exports){ +// 19.1.2.14 / 15.2.3.14 Object.keys(O) +var $keys = _dereq_('./_object-keys-internal') + , enumBugKeys = _dereq_('./_enum-bug-keys'); + +module.exports = Object.keys || function keys(O){ + return $keys(O, enumBugKeys); +}; +},{"./_enum-bug-keys":95,"./_object-keys-internal":126}],128:[function(_dereq_,module,exports){ +exports.f = {}.propertyIsEnumerable; +},{}],129:[function(_dereq_,module,exports){ +// most Object methods by ES6 should accept primitives +var $export = _dereq_('./_export') + , core = _dereq_('./_core') + , fails = _dereq_('./_fails'); +module.exports = function(KEY, exec){ + var fn = (core.Object || {})[KEY] || Object[KEY] + , exp = {}; + exp[KEY] = exec(fn); + $export($export.S + $export.F * fails(function(){ fn(1); }), 'Object', exp); +}; +},{"./_core":90,"./_export":97,"./_fails":98}],130:[function(_dereq_,module,exports){ +module.exports = function(bitmap, value){ + return { + enumerable : !(bitmap & 1), + configurable: !(bitmap & 2), + writable : !(bitmap & 4), + value : value + }; +}; +},{}],131:[function(_dereq_,module,exports){ +var hide = _dereq_('./_hide'); +module.exports = function(target, src, safe){ + for(var key in src){ + if(safe && target[key])target[key] = src[key]; + else hide(target, key, src[key]); + } return target; +}; +},{"./_hide":102}],132:[function(_dereq_,module,exports){ +module.exports = _dereq_('./_hide'); +},{"./_hide":102}],133:[function(_dereq_,module,exports){ +// Works with __proto__ only. Old v8 can't work with null proto objects. +/* eslint-disable no-proto */ +var isObject = _dereq_('./_is-object') + , anObject = _dereq_('./_an-object'); +var check = function(O, proto){ + anObject(O); + if(!isObject(proto) && proto !== null)throw TypeError(proto + ": can't set as prototype!"); +}; +module.exports = { + set: Object.setPrototypeOf || ('__proto__' in {} ? // eslint-disable-line + function(test, buggy, set){ + try { + set = _dereq_('./_ctx')(Function.call, _dereq_('./_object-gopd').f(Object.prototype, '__proto__').set, 2); + set(test, []); + buggy = !(test instanceof Array); + } catch(e){ buggy = true; } + return function setPrototypeOf(O, proto){ + check(O, proto); + if(buggy)O.__proto__ = proto; + else set(O, proto); + return O; + }; + }({}, false) : undefined), + check: check +}; +},{"./_an-object":78,"./_ctx":91,"./_is-object":108,"./_object-gopd":121}],134:[function(_dereq_,module,exports){ +'use strict'; +var global = _dereq_('./_global') + , core = _dereq_('./_core') + , dP = _dereq_('./_object-dp') + , DESCRIPTORS = _dereq_('./_descriptors') + , SPECIES = _dereq_('./_wks')('species'); + +module.exports = function(KEY){ + var C = typeof core[KEY] == 'function' ? core[KEY] : global[KEY]; + if(DESCRIPTORS && C && !C[SPECIES])dP.f(C, SPECIES, { + configurable: true, + get: function(){ return this; } + }); +}; +},{"./_core":90,"./_descriptors":93,"./_global":100,"./_object-dp":119,"./_wks":148}],135:[function(_dereq_,module,exports){ +var def = _dereq_('./_object-dp').f + , has = _dereq_('./_has') + , TAG = _dereq_('./_wks')('toStringTag'); + +module.exports = function(it, tag, stat){ + if(it && !has(it = stat ? it : it.prototype, TAG))def(it, TAG, {configurable: true, value: tag}); +}; +},{"./_has":101,"./_object-dp":119,"./_wks":148}],136:[function(_dereq_,module,exports){ +var shared = _dereq_('./_shared')('keys') + , uid = _dereq_('./_uid'); +module.exports = function(key){ + return shared[key] || (shared[key] = uid(key)); +}; +},{"./_shared":137,"./_uid":145}],137:[function(_dereq_,module,exports){ +var global = _dereq_('./_global') + , SHARED = '__core-js_shared__' + , store = global[SHARED] || (global[SHARED] = {}); +module.exports = function(key){ + return store[key] || (store[key] = {}); +}; +},{"./_global":100}],138:[function(_dereq_,module,exports){ +var toInteger = _dereq_('./_to-integer') + , defined = _dereq_('./_defined'); +// true -> String#at +// false -> String#codePointAt +module.exports = function(TO_STRING){ + return function(that, pos){ + var s = String(defined(that)) + , i = toInteger(pos) + , l = s.length + , a, b; + if(i < 0 || i >= l)return TO_STRING ? '' : undefined; + a = s.charCodeAt(i); + return a < 0xd800 || a > 0xdbff || i + 1 === l || (b = s.charCodeAt(i + 1)) < 0xdc00 || b > 0xdfff + ? TO_STRING ? s.charAt(i) : a + : TO_STRING ? s.slice(i, i + 2) : (a - 0xd800 << 10) + (b - 0xdc00) + 0x10000; + }; +}; +},{"./_defined":92,"./_to-integer":140}],139:[function(_dereq_,module,exports){ +var toInteger = _dereq_('./_to-integer') + , max = Math.max + , min = Math.min; +module.exports = function(index, length){ + index = toInteger(index); + return index < 0 ? max(index + length, 0) : min(index, length); +}; +},{"./_to-integer":140}],140:[function(_dereq_,module,exports){ +// 7.1.4 ToInteger +var ceil = Math.ceil + , floor = Math.floor; +module.exports = function(it){ + return isNaN(it = +it) ? 0 : (it > 0 ? floor : ceil)(it); +}; +},{}],141:[function(_dereq_,module,exports){ +// to indexed object, toObject with fallback for non-array-like ES3 strings +var IObject = _dereq_('./_iobject') + , defined = _dereq_('./_defined'); +module.exports = function(it){ + return IObject(defined(it)); +}; +},{"./_defined":92,"./_iobject":105}],142:[function(_dereq_,module,exports){ +// 7.1.15 ToLength +var toInteger = _dereq_('./_to-integer') + , min = Math.min; +module.exports = function(it){ + return it > 0 ? min(toInteger(it), 0x1fffffffffffff) : 0; // pow(2, 53) - 1 == 9007199254740991 +}; +},{"./_to-integer":140}],143:[function(_dereq_,module,exports){ +// 7.1.13 ToObject(argument) +var defined = _dereq_('./_defined'); +module.exports = function(it){ + return Object(defined(it)); +}; +},{"./_defined":92}],144:[function(_dereq_,module,exports){ +// 7.1.1 ToPrimitive(input [, PreferredType]) +var isObject = _dereq_('./_is-object'); +// instead of the ES6 spec version, we didn't implement @@toPrimitive case +// and the second argument - flag - preferred type is a string +module.exports = function(it, S){ + if(!isObject(it))return it; + var fn, val; + if(S && typeof (fn = it.toString) == 'function' && !isObject(val = fn.call(it)))return val; + if(typeof (fn = it.valueOf) == 'function' && !isObject(val = fn.call(it)))return val; + if(!S && typeof (fn = it.toString) == 'function' && !isObject(val = fn.call(it)))return val; + throw TypeError("Can't convert object to primitive value"); +}; +},{"./_is-object":108}],145:[function(_dereq_,module,exports){ +var id = 0 + , px = Math.random(); +module.exports = function(key){ + return 'Symbol('.concat(key === undefined ? '' : key, ')_', (++id + px).toString(36)); +}; +},{}],146:[function(_dereq_,module,exports){ +var global = _dereq_('./_global') + , core = _dereq_('./_core') + , LIBRARY = _dereq_('./_library') + , wksExt = _dereq_('./_wks-ext') + , defineProperty = _dereq_('./_object-dp').f; +module.exports = function(name){ + var $Symbol = core.Symbol || (core.Symbol = LIBRARY ? {} : global.Symbol || {}); + if(name.charAt(0) != '_' && !(name in $Symbol))defineProperty($Symbol, name, {value: wksExt.f(name)}); +}; +},{"./_core":90,"./_global":100,"./_library":115,"./_object-dp":119,"./_wks-ext":147}],147:[function(_dereq_,module,exports){ +exports.f = _dereq_('./_wks'); +},{"./_wks":148}],148:[function(_dereq_,module,exports){ +var store = _dereq_('./_shared')('wks') + , uid = _dereq_('./_uid') + , Symbol = _dereq_('./_global').Symbol + , USE_SYMBOL = typeof Symbol == 'function'; + +var $exports = module.exports = function(name){ + return store[name] || (store[name] = + USE_SYMBOL && Symbol[name] || (USE_SYMBOL ? Symbol : uid)('Symbol.' + name)); +}; + +$exports.store = store; +},{"./_global":100,"./_shared":137,"./_uid":145}],149:[function(_dereq_,module,exports){ +var classof = _dereq_('./_classof') + , ITERATOR = _dereq_('./_wks')('iterator') + , Iterators = _dereq_('./_iterators'); +module.exports = _dereq_('./_core').getIteratorMethod = function(it){ + if(it != undefined)return it[ITERATOR] + || it['@@iterator'] + || Iterators[classof(it)]; +}; +},{"./_classof":84,"./_core":90,"./_iterators":113,"./_wks":148}],150:[function(_dereq_,module,exports){ +var anObject = _dereq_('./_an-object') + , get = _dereq_('./core.get-iterator-method'); +module.exports = _dereq_('./_core').getIterator = function(it){ + var iterFn = get(it); + if(typeof iterFn != 'function')throw TypeError(it + ' is not iterable!'); + return anObject(iterFn.call(it)); +}; +},{"./_an-object":78,"./_core":90,"./core.get-iterator-method":149}],151:[function(_dereq_,module,exports){ +'use strict'; +var addToUnscopables = _dereq_('./_add-to-unscopables') + , step = _dereq_('./_iter-step') + , Iterators = _dereq_('./_iterators') + , toIObject = _dereq_('./_to-iobject'); + +// 22.1.3.4 Array.prototype.entries() +// 22.1.3.13 Array.prototype.keys() +// 22.1.3.29 Array.prototype.values() +// 22.1.3.30 Array.prototype[@@iterator]() +module.exports = _dereq_('./_iter-define')(Array, 'Array', function(iterated, kind){ + this._t = toIObject(iterated); // target + this._i = 0; // next index + this._k = kind; // kind +// 22.1.5.2.1 %ArrayIteratorPrototype%.next() +}, function(){ + var O = this._t + , kind = this._k + , index = this._i++; + if(!O || index >= O.length){ + this._t = undefined; + return step(1); + } + if(kind == 'keys' )return step(0, index); + if(kind == 'values')return step(0, O[index]); + return step(0, [index, O[index]]); +}, 'values'); + +// argumentsList[@@iterator] is %ArrayProto_values% (9.4.4.6, 9.4.4.7) +Iterators.Arguments = Iterators.Array; + +addToUnscopables('keys'); +addToUnscopables('values'); +addToUnscopables('entries'); +},{"./_add-to-unscopables":76,"./_iter-define":111,"./_iter-step":112,"./_iterators":113,"./_to-iobject":141}],152:[function(_dereq_,module,exports){ +'use strict'; +var strong = _dereq_('./_collection-strong'); + +// 23.1 Map Objects +module.exports = _dereq_('./_collection')('Map', function(get){ + return function Map(){ return get(this, arguments.length > 0 ? arguments[0] : undefined); }; +}, { + // 23.1.3.6 Map.prototype.get(key) + get: function get(key){ + var entry = strong.getEntry(this, key); + return entry && entry.v; + }, + // 23.1.3.9 Map.prototype.set(key, value) + set: function set(key, value){ + return strong.def(this, key === 0 ? 0 : key, value); + } +}, strong, true); +},{"./_collection":89,"./_collection-strong":86}],153:[function(_dereq_,module,exports){ +var $export = _dereq_('./_export') +// 19.1.2.2 / 15.2.3.5 Object.create(O [, Properties]) +$export($export.S, 'Object', {create: _dereq_('./_object-create')}); +},{"./_export":97,"./_object-create":118}],154:[function(_dereq_,module,exports){ +var $export = _dereq_('./_export'); +// 19.1.2.4 / 15.2.3.6 Object.defineProperty(O, P, Attributes) +$export($export.S + $export.F * !_dereq_('./_descriptors'), 'Object', {defineProperty: _dereq_('./_object-dp').f}); +},{"./_descriptors":93,"./_export":97,"./_object-dp":119}],155:[function(_dereq_,module,exports){ +// 19.1.2.5 Object.freeze(O) +var isObject = _dereq_('./_is-object') + , meta = _dereq_('./_meta').onFreeze; + +_dereq_('./_object-sap')('freeze', function($freeze){ + return function freeze(it){ + return $freeze && isObject(it) ? $freeze(meta(it)) : it; + }; +}); +},{"./_is-object":108,"./_meta":116,"./_object-sap":129}],156:[function(_dereq_,module,exports){ +// 19.1.2.6 Object.getOwnPropertyDescriptor(O, P) +var toIObject = _dereq_('./_to-iobject') + , $getOwnPropertyDescriptor = _dereq_('./_object-gopd').f; + +_dereq_('./_object-sap')('getOwnPropertyDescriptor', function(){ + return function getOwnPropertyDescriptor(it, key){ + return $getOwnPropertyDescriptor(toIObject(it), key); + }; +}); +},{"./_object-gopd":121,"./_object-sap":129,"./_to-iobject":141}],157:[function(_dereq_,module,exports){ +// 19.1.2.9 Object.getPrototypeOf(O) +var toObject = _dereq_('./_to-object') + , $getPrototypeOf = _dereq_('./_object-gpo'); + +_dereq_('./_object-sap')('getPrototypeOf', function(){ + return function getPrototypeOf(it){ + return $getPrototypeOf(toObject(it)); + }; +}); +},{"./_object-gpo":125,"./_object-sap":129,"./_to-object":143}],158:[function(_dereq_,module,exports){ +// 19.1.2.14 Object.keys(O) +var toObject = _dereq_('./_to-object') + , $keys = _dereq_('./_object-keys'); + +_dereq_('./_object-sap')('keys', function(){ + return function keys(it){ + return $keys(toObject(it)); + }; +}); +},{"./_object-keys":127,"./_object-sap":129,"./_to-object":143}],159:[function(_dereq_,module,exports){ +// 19.1.3.19 Object.setPrototypeOf(O, proto) +var $export = _dereq_('./_export'); +$export($export.S, 'Object', {setPrototypeOf: _dereq_('./_set-proto').set}); +},{"./_export":97,"./_set-proto":133}],160:[function(_dereq_,module,exports){ + +},{}],161:[function(_dereq_,module,exports){ +'use strict'; +var $at = _dereq_('./_string-at')(true); + +// 21.1.3.27 String.prototype[@@iterator]() +_dereq_('./_iter-define')(String, 'String', function(iterated){ + this._t = String(iterated); // target + this._i = 0; // next index +// 21.1.5.2.1 %StringIteratorPrototype%.next() +}, function(){ + var O = this._t + , index = this._i + , point; + if(index >= O.length)return {value: undefined, done: true}; + point = $at(O, index); + this._i += point.length; + return {value: point, done: false}; +}); +},{"./_iter-define":111,"./_string-at":138}],162:[function(_dereq_,module,exports){ +'use strict'; +// ECMAScript 6 symbols shim +var global = _dereq_('./_global') + , has = _dereq_('./_has') + , DESCRIPTORS = _dereq_('./_descriptors') + , $export = _dereq_('./_export') + , redefine = _dereq_('./_redefine') + , META = _dereq_('./_meta').KEY + , $fails = _dereq_('./_fails') + , shared = _dereq_('./_shared') + , setToStringTag = _dereq_('./_set-to-string-tag') + , uid = _dereq_('./_uid') + , wks = _dereq_('./_wks') + , wksExt = _dereq_('./_wks-ext') + , wksDefine = _dereq_('./_wks-define') + , keyOf = _dereq_('./_keyof') + , enumKeys = _dereq_('./_enum-keys') + , isArray = _dereq_('./_is-array') + , anObject = _dereq_('./_an-object') + , toIObject = _dereq_('./_to-iobject') + , toPrimitive = _dereq_('./_to-primitive') + , createDesc = _dereq_('./_property-desc') + , _create = _dereq_('./_object-create') + , gOPNExt = _dereq_('./_object-gopn-ext') + , $GOPD = _dereq_('./_object-gopd') + , $DP = _dereq_('./_object-dp') + , $keys = _dereq_('./_object-keys') + , gOPD = $GOPD.f + , dP = $DP.f + , gOPN = gOPNExt.f + , $Symbol = global.Symbol + , $JSON = global.JSON + , _stringify = $JSON && $JSON.stringify + , PROTOTYPE = 'prototype' + , HIDDEN = wks('_hidden') + , TO_PRIMITIVE = wks('toPrimitive') + , isEnum = {}.propertyIsEnumerable + , SymbolRegistry = shared('symbol-registry') + , AllSymbols = shared('symbols') + , OPSymbols = shared('op-symbols') + , ObjectProto = Object[PROTOTYPE] + , USE_NATIVE = typeof $Symbol == 'function' + , QObject = global.QObject; +// Don't use setters in Qt Script, https://github.com/zloirock/core-js/issues/173 +var setter = !QObject || !QObject[PROTOTYPE] || !QObject[PROTOTYPE].findChild; + +// fallback for old Android, https://code.google.com/p/v8/issues/detail?id=687 +var setSymbolDesc = DESCRIPTORS && $fails(function(){ + return _create(dP({}, 'a', { + get: function(){ return dP(this, 'a', {value: 7}).a; } + })).a != 7; +}) ? function(it, key, D){ + var protoDesc = gOPD(ObjectProto, key); + if(protoDesc)delete ObjectProto[key]; + dP(it, key, D); + if(protoDesc && it !== ObjectProto)dP(ObjectProto, key, protoDesc); +} : dP; + +var wrap = function(tag){ + var sym = AllSymbols[tag] = _create($Symbol[PROTOTYPE]); + sym._k = tag; + return sym; +}; + +var isSymbol = USE_NATIVE && typeof $Symbol.iterator == 'symbol' ? function(it){ + return typeof it == 'symbol'; +} : function(it){ + return it instanceof $Symbol; +}; + +var $defineProperty = function defineProperty(it, key, D){ + if(it === ObjectProto)$defineProperty(OPSymbols, key, D); + anObject(it); + key = toPrimitive(key, true); + anObject(D); + if(has(AllSymbols, key)){ + if(!D.enumerable){ + if(!has(it, HIDDEN))dP(it, HIDDEN, createDesc(1, {})); + it[HIDDEN][key] = true; + } else { + if(has(it, HIDDEN) && it[HIDDEN][key])it[HIDDEN][key] = false; + D = _create(D, {enumerable: createDesc(0, false)}); + } return setSymbolDesc(it, key, D); + } return dP(it, key, D); +}; +var $defineProperties = function defineProperties(it, P){ + anObject(it); + var keys = enumKeys(P = toIObject(P)) + , i = 0 + , l = keys.length + , key; + while(l > i)$defineProperty(it, key = keys[i++], P[key]); + return it; +}; +var $create = function create(it, P){ + return P === undefined ? _create(it) : $defineProperties(_create(it), P); +}; +var $propertyIsEnumerable = function propertyIsEnumerable(key){ + var E = isEnum.call(this, key = toPrimitive(key, true)); + if(this === ObjectProto && has(AllSymbols, key) && !has(OPSymbols, key))return false; + return E || !has(this, key) || !has(AllSymbols, key) || has(this, HIDDEN) && this[HIDDEN][key] ? E : true; +}; +var $getOwnPropertyDescriptor = function getOwnPropertyDescriptor(it, key){ + it = toIObject(it); + key = toPrimitive(key, true); + if(it === ObjectProto && has(AllSymbols, key) && !has(OPSymbols, key))return; + var D = gOPD(it, key); + if(D && has(AllSymbols, key) && !(has(it, HIDDEN) && it[HIDDEN][key]))D.enumerable = true; + return D; +}; +var $getOwnPropertyNames = function getOwnPropertyNames(it){ + var names = gOPN(toIObject(it)) + , result = [] + , i = 0 + , key; + while(names.length > i){ + if(!has(AllSymbols, key = names[i++]) && key != HIDDEN && key != META)result.push(key); + } return result; +}; +var $getOwnPropertySymbols = function getOwnPropertySymbols(it){ + var IS_OP = it === ObjectProto + , names = gOPN(IS_OP ? OPSymbols : toIObject(it)) + , result = [] + , i = 0 + , key; + while(names.length > i){ + if(has(AllSymbols, key = names[i++]) && (IS_OP ? has(ObjectProto, key) : true))result.push(AllSymbols[key]); + } return result; +}; + +// 19.4.1.1 Symbol([description]) +if(!USE_NATIVE){ + $Symbol = function Symbol(){ + if(this instanceof $Symbol)throw TypeError('Symbol is not a constructor!'); + var tag = uid(arguments.length > 0 ? arguments[0] : undefined); + var $set = function(value){ + if(this === ObjectProto)$set.call(OPSymbols, value); + if(has(this, HIDDEN) && has(this[HIDDEN], tag))this[HIDDEN][tag] = false; + setSymbolDesc(this, tag, createDesc(1, value)); + }; + if(DESCRIPTORS && setter)setSymbolDesc(ObjectProto, tag, {configurable: true, set: $set}); + return wrap(tag); + }; + redefine($Symbol[PROTOTYPE], 'toString', function toString(){ + return this._k; + }); + + $GOPD.f = $getOwnPropertyDescriptor; + $DP.f = $defineProperty; + _dereq_('./_object-gopn').f = gOPNExt.f = $getOwnPropertyNames; + _dereq_('./_object-pie').f = $propertyIsEnumerable; + _dereq_('./_object-gops').f = $getOwnPropertySymbols; + + if(DESCRIPTORS && !_dereq_('./_library')){ + redefine(ObjectProto, 'propertyIsEnumerable', $propertyIsEnumerable, true); + } + + wksExt.f = function(name){ + return wrap(wks(name)); + } +} + +$export($export.G + $export.W + $export.F * !USE_NATIVE, {Symbol: $Symbol}); + +for(var symbols = ( + // 19.4.2.2, 19.4.2.3, 19.4.2.4, 19.4.2.6, 19.4.2.8, 19.4.2.9, 19.4.2.10, 19.4.2.11, 19.4.2.12, 19.4.2.13, 19.4.2.14 + 'hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables' +).split(','), i = 0; symbols.length > i; )wks(symbols[i++]); + +for(var symbols = $keys(wks.store), i = 0; symbols.length > i; )wksDefine(symbols[i++]); + +$export($export.S + $export.F * !USE_NATIVE, 'Symbol', { + // 19.4.2.1 Symbol.for(key) + 'for': function(key){ + return has(SymbolRegistry, key += '') + ? SymbolRegistry[key] + : SymbolRegistry[key] = $Symbol(key); + }, + // 19.4.2.5 Symbol.keyFor(sym) + keyFor: function keyFor(key){ + if(isSymbol(key))return keyOf(SymbolRegistry, key); + throw TypeError(key + ' is not a symbol!'); + }, + useSetter: function(){ setter = true; }, + useSimple: function(){ setter = false; } +}); + +$export($export.S + $export.F * !USE_NATIVE, 'Object', { + // 19.1.2.2 Object.create(O [, Properties]) + create: $create, + // 19.1.2.4 Object.defineProperty(O, P, Attributes) + defineProperty: $defineProperty, + // 19.1.2.3 Object.defineProperties(O, Properties) + defineProperties: $defineProperties, + // 19.1.2.6 Object.getOwnPropertyDescriptor(O, P) + getOwnPropertyDescriptor: $getOwnPropertyDescriptor, + // 19.1.2.7 Object.getOwnPropertyNames(O) + getOwnPropertyNames: $getOwnPropertyNames, + // 19.1.2.8 Object.getOwnPropertySymbols(O) + getOwnPropertySymbols: $getOwnPropertySymbols +}); + +// 24.3.2 JSON.stringify(value [, replacer [, space]]) +$JSON && $export($export.S + $export.F * (!USE_NATIVE || $fails(function(){ + var S = $Symbol(); + // MS Edge converts symbol values to JSON as {} + // WebKit converts symbol values to JSON as null + // V8 throws on boxed symbols + return _stringify([S]) != '[null]' || _stringify({a: S}) != '{}' || _stringify(Object(S)) != '{}'; +})), 'JSON', { + stringify: function stringify(it){ + if(it === undefined || isSymbol(it))return; // IE8 returns string on undefined + var args = [it] + , i = 1 + , replacer, $replacer; + while(arguments.length > i)args.push(arguments[i++]); + replacer = args[1]; + if(typeof replacer == 'function')$replacer = replacer; + if($replacer || !isArray(replacer))replacer = function(key, value){ + if($replacer)value = $replacer.call(this, key, value); + if(!isSymbol(value))return value; + }; + args[1] = replacer; + return _stringify.apply($JSON, args); + } +}); + +// 19.4.3.4 Symbol.prototype[@@toPrimitive](hint) +$Symbol[PROTOTYPE][TO_PRIMITIVE] || _dereq_('./_hide')($Symbol[PROTOTYPE], TO_PRIMITIVE, $Symbol[PROTOTYPE].valueOf); +// 19.4.3.5 Symbol.prototype[@@toStringTag] +setToStringTag($Symbol, 'Symbol'); +// 20.2.1.9 Math[@@toStringTag] +setToStringTag(Math, 'Math', true); +// 24.3.3 JSON[@@toStringTag] +setToStringTag(global.JSON, 'JSON', true); +},{"./_an-object":78,"./_descriptors":93,"./_enum-keys":96,"./_export":97,"./_fails":98,"./_global":100,"./_has":101,"./_hide":102,"./_is-array":107,"./_keyof":114,"./_library":115,"./_meta":116,"./_object-create":118,"./_object-dp":119,"./_object-gopd":121,"./_object-gopn":123,"./_object-gopn-ext":122,"./_object-gops":124,"./_object-keys":127,"./_object-pie":128,"./_property-desc":130,"./_redefine":132,"./_set-to-string-tag":135,"./_shared":137,"./_to-iobject":141,"./_to-primitive":144,"./_uid":145,"./_wks":148,"./_wks-define":146,"./_wks-ext":147}],163:[function(_dereq_,module,exports){ +'use strict'; +var each = _dereq_('./_array-methods')(0) + , redefine = _dereq_('./_redefine') + , meta = _dereq_('./_meta') + , assign = _dereq_('./_object-assign') + , weak = _dereq_('./_collection-weak') + , isObject = _dereq_('./_is-object') + , getWeak = meta.getWeak + , isExtensible = Object.isExtensible + , uncaughtFrozenStore = weak.ufstore + , tmp = {} + , InternalMap; + +var wrapper = function(get){ + return function WeakMap(){ + return get(this, arguments.length > 0 ? arguments[0] : undefined); + }; +}; + +var methods = { + // 23.3.3.3 WeakMap.prototype.get(key) + get: function get(key){ + if(isObject(key)){ + var data = getWeak(key); + if(data === true)return uncaughtFrozenStore(this).get(key); + return data ? data[this._i] : undefined; + } + }, + // 23.3.3.5 WeakMap.prototype.set(key, value) + set: function set(key, value){ + return weak.def(this, key, value); + } +}; + +// 23.3 WeakMap Objects +var $WeakMap = module.exports = _dereq_('./_collection')('WeakMap', wrapper, methods, weak, true, true); + +// IE11 WeakMap frozen keys fix +if(new $WeakMap().set((Object.freeze || Object)(tmp), 7).get(tmp) != 7){ + InternalMap = weak.getConstructor(wrapper); + assign(InternalMap.prototype, methods); + meta.NEED = true; + each(['delete', 'has', 'get', 'set'], function(key){ + var proto = $WeakMap.prototype + , method = proto[key]; + redefine(proto, key, function(a, b){ + // store frozen objects on internal weakmap shim + if(isObject(a) && !isExtensible(a)){ + if(!this._f)this._f = new InternalMap; + var result = this._f[key](a, b); + return key == 'set' ? this : result; + // store all the rest on native weakmap + } return method.call(this, a, b); + }); + }); +} +},{"./_array-methods":81,"./_collection":89,"./_collection-weak":88,"./_is-object":108,"./_meta":116,"./_object-assign":117,"./_redefine":132}],164:[function(_dereq_,module,exports){ +// https://github.com/DavidBruant/Map-Set.prototype.toJSON +var $export = _dereq_('./_export'); + +$export($export.P + $export.R, 'Map', {toJSON: _dereq_('./_collection-to-json')('Map')}); +},{"./_collection-to-json":87,"./_export":97}],165:[function(_dereq_,module,exports){ +_dereq_('./_wks-define')('asyncIterator'); +},{"./_wks-define":146}],166:[function(_dereq_,module,exports){ +_dereq_('./_wks-define')('observable'); +},{"./_wks-define":146}],167:[function(_dereq_,module,exports){ +_dereq_('./es6.array.iterator'); +var global = _dereq_('./_global') + , hide = _dereq_('./_hide') + , Iterators = _dereq_('./_iterators') + , TO_STRING_TAG = _dereq_('./_wks')('toStringTag'); + +for(var collections = ['NodeList', 'DOMTokenList', 'MediaList', 'StyleSheetList', 'CSSRuleList'], i = 0; i < 5; i++){ + var NAME = collections[i] + , Collection = global[NAME] + , proto = Collection && Collection.prototype; + if(proto && !proto[TO_STRING_TAG])hide(proto, TO_STRING_TAG, NAME); + Iterators[NAME] = Iterators.Array; +} +},{"./_global":100,"./_hide":102,"./_iterators":113,"./_wks":148,"./es6.array.iterator":151}],168:[function(_dereq_,module,exports){ +arguments[4][160][0].apply(exports,arguments) +},{"dup":160}],169:[function(_dereq_,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function(n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; +}; + +EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } + throw TypeError('Uncaught, unspecified "error" event.'); + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) + return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + handler.apply(this, args); + } + } else if (isObject(handler)) { + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; +}; + +EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + var m; + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; +}; + +// emits a 'removeListener' event iff the listener was removed +EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; +}; + +EventEmitter.listenerCount = function(emitter, type) { + var ret; + if (!emitter._events || !emitter._events[type]) + ret = 0; + else if (isFunction(emitter._events[type])) + ret = 1; + else + ret = emitter._events[type].length; + return ret; +}; + +function isFunction(arg) { + return typeof arg === 'function'; +} + +function isNumber(arg) { + return typeof arg === 'number'; +} + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} + +function isUndefined(arg) { + return arg === void 0; +} + +},{}]},{},[10])(10) +}); \ No newline at end of file diff --git a/dist/parse.min.js b/dist/parse.min.js new file mode 100644 index 000000000..1e917b729 --- /dev/null +++ b/dist/parse.min.js @@ -0,0 +1,18 @@ +/** + * Parse JavaScript SDK v1.9.2 + * + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * The source tree of this library can be found at + * https://github.com/ParsePlatform/Parse-SDK-JS + * 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(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Parse=e()}}(function(){return function e(t,r,n){function o(i,s){if(!r[i]){if(!t[i]){var u="function"==typeof require&&require;if(!s&&u)return u(i,!0);if(a)return a(i,!0);var l=new Error("Cannot find module '"+i+"'");throw l.code="MODULE_NOT_FOUND",l}var c=r[i]={exports:{}};t[i][0].call(c.exports,function(e){var r=t[i][1][e];return o(r?r:e)},c,c.exports,e,t,r,n)}return r[i].exports}for(var a="function"==typeof require&&require,i=0;i>2&63),o(n<<4&48|a>>4&15),s?o(a<<2&60|i>>6&3):"=",u?o(63&i):"="].join("")}return t.join("")}}]),e}();r["default"]=h;var p={saveFile:function(e,t){if("file"!==t.format)throw new Error("saveFile can only be used with File-type sources.");var r={"X-Parse-Application-ID":c["default"].get("APPLICATION_ID"),"X-Parse-JavaScript-Key":c["default"].get("JAVASCRIPT_KEY"),"Content-Type":t.type||(t.file?t.file.type:null)},n=c["default"].get("SERVER_URL");return"/"!==n[n.length-1]&&(n+="/"),n+="files/"+e,c["default"].getRESTController().ajax("POST",n,t.file,r)},saveBase64:function(e,t){if("base64"!==t.format)throw new Error("saveBase64 can only be used with Base64-type sources.");var r={base64:t.base64};return t.type&&(r._ContentType=t.type),c["default"].getRESTController().request("POST","files/"+e,r)}};c["default"].setFileController(p)},{"./CoreManager":3,"./ParsePromise":20,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57}],15:[function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(r,"__esModule",{value:!0});var o=e("babel-runtime/helpers/typeof"),a=n(o),i=e("babel-runtime/helpers/classCallCheck"),s=n(i),u=e("babel-runtime/helpers/createClass"),l=n(u),c=e("./ParsePromise"),f=n(c),d=function(){function e(t,r){(0,s["default"])(this,e),Array.isArray(t)?(e._validate(t[0],t[1]),this._latitude=t[0],this._longitude=t[1]):"object"===("undefined"==typeof t?"undefined":(0,a["default"])(t))?(e._validate(t.latitude,t.longitude),this._latitude=t.latitude,this._longitude=t.longitude):"number"==typeof t&&"number"==typeof r?(e._validate(t,r),this._latitude=t,this._longitude=r):(this._latitude=0,this._longitude=0)}return(0,l["default"])(e,[{key:"toJSON",value:function(){return e._validate(this._latitude,this._longitude),{__type:"GeoPoint",latitude:this._latitude,longitude:this._longitude}}},{key:"equals",value:function(t){return t instanceof e&&this.latitude===t.latitude&&this.longitude===t.longitude}},{key:"radiansTo",value:function(e){var t=Math.PI/180,r=this.latitude*t,n=this.longitude*t,o=e.latitude*t,a=e.longitude*t,i=Math.sin((r-o)/2),s=Math.sin((n-a)/2),u=i*i+Math.cos(r)*Math.cos(o)*s*s;return u=Math.min(1,u),2*Math.asin(Math.sqrt(u))}},{key:"kilometersTo",value:function(e){return 6371*this.radiansTo(e)}},{key:"milesTo",value:function(e){return 3958.8*this.radiansTo(e)}},{key:"latitude",get:function(){return this._latitude},set:function(t){e._validate(t,this.longitude),this._latitude=t}},{key:"longitude",get:function(){return this._longitude},set:function(t){e._validate(this.latitude,t),this._longitude=t}}],[{key:"_validate",value:function(e,t){if(e!==e||t!==t)throw new TypeError("GeoPoint latitude and longitude must be valid numbers");if(e<-90)throw new TypeError("GeoPoint latitude out of bounds: "+e+" < -90.0.");if(e>90)throw new TypeError("GeoPoint latitude out of bounds: "+e+" > 90.0.");if(t<-180)throw new TypeError("GeoPoint longitude out of bounds: "+t+" < -180.0.");if(t>180)throw new TypeError("GeoPoint longitude out of bounds: "+t+" > 180.0.")}},{key:"current",value:function(t){var r=new f["default"];return navigator.geolocation.getCurrentPosition(function(t){r.resolve(new e(t.coords.latitude,t.coords.longitude))},function(e){r.reject(e)}),r._thenRunCallbacks(t)}}]),e}();r["default"]=d},{"./ParsePromise":20,"babel-runtime/helpers/classCallCheck":56,"babel-runtime/helpers/createClass":57,"babel-runtime/helpers/typeof":61}],16:[function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(r,"__esModule",{value:!0});var o=e("babel-runtime/helpers/typeof"),a=n(o),i=e("babel-runtime/core-js/object/get-prototype-of"),s=n(i),u=e("babel-runtime/helpers/classCallCheck"),l=n(u),c=e("babel-runtime/helpers/possibleConstructorReturn"),f=n(c),d=e("babel-runtime/helpers/inherits"),h=n(d),p=e("./ParseObject"),_=n(p),v=function(e){function t(e){(0,l["default"])(this,t);var r=(0,f["default"])(this,(t.__proto__||(0,s["default"])(t)).call(this,"_Installation"));if(e&&"object"===("undefined"==typeof e?"undefined":(0,a["default"])(e))&&!r.set(e||{}))throw new Error("Can't create an invalid Session");return r}return(0,h["default"])(t,e),t}(_["default"]);r["default"]=v,_["default"].registerSubclass("_Installation",v)},{"./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(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}function o(){var e=h["default"].getLiveQueryController();e.open()}function a(){var e=h["default"].getLiveQueryController();e.close()}function i(){var e=h["default"].getUserController();return e.currentUserAsync().then(function(e){return e?e.getSessionToken():void 0})}function s(){return h["default"].getLiveQueryController().getDefaultLiveQueryClient()}Object.defineProperty(r,"__esModule",{value:!0});var u=e("./EventEmitter"),l=n(u),c=e("./LiveQueryClient"),f=n(c),d=e("./CoreManager"),h=n(d),p=e("./ParsePromise"),_=n(p),v=new l["default"];v.open=o,v.close=a,v.on("error",function(){}),r["default"]=v;var y=void 0,b={setDefaultLiveQueryClient:function(e){y=e},getDefaultLiveQueryClient:function(){return y?_["default"].as(y):i().then(function(e){var t=h["default"].get("LIVEQUERY_SERVER_URL");if(t&&0!==t.indexOf("ws"))throw new Error("You need to set a proper Parse LiveQuery server url before using LiveQueryClient");if(!t){var r=h["default"].get("SERVER_URL"),n="ws://";0===r.indexOf("https")&&(n="wss://");var o=r.replace(/^https?:\/\//,"");t=n+o,h["default"].set("LIVEQUERY_SERVER_URL",t)}var a=h["default"].get("APPLICATION_ID"),i=h["default"].get("JAVASCRIPT_KEY"),s=h["default"].get("MASTER_KEY");return y=new f["default"]({applicationId:a,serverURL:t,javascriptKey:i,masterKey:s,sessionToken:e}),y.on("error",function(e){v.emit("error",e)}),y.on("open",function(){v.emit("open")}),y.on("close",function(){v.emit("close")}),y})},open:function(){var e=this;s().then(function(t){e.resolve(t.open())})},close:function(){var e=this;s().then(function(t){e.resolve(t.close())})},subscribe:function(e){var t=this,r=new l["default"];return s().then(function(n){n.shouldOpen()&&n.open();var o=i();return o.then(function(o){var a=n.subscribe(e,o);r.id=a.id,r.query=a.query,r.sessionToken=a.sessionToken,r.unsubscribe=a.unsubscribe,a.on("open",function(){r.emit("open")}),a.on("create",function(e){r.emit("create",e)}),a.on("update",function(e){r.emit("update",e)}),a.on("enter",function(e){r.emit("enter",e)}),a.on("leave",function(e){r.emit("leave",e)}),a.on("delete",function(e){r.emit("delete",e)}),t.resolve()})}),r},unsubscribe:function(e){var t=this;s().then(function(r){t.resolve(r.unsubscribe(e))})},_clearCachedDefaultClient:function(){y=null}};h["default"].setLiveQueryController(b)},{"./CoreManager":3,"./EventEmitter":4,"./LiveQueryClient":7,"./ParsePromise":20}],18:[function(e,t,r){"use strict";function n(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t["default"]=e,t}function o(e){return e&&e.__esModule?e:{"default":e}}function a(){var e=k["default"].get("SERVER_URL");"/"!==e[e.length-1]&&(e+="/");var t=e.replace(/https?:\/\//,"");return t.substr(t.indexOf("/"))}Object.defineProperty(r,"__esModule",{value:!0});var i=e("babel-runtime/core-js/object/define-property"),s=o(i),u=e("babel-runtime/core-js/object/create"),l=o(u),c=e("babel-runtime/core-js/object/freeze"),f=o(c),d=e("babel-runtime/core-js/json/stringify"),h=o(d),p=e("babel-runtime/core-js/object/keys"),_=o(p),v=e("babel-runtime/helpers/typeof"),y=o(v),b=e("babel-runtime/helpers/classCallCheck"),g=o(b),m=e("babel-runtime/helpers/createClass"),C=o(m),j=e("./CoreManager"),k=o(j),O=e("./canBeSerialized"),w=o(O),S=e("./decode"),E=o(S),P=e("./encode"),A=o(P),I=e("./equals"),T=(o(I),e("./escape")),N=o(T),R=e("./ParseACL"),M=o(R),x=e("./parseDate"),D=o(x),L=e("./ParseError"),U=o(L),F=e("./ParseFile"),K=o(F),q=e("./ParseOp"),J=e("./ParsePromise"),W=o(J),Q=e("./ParseQuery"),B=o(Q),V=e("./ParseRelation"),G=o(V),z=e("./SingleInstanceStateController"),Y=n(z),H=e("./unique"),$=o(H),X=e("./UniqueInstanceStateController"),Z=n(X),ee=e("./unsavedChildren"),te=o(ee),re={},ne=0,oe=0,ae=!k["default"].get("IS_NODE");ae?k["default"].setObjectStateController(Y):k["default"].setObjectStateController(Z);var ie=function(){function e(t,r,n){(0,g["default"])(this,e),"function"==typeof this.initialize&&this.initialize.apply(this,arguments);var o=null;if(this._objCount=oe++,"string"==typeof t)this.className=t,r&&"object"===("undefined"==typeof r?"undefined":(0,y["default"])(r))&&(o=r);else if(t&&"object"===("undefined"==typeof t?"undefined":(0,y["default"])(t))){this.className=t.className,o={};for(var a in t)"className"!==a&&(o[a]=t[a]);r&&"object"===("undefined"==typeof r?"undefined":(0,y["default"])(r))&&(n=r)}if(o&&!this.set(o,n))throw new Error("Can't create an invalid Parse Object")}return(0,C["default"])(e,[{key:"_getId",value:function(){if("string"==typeof this.id)return this.id;if("string"==typeof this._localId)return this._localId;var e="local"+String(ne++);return this._localId=e,e}},{key:"_getStateIdentifier",value:function(){if(ae){var e=this.id;return e||(e=this._getId()),{id:e,className:this.className}}return this}},{key:"_getServerData",value:function(){var e=k["default"].getObjectStateController();return e.getServerData(this._getStateIdentifier())}},{key:"_clearServerData",value:function(){var e=this._getServerData(),t={};for(var r in e)t[r]=void 0;var n=k["default"].getObjectStateController();n.setServerData(this._getStateIdentifier(),t)}},{key:"_getPendingOps",value:function(){var e=k["default"].getObjectStateController();return e.getPendingOps(this._getStateIdentifier())}},{key:"_clearPendingOps",value:function(){var e=this._getPendingOps(),t=e[e.length-1],r=(0,_["default"])(t);r.forEach(function(e){delete t[e]})}},{key:"_getDirtyObjectAttributes",value:function(){var t=this.attributes,r=k["default"].getObjectStateController(),n=r.getObjectCache(this._getStateIdentifier()),o={};for(var a in t){var i=t[a];if(i&&"object"===("undefined"==typeof i?"undefined":(0,y["default"])(i))&&!(i instanceof e)&&!(i instanceof K["default"])&&!(i instanceof G["default"]))try{var s=(0,A["default"])(i,!1,!0),u=(0,h["default"])(s);n[a]!==u&&(o[a]=i)}catch(l){o[a]=i}}return o}},{key:"_toFullJSON",value:function(e){var t=this.toJSON(e);return t.__type="Object",t.className=this.className,t}},{key:"_getSaveJSON",value:function(){var e=this._getPendingOps(),t=this._getDirtyObjectAttributes(),r={};for(var n in t)r[n]=new q.SetOp(t[n]).toJSON();for(n in e[0])r[n]=e[0][n].toJSON();return r}},{key:"_getSaveParams",value:function(){var e=this.id?"PUT":"POST",t=this._getSaveJSON(),r="classes/"+this.className;return this.id?r+="/"+this.id:"_User"===this.className&&(r="users"),{method:e,body:t,path:r}}},{key:"_finishFetch",value:function(e){!this.id&&e.objectId&&(this.id=e.objectId);var t=k["default"].getObjectStateController();t.initializeState(this._getStateIdentifier());var r={};for(var n in e)"ACL"===n?r[n]=new M["default"](e[n]):"objectId"!==n&&(r[n]=(0,E["default"])(e[n]),r[n]instanceof G["default"]&&r[n]._ensureParentAndKey(this,n));r.createdAt&&"string"==typeof r.createdAt&&(r.createdAt=(0,D["default"])(r.createdAt)),r.updatedAt&&"string"==typeof r.updatedAt&&(r.updatedAt=(0,D["default"])(r.updatedAt)),!r.updatedAt&&r.createdAt&&(r.updatedAt=r.createdAt),t.commitServerChanges(this._getStateIdentifier(),r)}},{key:"_setExisted",value:function(e){var t=k["default"].getObjectStateController(),r=t.getState(this._getStateIdentifier());r&&(r.existed=e)}},{key:"_migrateId",value:function(e){if(this._localId&&e)if(ae){var t=k["default"].getObjectStateController(),r=t.removeState(this._getStateIdentifier());this.id=e,delete this._localId,r&&t.initializeState(this._getStateIdentifier(),r)}else this.id=e,delete this._localId}},{key:"_handleSaveResponse",value:function(e,t){var r={},n=k["default"].getObjectStateController(),o=n.popPendingState(this._getStateIdentifier());for(var a in o)o[a]instanceof q.RelationOp?r[a]=o[a].applyTo(void 0,this,a):a in e||(r[a]=o[a].applyTo(void 0));for(a in e)"createdAt"!==a&&"updatedAt"!==a||"string"!=typeof e[a]?"ACL"===a?r[a]=new M["default"](e[a]):"objectId"!==a&&(r[a]=(0,E["default"])(e[a]),r[a]instanceof q.UnsetOp&&(r[a]=void 0)):r[a]=(0,D["default"])(e[a]);r.createdAt&&!r.updatedAt&&(r.updatedAt=r.createdAt),this._migrateId(e.objectId),201!==t&&this._setExisted(!0),n.commitServerChanges(this._getStateIdentifier(),r)}},{key:"_handleSaveError",value:function(){this._getPendingOps();var e=k["default"].getObjectStateController();e.mergeFirstPendingState(this._getStateIdentifier())}},{key:"initialize",value:function(){}},{key:"toJSON",value:function(e){var t=this.id?this.className+":"+this.id:this,e=e||[t],r={},n=this.attributes;for(var o in n)"createdAt"!==o&&"updatedAt"!==o||!n[o].toJSON?r[o]=(0,A["default"])(n[o],!1,!1,e):r[o]=n[o].toJSON();var a=this._getPendingOps();for(var o in a[0])r[o]=a[0][o].toJSON();return this.id&&(r.objectId=this.id),r}},{key:"equals",value:function(t){return this===t||t instanceof e&&this.className===t.className&&this.id===t.id&&"undefined"!=typeof this.id}},{key:"dirty",value:function(e){if(!this.id)return!0;var t=this._getPendingOps(),r=this._getDirtyObjectAttributes();if(e){if(r.hasOwnProperty(e))return!0;for(var n=0;n-1)throw new Error("Cannot modify readonly attribute: "+i);r.unset?o[i]=new q.UnsetOp:n[i]instanceof q.Op?o[i]=n[i]:n[i]&&"object"===(0,y["default"])(n[i])&&"string"==typeof n[i].__op?o[i]=(0,q.opFromJSON)(n[i]):"objectId"===i||"id"===i?"string"==typeof n[i]&&(this.id=n[i]):"ACL"!==i||"object"!==(0,y["default"])(n[i])||n[i]instanceof M["default"]?o[i]=new q.SetOp(n[i]):o[i]=new q.SetOp(new M["default"](n[i]))}var s=this.attributes,u={};for(var l in o)o[l]instanceof q.RelationOp?u[l]=o[l].applyTo(s[l],this,l):o[l]instanceof q.UnsetOp||(u[l]=o[l].applyTo(s[l]));if(!r.ignoreValidation){var c=this.validate(u);if(c)return"function"==typeof r.error&&r.error(this,c),!1}var f=this._getPendingOps(),d=f.length-1,h=k["default"].getObjectStateController();for(var l in o){var p=o[l].mergeWith(f[d][l]);h.setPendingOp(this._getStateIdentifier(),l,p)}return this}},{key:"unset",value:function(e,t){return t=t||{},t.unset=!0,this.set(e,null,t)}},{key:"increment",value:function(e,t){if("undefined"==typeof t&&(t=1),"number"!=typeof t)throw new Error("Cannot increment by a non-numeric amount.");return this.set(e,new q.IncrementOp(t))}},{key:"add",value:function(e,t){return this.set(e,new q.AddOp([t]))}},{key:"addUnique",value:function(e,t){return this.set(e,new q.AddUniqueOp([t]))}},{key:"remove",value:function(e,t){return this.set(e,new q.RemoveOp([t]))}},{key:"op",value:function(e){for(var t=this._getPendingOps(),r=t.length;r--;)if(t[r][e])return t[r][e]}},{key:"clone",value:function(){var e=new this.constructor;e.className||(e.className=this.className);var t=this.attributes;if("function"==typeof this.constructor.readOnlyAttributes){var r=this.constructor.readOnlyAttributes()||[],n={};for(var o in t)r.indexOf(o)<0&&(n[o]=t[o]);t=n}return e.set&&e.set(t),e}},{key:"newInstance",value:function(){var e=new this.constructor;if(e.className||(e.className=this.className),e.id=this.id,ae)return e;var t=k["default"].getObjectStateController();return t&&t.duplicateState(this._getStateIdentifier(),e._getStateIdentifier()),e}},{key:"isNew",value:function(){return!this.id}},{key:"existed",value:function(){if(!this.id)return!1;var e=k["default"].getObjectStateController(),t=e.getState(this._getStateIdentifier());return!!t&&t.existed}},{key:"isValid",value:function(){return!this.validate(this.attributes)}},{key:"validate",value:function(e){if(e.hasOwnProperty("ACL")&&!(e.ACL instanceof M["default"]))return new U["default"](U["default"].OTHER_CAUSE,"ACL must be a Parse ACL.");for(var t in e)if(!/^[A-Za-z][0-9A-Za-z_]*$/.test(t))return new U["default"](U["default"].INVALID_KEY_NAME);return!1}},{key:"getACL",value:function(){var e=this.get("ACL");return e instanceof M["default"]?e:null}},{key:"setACL",value:function(e,t){return this.set("ACL",e,t)}},{key:"revert",value:function(){this._clearPendingOps()}},{key:"clear",value:function(){var e=this.attributes,t={},r=["createdAt","updatedAt"];"function"==typeof this.constructor.readOnlyAttributes&&(r=r.concat(this.constructor.readOnlyAttributes()));for(var n in e)r.indexOf(n)<0&&(t[n]=!0);return this.set(t,{unset:!0})}},{key:"fetch",value:function(e){e=e||{};var t={};e.hasOwnProperty("useMasterKey")&&(t.useMasterKey=e.useMasterKey),e.hasOwnProperty("sessionToken")&&(t.sessionToken=e.sessionToken);var r=k["default"].getObjectController();return r.fetch(this,!0,t)._thenRunCallbacks(e)}},{key:"save",value:function(e,t,r){var n,o,a=this;if("object"===("undefined"==typeof e?"undefined":(0,y["default"])(e))||"undefined"==typeof e?(n=e,"object"===("undefined"==typeof t?"undefined":(0,y["default"])(t))&&(o=t)):(n={},n[e]=t,o=r),!o&&n&&(o={},"function"==typeof n.success&&(o.success=n.success,delete n.success),"function"==typeof n.error&&(o.error=n.error,delete n.error)),n){var i=this.validate(n);if(i)return o&&"function"==typeof o.error&&o.error(this,i),W["default"].error(i);this.set(n,o)}o=o||{};var s={};o.hasOwnProperty("useMasterKey")&&(s.useMasterKey=!!o.useMasterKey),o.hasOwnProperty("sessionToken")&&"string"==typeof o.sessionToken&&(s.sessionToken=o.sessionToken);var u=k["default"].getObjectController(),l=(0,te["default"])(this);return u.save(l,s).then(function(){return u.save(a,s)})._thenRunCallbacks(o,this)}},{key:"destroy",value:function(e){e=e||{};var t={};return e.hasOwnProperty("useMasterKey")&&(t.useMasterKey=e.useMasterKey),e.hasOwnProperty("sessionToken")&&(t.sessionToken=e.sessionToken),this.id?k["default"].getObjectController().destroy(this,t)._thenRunCallbacks(e):W["default"].as()._thenRunCallbacks(e)}},{key:"attributes",get:function(){var e=k["default"].getObjectStateController();return(0,f["default"])(e.estimateAttributes(this._getStateIdentifier()))}},{key:"createdAt",get:function(){return this._getServerData().createdAt}},{key:"updatedAt",get:function(){return this._getServerData().updatedAt}}],[{key:"_clearAllState",value:function(){var e=k["default"].getObjectStateController();e.clearAllState()}},{key:"fetchAll",value:function(e,t){var t=t||{},r={};return t.hasOwnProperty("useMasterKey")&&(r.useMasterKey=t.useMasterKey),t.hasOwnProperty("sessionToken")&&(r.sessionToken=t.sessionToken),k["default"].getObjectController().fetch(e,!0,r)._thenRunCallbacks(t)}},{key:"fetchAllIfNeeded",value:function(e,t){var t=t||{},r={};return t.hasOwnProperty("useMasterKey")&&(r.useMasterKey=t.useMasterKey),t.hasOwnProperty("sessionToken")&&(r.sessionToken=t.sessionToken),k["default"].getObjectController().fetch(e,!1,r)._thenRunCallbacks(t)}},{key:"destroyAll",value:function(e,t){var t=t||{},r={};return t.hasOwnProperty("useMasterKey")&&(r.useMasterKey=t.useMasterKey),t.hasOwnProperty("sessionToken")&&(r.sessionToken=t.sessionToken),k["default"].getObjectController().destroy(e,r)._thenRunCallbacks(t)}},{key:"saveAll",value:function(e,t){var t=t||{},r={};return t.hasOwnProperty("useMasterKey")&&(r.useMasterKey=t.useMasterKey),t.hasOwnProperty("sessionToken")&&(r.sessionToken=t.sessionToken),k["default"].getObjectController().save(e,r)._thenRunCallbacks(t)}},{key:"createWithoutData",value:function(e){var t=new this;return t.id=e,t}},{key:"fromJSON",value:function(t,r){if(!t.className)throw new Error("Cannot create an object without a className");var n=re[t.className],o=n?new n:new e(t.className),a={};for(var i in t)"className"!==i&&"__type"!==i&&(a[i]=t[i]);if(r){a.objectId&&(o.id=a.objectId);var s=null;"function"==typeof o._preserveFieldsOnFetch&&(s=o._preserveFieldsOnFetch()),o._clearServerData(),s&&o._finishFetch(s)}return o._finishFetch(a),t.objectId&&o._setExisted(!0),o}},{key:"registerSubclass",value:function(e,t){if("string"!=typeof e)throw new TypeError("The first argument must be a valid class name.");if("undefined"==typeof t)throw new TypeError("You must supply a subclass constructor.");if("function"!=typeof t)throw new TypeError("You must register the subclass constructor. Did you attempt to register an instance of the subclass?");re[e]=t,t.className||(t.className=e)}},{key:"extend",value:function(t,r,n){if("string"!=typeof t){if(t&&"string"==typeof t.className)return e.extend(t.className,t,r);throw new Error("Parse.Object.extend's first argument should be the className.")}var o=t;"User"===o&&k["default"].get("PERFORM_USER_REWRITE")&&(o="_User");var a=e.prototype;this.hasOwnProperty("__super__")&&this.__super__?a=this.prototype:re[o]&&(a=re[o].prototype);var i=function(e,t){if(this.className=o,this._objCount=oe++,"function"==typeof this.initialize&&this.initialize.apply(this,arguments),e&&"object"===("undefined"==typeof e?"undefined":(0,y["default"])(e))&&!this.set(e||{},t))throw new Error("Can't create an invalid Parse Object")};if(i.className=o,i.__super__=a,i.prototype=(0,l["default"])(a,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}),r)for(var u in r)"className"!==u&&(0,s["default"])(i.prototype,u,{value:r[u],enumerable:!1,writable:!0,configurable:!0});if(n)for(var u in n)"className"!==u&&(0,s["default"])(i,u,{value:n[u],enumerable:!1,writable:!0,configurable:!0});return i.extend=function(t,r,n){return"string"==typeof t?e.extend.call(i,t,r,n):e.extend.call(i,o,t,r)},i.createWithoutData=e.createWithoutData,re[o]=i,i}},{key:"enableSingleInstance",value:function(){ae=!0,k["default"].setObjectStateController(Y)}},{key:"disableSingleInstance",value:function(){ae=!1,k["default"].setObjectStateController(Z)}}]),e}();r["default"]=ie;var se={fetch:function(e,t,r){if(Array.isArray(e)){if(e.length<1)return W["default"].as([]);var n=[],o=[],a=null,i=[],s=null;if(e.forEach(function(e,r){s||(a||(a=e.className),a!==e.className&&(s=new U["default"](U["default"].INVALID_CLASS_NAME,"All objects should be of the same class")),e.id||(s=new U["default"](U["default"].MISSING_OBJECT_ID,"All objects must have an ID")),(t||0===(0,_["default"])(e._getServerData()).length)&&(o.push(e.id),n.push(e)),i.push(e))}),s)return W["default"].error(s);var u=new B["default"](a);return u.containedIn("objectId",o),u._limit=o.length,u.find(r).then(function(e){var r={};e.forEach(function(e){r[e.id]=e});for(var o=0;o=20&&n.push([]))}),0===n[n.length-1].length&&n.pop();var o=W["default"].as(),i=[];return n.forEach(function(e){o=o.then(function(){return r.request("POST","batch",{requests:e.map(function(e){return{method:"DELETE",path:a()+"classes/"+e.className+"/"+e._getId(),body:{}}})},t).then(function(t){for(var r=0;r0},function(){var e=[],i=[];if(u.forEach(function(t){e.length<20&&(0,w["default"])(t)?e.push(t):i.push(t)}),u=i,e.length<1)return W["default"].error(new U["default"](U["default"].OTHER_CAUSE,"Tried to save a batch with a cycle."));var s=new W["default"],l=[],c=[];return e.forEach(function(e,t){var r=new W["default"];l.push(r),n.pushPendingState(e._getStateIdentifier()),c.push(n.enqueueTask(e._getStateIdentifier(),function(){return r.resolve(),s.then(function(r,n){if(r[t].hasOwnProperty("success"))e._handleSaveResponse(r[t].success,n);else{if(!o&&r[t].hasOwnProperty("error")){var a=r[t].error;o=new U["default"](a.code,a.error),u=[]}e._handleSaveError()}})}))}),W["default"].when(l).then(function(){return r.request("POST","batch",{requests:e.map(function(e){var t=e._getSaveParams();return t.path=a()+t.path,t})},t)}).then(function(e,t,r){s.resolve(e,t)}),W["default"].when(c)}).then(function(){return o?W["default"].error(o):W["default"].as(e)})})}if(e instanceof ie){var l=e,c=function(){var e=l._getSaveParams();return r.request(e.method,e.path,e.body,t).then(function(e,t){l._handleSaveResponse(e,t)},function(e){return l._handleSaveError(),W["default"].error(e)})};return n.pushPendingState(e._getStateIdentifier()),n.enqueueTask(e._getStateIdentifier(),c).then(function(){return e},function(e){return W["default"].error(e)})}return W["default"].as()}};k["default"].setObjectController(se)},{"./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(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}function o(e){if(!e||!e.__op)return null;switch(e.__op){case"Delete":return new A;case"Increment":return new I(e.amount);case"Add":return new T((0,b["default"])(e.objects));case"AddUnique":return new N((0,b["default"])(e.objects));case"Remove":return new R((0,b["default"])(e.objects));case"AddRelation":var t=(0,b["default"])(e.objects);return Array.isArray(t)?new M(t,[]):new M([],[]);case"RemoveRelation":var r=(0,b["default"])(e.objects);return Array.isArray(r)?new M([],r):new M([],[]);case"Batch":for(var t=[],r=[],n=0;n-1;)r.splice(n,1),n=r.indexOf(this._value[t]);if(this._value[t]instanceof j["default"]&&this._value[t].id)for(var o=0;o-1&&r.splice(t,1)}),this.relationsToAdd.forEach(function(e){var t=r.indexOf(e);t<0&&r.push(e)});var n=e.relationsToRemove.concat([]);this.relationsToAdd.forEach(function(e){var t=n.indexOf(e);t>-1&&n.splice(t,1)}),this.relationsToRemove.forEach(function(e){var t=n.indexOf(e);t<0&&n.push(e)});var o=new t(r,n);return o._targetClassName=this._targetClassName,o}throw new Error("Cannot merge Relation Op with the previous Op")}},{key:"toJSON",value:function(){var e=this,t=function(t){return{__type:"Pointer",className:e._targetClassName,objectId:t}},r=null,n=null,o=null;return this.relationsToAdd.length>0&&(o=this.relationsToAdd.map(t),r={__op:"AddRelation",objects:o}),this.relationsToRemove.length>0&&(o=this.relationsToRemove.map(t),n={__op:"RemoveRelation",objects:o}),r&&n?{__op:"Batch",ops:[r,n]}:r||n||{}}}]),t}(E)},{"./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(e,t,r){(function(t){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(r,"__esModule",{value:!0});var o=e("babel-runtime/core-js/get-iterator"),a=n(o),i=e("babel-runtime/helpers/typeof"),s=n(i),u=e("babel-runtime/helpers/classCallCheck"),l=n(u),c=e("babel-runtime/helpers/createClass"),f=n(c),d=!0,h=function(){function e(t){(0,l["default"])(this,e),this._resolved=!1,this._rejected=!1,this._resolvedCallbacks=[],this._rejectedCallbacks=[],"function"==typeof t&&t(this.resolve.bind(this),this.reject.bind(this))}return(0,f["default"])(e,[{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=!0;for(var e=arguments.length,t=Array(e),r=0;r=r&&h.resolve(_)))},function(e){h.reject(e),d=!0}):(_[n]=t,p++,!d&&p>=r&&h.resolve(_))}),h}},{key:"race",value:function(t){var r=!1,n=new e,o=!0,i=!1,s=void 0;try{for(var u,l=(0,a["default"])(t);!(o=(u=l.next()).done);o=!0){var c=u.value;e.is(c)?c.then(function(e){r||(r=!0,n.resolve(e))},function(e){r||(r=!0,n.reject(e))}):r||(r=!0,n.resolve(c))}}catch(f){i=!0,s=f}finally{try{!o&&l["return"]&&l["return"]()}finally{if(i)throw s}}return n}},{key:"_continueWhile",value:function(t,r){return t()?r().then(function(){return e._continueWhile(t,r)}):e.as()}},{key:"isPromisesAPlusCompliant",value:function(){return d}},{key:"enableAPlusCompliant",value:function(){d=!0}},{key:"disableAPlusCompliant",value:function(){d=!1}}]),e}();r["default"]=h}).call(this,e("_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(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}function o(e){return"\\Q"+e.replace("\\E","\\E\\\\E\\Q")+"\\E"}function a(e,t){var r={};if(t.forEach(function(t){var n=t.indexOf(".")!==-1;if(n||e.hasOwnProperty(t)){if(n){var o=t.split("."),a=e,i=r;o.forEach(function(e,t,r){a[e]||(a[e]=t==r.length-1?void 0:{}),a=a[e],t0){var n=function a(e,t,r,n){if(n)for(var o in e)e.hasOwnProperty(o)&&!t.hasOwnProperty(o)&&(t[o]=e[o]);for(var o in r)a(e[o],t[o],r[o],!0)},o=_["default"].getObjectStateController().getServerData({id:e.objectId,className:e.className});n(o,e,r,!1)}}Object.defineProperty(r,"__esModule",{value:!0});var i=e("babel-runtime/helpers/typeof"),s=n(i),u=e("babel-runtime/helpers/classCallCheck"),l=n(u),c=e("babel-runtime/helpers/createClass"),f=n(c),d=e("babel-runtime/core-js/object/keys"),h=n(d),p=e("./CoreManager"),_=n(p),v=e("./encode"),y=n(v),b=e("./ParseError"),g=n(b),m=e("./ParseGeoPoint"),C=n(m),j=e("./ParseObject"),k=n(j),O=e("./ParsePromise"),w=n(O),S=function(){function e(t){if((0,l["default"])(this,e),"string"==typeof t)"User"===t&&_["default"].get("PERFORM_USER_REWRITE")?this.className="_User":this.className=t;else if(t instanceof k["default"])this.className=t.className;else{if("function"!=typeof t)throw new TypeError("A ParseQuery must be constructed with a ParseObject or class name.");if("string"==typeof t.className)this.className=t.className;else{var r=new t;this.className=r.className}}this._where={},this._include=[],this._limit=-1,this._skip=0,this._extraOptions={}}return(0,f["default"])(e,[{key:"_orQuery",value:function(e){var t=e.map(function(e){return e.toJSON().where});return this._where.$or=t,this}},{key:"_addCondition",value:function(e,t,r){return this._where[e]&&"string"!=typeof this._where[e]||(this._where[e]={}),this._where[e][t]=(0,y["default"])(r,!1,!0),this}},{key:"_regexStartWith",value:function(e){return"^"+o(e)}},{key:"toJSON",value:function(){var e={where:this._where};this._include.length&&(e.include=this._include.join(",")),this._select&&(e.keys=this._select.join(",")),this._limit>=0&&(e.limit=this._limit),this._skip>0&&(e.skip=this._skip),this._order&&(e.order=this._order.join(","));for(var t in this._extraOptions)e[t]=this._extraOptions[t];return e}},{key:"get",value:function(e,t){this.equalTo("objectId",e);var r={};return t&&t.hasOwnProperty("useMasterKey")&&(r.useMasterKey=t.useMasterKey),t&&t.hasOwnProperty("sessionToken")&&(r.sessionToken=t.sessionToken),this.first(r).then(function(e){if(e)return e;var t=new g["default"](g["default"].OBJECT_NOT_FOUND,"Object not found.");return w["default"].error(t)})._thenRunCallbacks(t,null)}},{key:"find",value:function(e){var t=this;e=e||{};var r={};e.hasOwnProperty("useMasterKey")&&(r.useMasterKey=e.useMasterKey),e.hasOwnProperty("sessionToken")&&(r.sessionToken=e.sessionToken);var n=_["default"].getQueryController(),o=this._select;return n.find(this.className,this.toJSON(),r).then(function(e){return e.results.map(function(r){var n=e.className||t.className;return r.className||(r.className=n),o&&a(r,o),k["default"].fromJSON(r,!o)})})._thenRunCallbacks(e)}},{key:"count",value:function(e){e=e||{};var t={};e.hasOwnProperty("useMasterKey")&&(t.useMasterKey=e.useMasterKey),e.hasOwnProperty("sessionToken")&&(t.sessionToken=e.sessionToken);var r=_["default"].getQueryController(),n=this.toJSON();return n.limit=0,n.count=1,r.find(this.className,n,t).then(function(e){return e.count})._thenRunCallbacks(e)}},{key:"first",value:function(e){var t=this;e=e||{};var r={};e.hasOwnProperty("useMasterKey")&&(r.useMasterKey=e.useMasterKey),e.hasOwnProperty("sessionToken")&&(r.sessionToken=e.sessionToken);var n=_["default"].getQueryController(),o=this.toJSON();o.limit=1;var i=this._select;return n.find(this.className,o,r).then(function(e){var r=e.results;if(r[0])return r[0].className||(r[0].className=t.className),i&&a(r[0],i),k["default"].fromJSON(r[0],!i)})._thenRunCallbacks(e)}},{key:"each",value:function(t,r){if(r=r||{},this._order||this._skip||this._limit>=0)return w["default"].error("Cannot iterate on a query with sort, skip, or limit.")._thenRunCallbacks(r);new w["default"];var n=new e(this.className);n._limit=r.batchSize||100,n._include=this._include.map(function(e){return e}),this._select&&(n._select=this._select.map(function(e){return e})),n._where={};for(var o in this._where){var a=this._where[o];if(Array.isArray(a))n._where[o]=a.map(function(e){return e});else if(a&&"object"===("undefined"==typeof a?"undefined":(0,s["default"])(a))){var i={};n._where[o]=i;for(var u in a)i[u]=a[u]}else n._where[o]=a}n.ascending("objectId");var l={};r.hasOwnProperty("useMasterKey")&&(l.useMasterKey=r.useMasterKey),r.hasOwnProperty("sessionToken")&&(l.sessionToken=r.sessionToken);var c=!1;return w["default"]._continueWhile(function(){return!c},function(){return n.find(l).then(function(e){var r=w["default"].as();return e.forEach(function(e){r=r.then(function(){return t(e)})}),r.then(function(){e.length>=n._limit?n.greaterThan("objectId",e[e.length-1].id):c=!0})})})._thenRunCallbacks(r)}},{key:"equalTo",value:function(e,t){return"undefined"==typeof t?this.doesNotExist(e):(this._where[e]=(0,y["default"])(t,!1,!0),this)}},{key:"notEqualTo",value:function(e,t){return this._addCondition(e,"$ne",t)}},{key:"lessThan",value:function(e,t){return this._addCondition(e,"$lt",t)}},{key:"greaterThan",value:function(e,t){return this._addCondition(e,"$gt",t)}},{key:"lessThanOrEqualTo",value:function(e,t){return this._addCondition(e,"$lte",t)}},{key:"greaterThanOrEqualTo",value:function(e,t){return this._addCondition(e,"$gte",t)}},{key:"containedIn",value:function(e,t){return this._addCondition(e,"$in",t)}},{key:"notContainedIn",value:function(e,t){return this._addCondition(e,"$nin",t)}},{key:"containsAll",value:function(e,t){return this._addCondition(e,"$all",t)}},{key:"containsAllStartingWith",value:function(e,t){var r=this;return Array.isArray(t)||(t=[t]),t=t.map(function(e){return{$regex:r._regexStartWith(e)}}),this.containsAll(e,t)}},{key:"exists",value:function(e){return this._addCondition(e,"$exists",!0)}},{key:"doesNotExist",value:function(e){return this._addCondition(e,"$exists",!1)}},{key:"matches",value:function(e,t,r){return this._addCondition(e,"$regex",t),r||(r=""),t.ignoreCase&&(r+="i"),t.multiline&&(r+="m"),r.length&&this._addCondition(e,"$options",r),this}},{key:"matchesQuery",value:function(e,t){var r=t.toJSON();return r.className=t.className,this._addCondition(e,"$inQuery",r)}},{key:"doesNotMatchQuery",value:function(e,t){var r=t.toJSON();return r.className=t.className,this._addCondition(e,"$notInQuery",r)}},{key:"matchesKeyInQuery",value:function(e,t,r){var n=r.toJSON();return n.className=r.className,this._addCondition(e,"$select",{key:t,query:n})}},{key:"doesNotMatchKeyInQuery",value:function(e,t,r){var n=r.toJSON();return n.className=r.className,this._addCondition(e,"$dontSelect",{key:t,query:n})}},{key:"contains",value:function(e,t){if("string"!=typeof t)throw new Error("The value being searched for must be a string.");return this._addCondition(e,"$regex",o(t))}},{key:"startsWith",value:function(e,t){if("string"!=typeof t)throw new Error("The value being searched for must be a string.");return this._addCondition(e,"$regex",this._regexStartWith(t))}},{key:"endsWith",value:function(e,t){if("string"!=typeof t)throw new Error("The value being searched for must be a string.");return this._addCondition(e,"$regex",o(t)+"$")}},{key:"near",value:function(e,t){return t instanceof C["default"]||(t=new C["default"](t)),this._addCondition(e,"$nearSphere",t)}},{key:"withinRadians",value:function(e,t,r){return this.near(e,t),this._addCondition(e,"$maxDistance",r)}},{key:"withinMiles",value:function(e,t,r){return this.withinRadians(e,t,r/3958.8)}},{key:"withinKilometers",value:function(e,t,r){return this.withinRadians(e,t,r/6371)}},{key:"withinGeoBox",value:function(e,t,r){return t instanceof C["default"]||(t=new C["default"](t)),r instanceof C["default"]||(r=new C["default"](r)),this._addCondition(e,"$within",{$box:[t,r]}),this}},{key:"ascending",value:function(){this._order=[];for(var e=arguments.length,t=Array(e),r=0;r=200&&l.status<300){var e;try{e=JSON.parse(l.responseText)}catch(t){i.reject(t.toString())}e&&i.resolve(e,l.status,l)}else if(l.status>=500||0===l.status)if(++s-1)return!0;for(var r=0;r-1||e.dirty()||(0,u["default"])(e._getServerData()).length<1?e.toPointer():(n=n.concat(a),e._toFullJSON(n))}if(e instanceof y.Op||e instanceof c["default"]||e instanceof p["default"]||e instanceof g["default"])return e.toJSON();if(e instanceof d["default"]){if(!e.url())throw new Error("Tried to encode an unsaved file.");return e.toJSON()}if("[object Date]"===m.call(e)){if(isNaN(e))throw new Error("Tried to encode an invalid date.");return{__type:"Date",iso:e.toJSON()}}if("[object RegExp]"===m.call(e)&&"string"==typeof e.source)return e.source;if(Array.isArray(e))return e.map(function(e){return o(e,t,r,n)});if(e&&"object"===("undefined"==typeof e?"undefined":(0,i["default"])(e))){var s={};for(var l in e)s[l]=o(e[l],t,r,n);return s}return e}Object.defineProperty(r,"__esModule",{value:!0});var a=e("babel-runtime/helpers/typeof"),i=n(a),s=e("babel-runtime/core-js/object/keys"),u=n(s);r["default"]=function(e,t,r,n){return o(e,!!t,!!r,n||[])};var l=e("./ParseACL"),c=n(l),f=e("./ParseFile"),d=n(f),h=e("./ParseGeoPoint"),p=n(h),_=e("./ParseObject"),v=n(_),y=e("./ParseOp"),b=e("./ParseRelation"),g=n(b),m=Object.prototype.toString},{"./ParseACL":11,"./ParseFile":14,"./ParseGeoPoint":15,"./ParseObject":18,"./ParseOp":19,"./ParseRelation":22,"babel-runtime/core-js/object/keys":51,"babel-runtime/helpers/typeof":61}],37:[function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(("undefined"==typeof e?"undefined":(0,u["default"])(e))!==("undefined"==typeof t?"undefined":(0,u["default"])(t)))return!1;if(!e||"object"!==("undefined"==typeof e?"undefined":(0,u["default"])(e)))return e===t;if(Array.isArray(e)||Array.isArray(t)){if(!Array.isArray(e)||!Array.isArray(t))return!1;if(e.length!==t.length)return!1;for(var r=e.length;r--;)if(!o(e[r],t[r]))return!1;return!0}if(e instanceof c["default"]||e instanceof d["default"]||e instanceof p["default"]||e instanceof v["default"])return e.equals(t);if((0,i["default"])(e).length!==(0,i["default"])(t).length)return!1;for(var n in e)if(!o(e[n],t[n]))return!1;return!0}Object.defineProperty(r,"__esModule",{value:!0});var a=e("babel-runtime/core-js/object/keys"),i=n(a),s=e("babel-runtime/helpers/typeof"),u=n(s);r["default"]=o;var l=e("./ParseACL"),c=n(l),f=e("./ParseFile"),d=n(f),h=e("./ParseGeoPoint"),p=n(h),_=e("./ParseObject"),v=n(_)},{"./ParseACL":11,"./ParseFile":14,"./ParseGeoPoint":15,"./ParseObject":18,"babel-runtime/core-js/object/keys":51,"babel-runtime/helpers/typeof":61}],38:[function(e,t,r){"use strict";function n(e){return e.replace(/[&<>\/'"]/g,function(e){return o[e]})}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=n;var o={"&":"&","<":"<",">":">","/":"/","'":"'",'"':"""}},{}],39:[function(e,t,r){"use strict";function n(e){return e.indexOf("r:")>-1}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=n},{}],40:[function(e,t,r){"use strict";function n(e){var t=new RegExp("^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})T([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})(.([0-9]+))?Z$"),r=t.exec(e);if(!r)return null;var n=r[1]||0,o=(r[2]||1)-1,a=r[3]||0,i=r[4]||0,s=r[5]||0,u=r[6]||0,l=r[8]||0;return new Date(Date.UTC(n,o,a,i,s,u,l))}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=n},{}],41:[function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}function o(e){var t=[];return e.forEach(function(e){e instanceof u["default"]?(0,i["default"])(t,e)||t.push(e):t.indexOf(e)<0&&t.push(e)}),t}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=o;var a=e("./arrayContainsObject"),i=n(a),s=e("./ParseObject"),u=n(s)},{"./ParseObject":18,"./arrayContainsObject":33}],42:[function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){var r={objects:{},files:[]},n=e.className+":"+e._getId();r.objects[n]=!e.dirty()||e;var o=e.attributes;for(var i in o)"object"===(0,s["default"])(o[i])&&a(o[i],r,!1,!!t);var u=[];for(var l in r.objects)l!==n&&r.objects[l]!==!0&&u.push(r.objects[l]);return u.concat(r.files)}function a(e,t,r,n){if(e instanceof f["default"]){if(!e.id&&r)throw new Error("Cannot create a pointer to an unsaved Object.");var o=e.className+":"+e._getId();if(!t.objects[o]){t.objects[o]=!e.dirty()||e;var i=e.attributes;for(var u in i)"object"===(0,s["default"])(i[u])&&a(i[u],t,!n,n)}}else{if(e instanceof l["default"])return void(!e.url()&&t.files.indexOf(e)<0&&t.files.push(e));if(!(e instanceof h["default"])){Array.isArray(e)&&e.forEach(function(e){"object"===("undefined"==typeof e?"undefined":(0,s["default"])(e))&&a(e,t,r,n)});for(var c in e)"object"===(0,s["default"])(e[c])&&a(e[c],t,r,n)}}}Object.defineProperty(r,"__esModule",{value:!0});var i=e("babel-runtime/helpers/typeof"),s=n(i);r["default"]=o;var u=e("./ParseFile"),l=n(u),c=e("./ParseObject"),f=n(c),d=e("./ParseRelation"),h=n(d)},{"./ParseFile":14,"./ParseObject":18,"./ParseRelation":22,"babel-runtime/helpers/typeof":61}],43:[function(e,t,r){t.exports={"default":e("core-js/library/fn/get-iterator"),__esModule:!0}},{"core-js/library/fn/get-iterator":62}],44:[function(e,t,r){t.exports={"default":e("core-js/library/fn/json/stringify"),__esModule:!0}},{"core-js/library/fn/json/stringify":63}],45:[function(e,t,r){t.exports={"default":e("core-js/library/fn/map"),__esModule:!0}},{"core-js/library/fn/map":64}],46:[function(e,t,r){t.exports={"default":e("core-js/library/fn/object/create"),__esModule:!0}},{"core-js/library/fn/object/create":65}],47:[function(e,t,r){t.exports={"default":e("core-js/library/fn/object/define-property"),__esModule:!0}},{"core-js/library/fn/object/define-property":66}],48:[function(e,t,r){t.exports={"default":e("core-js/library/fn/object/freeze"),__esModule:!0}},{"core-js/library/fn/object/freeze":67}],49:[function(e,t,r){t.exports={"default":e("core-js/library/fn/object/get-own-property-descriptor"),__esModule:!0}},{"core-js/library/fn/object/get-own-property-descriptor":68}],50:[function(e,t,r){t.exports={"default":e("core-js/library/fn/object/get-prototype-of"),__esModule:!0}},{"core-js/library/fn/object/get-prototype-of":69}],51:[function(e,t,r){t.exports={"default":e("core-js/library/fn/object/keys"),__esModule:!0}},{"core-js/library/fn/object/keys":70}],52:[function(e,t,r){t.exports={"default":e("core-js/library/fn/object/set-prototype-of"),__esModule:!0}},{"core-js/library/fn/object/set-prototype-of":71}],53:[function(e,t,r){t.exports={"default":e("core-js/library/fn/symbol"),__esModule:!0}},{"core-js/library/fn/symbol":72}],54:[function(e,t,r){t.exports={"default":e("core-js/library/fn/symbol/iterator"),__esModule:!0}},{"core-js/library/fn/symbol/iterator":73}],55:[function(e,t,r){t.exports={"default":e("core-js/library/fn/weak-map"),__esModule:!0}},{"core-js/library/fn/weak-map":74}],56:[function(e,t,r){"use strict";r.__esModule=!0,r["default"]=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},{}],57:[function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}r.__esModule=!0;var o=e("../core-js/object/define-property"),a=n(o);r["default"]=function(){function e(e,t){for(var r=0;rc;)if(s=u[c++],s!=s)return!0}else for(;l>c;c++)if((e||c in u)&&u[c]===r)return e||c||0;return!e&&-1}}},{"./_to-index":139,"./_to-iobject":141,"./_to-length":142}],81:[function(e,t,r){var n=e("./_ctx"),o=e("./_iobject"),a=e("./_to-object"),i=e("./_to-length"),s=e("./_array-species-create");t.exports=function(e,t){var r=1==e,u=2==e,l=3==e,c=4==e,f=6==e,d=5==e||f,h=t||s;return function(t,s,p){for(var _,v,y=a(t),b=o(y),g=n(s,p,3),m=i(b.length),C=0,j=r?h(t,m):u?h(t,0):void 0;m>C;C++)if((d||C in b)&&(_=b[C],v=g(_,C,y),e))if(r)j[C]=v;else if(v)switch(e){case 3:return!0;case 5:return _;case 6:return C;case 2:j.push(_)}else if(c)return!1;return f?-1:l||c?c:j}}},{"./_array-species-create":83,"./_ctx":91,"./_iobject":105,"./_to-length":142,"./_to-object":143}],82:[function(e,t,r){var n=e("./_is-object"),o=e("./_is-array"),a=e("./_wks")("species");t.exports=function(e){var t;return o(e)&&(t=e.constructor,"function"!=typeof t||t!==Array&&!o(t.prototype)||(t=void 0),n(t)&&(t=t[a],null===t&&(t=void 0))),void 0===t?Array:t}},{"./_is-array":107,"./_is-object":108,"./_wks":148}],83:[function(e,t,r){var n=e("./_array-species-constructor");t.exports=function(e,t){return new(n(e))(t)}},{"./_array-species-constructor":82}],84:[function(e,t,r){var n=e("./_cof"),o=e("./_wks")("toStringTag"),a="Arguments"==n(function(){return arguments}()),i=function(e,t){try{return e[t]}catch(r){}};t.exports=function(e){var t,r,s;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(r=i(t=Object(e),o))?r:a?n(t):"Object"==(s=n(t))&&"function"==typeof t.callee?"Arguments":s}},{"./_cof":85,"./_wks":148}],85:[function(e,t,r){var n={}.toString;t.exports=function(e){return n.call(e).slice(8,-1)}},{}],86:[function(e,t,r){"use strict";var n=e("./_object-dp").f,o=e("./_object-create"),a=e("./_redefine-all"),i=e("./_ctx"),s=e("./_an-instance"),u=e("./_defined"),l=e("./_for-of"),c=e("./_iter-define"),f=e("./_iter-step"),d=e("./_set-species"),h=e("./_descriptors"),p=e("./_meta").fastKey,_=h?"_s":"size",v=function(e,t){var r,n=p(t);if("F"!==n)return e._i[n];for(r=e._f;r;r=r.n)if(r.k==t)return r};t.exports={getConstructor:function(e,t,r,c){var f=e(function(e,n){s(e,f,t,"_i"),e._i=o(null),e._f=void 0,e._l=void 0,e[_]=0,void 0!=n&&l(n,r,e[c],e)});return a(f.prototype,{clear:function(){for(var e=this,t=e._i,r=e._f;r;r=r.n)r.r=!0,r.p&&(r.p=r.p.n=void 0),delete t[r.i];e._f=e._l=void 0,e[_]=0},"delete":function(e){var t=this,r=v(t,e);if(r){var n=r.n,o=r.p;delete t._i[r.i],r.r=!0,o&&(o.n=n),n&&(n.p=o),t._f==r&&(t._f=n),t._l==r&&(t._l=o),t[_]--}return!!r},forEach:function(e){s(this,f,"forEach");for(var t,r=i(e,arguments.length>1?arguments[1]:void 0,3);t=t?t.n:this._f;)for(r(t.v,t.k,this);t&&t.r;)t=t.p},has:function(e){return!!v(this,e)}}),h&&n(f.prototype,"size",{get:function(){return u(this[_])}}),f},def:function(e,t,r){var n,o,a=v(e,t);return a?a.v=r:(e._l=a={i:o=p(t,!0),k:t,v:r,p:n=e._l,n:void 0,r:!1},e._f||(e._f=a),n&&(n.n=a),e[_]++,"F"!==o&&(e._i[o]=a)),e},getEntry:v,setStrong:function(e,t,r){c(e,t,function(e,t){this._t=e,this._k=t,this._l=void 0},function(){for(var e=this,t=e._k,r=e._l;r&&r.r;)r=r.p;return e._t&&(e._l=r=r?r.n:e._t._f)?"keys"==t?f(0,r.k):"values"==t?f(0,r.v):f(0,[r.k,r.v]):(e._t=void 0,f(1))},r?"entries":"values",!r,!0),d(t)}}},{"./_an-instance":77,"./_ctx":91,"./_defined":92,"./_descriptors":93,"./_for-of":99,"./_iter-define":111,"./_iter-step":112,"./_meta":116,"./_object-create":118,"./_object-dp":119,"./_redefine-all":131,"./_set-species":134}],87:[function(e,t,r){var n=e("./_classof"),o=e("./_array-from-iterable");t.exports=function(e){return function(){if(n(this)!=e)throw TypeError(e+"#toJSON isn't generic");return o(this)}}},{"./_array-from-iterable":79,"./_classof":84}],88:[function(e,t,r){"use strict";var n=e("./_redefine-all"),o=e("./_meta").getWeak,a=e("./_an-object"),i=e("./_is-object"),s=e("./_an-instance"),u=e("./_for-of"),l=e("./_array-methods"),c=e("./_has"),f=l(5),d=l(6),h=0,p=function(e){return e._l||(e._l=new _)},_=function(){this.a=[]},v=function(e,t){return f(e.a,function(e){return e[0]===t})};_.prototype={get:function(e){var t=v(this,e);if(t)return t[1]},has:function(e){return!!v(this,e)},set:function(e,t){var r=v(this,e);r?r[1]=t:this.a.push([e,t])},"delete":function(e){var t=d(this.a,function(t){return t[0]===e});return~t&&this.a.splice(t,1),!!~t}},t.exports={getConstructor:function(e,t,r,a){var l=e(function(e,n){s(e,l,t,"_i"),e._i=h++,e._l=void 0,void 0!=n&&u(n,r,e[a],e)});return n(l.prototype,{"delete":function(e){if(!i(e))return!1;var t=o(e);return t===!0?p(this)["delete"](e):t&&c(t,this._i)&&delete t[this._i]},has:function(e){if(!i(e))return!1;var t=o(e);return t===!0?p(this).has(e):t&&c(t,this._i)}}),l},def:function(e,t,r){var n=o(a(t),!0);return n===!0?p(e).set(t,r):n[e._i]=r,e},ufstore:p}},{"./_an-instance":77,"./_an-object":78,"./_array-methods":81,"./_for-of":99,"./_has":101,"./_is-object":108,"./_meta":116,"./_redefine-all":131}],89:[function(e,t,r){"use strict";var n=e("./_global"),o=e("./_export"),a=e("./_meta"),i=e("./_fails"),s=e("./_hide"),u=e("./_redefine-all"),l=e("./_for-of"),c=e("./_an-instance"),f=e("./_is-object"),d=e("./_set-to-string-tag"),h=e("./_object-dp").f,p=e("./_array-methods")(0),_=e("./_descriptors");t.exports=function(e,t,r,v,y,b){var g=n[e],m=g,C=y?"set":"add",j=m&&m.prototype,k={};return _&&"function"==typeof m&&(b||j.forEach&&!i(function(){(new m).entries().next()}))?(m=t(function(t,r){c(t,m,e,"_c"),t._c=new g,void 0!=r&&l(r,y,t[C],t)}),p("add,clear,delete,forEach,get,has,set,keys,values,entries,toJSON".split(","),function(e){var t="add"==e||"set"==e;e in j&&(!b||"clear"!=e)&&s(m.prototype,e,function(r,n){if(c(this,m,e),!t&&b&&!f(r))return"get"==e&&void 0;var o=this._c[e](0===r?0:r,n);return t?this:o})}),"size"in j&&h(m.prototype,"size",{get:function(){return this._c.size}})):(m=v.getConstructor(t,e,y,C),u(m.prototype,r),a.NEED=!0),d(m,e),k[e]=m,o(o.G+o.W+o.F,k),b||v.setStrong(m,e,y),m}},{"./_an-instance":77,"./_array-methods":81,"./_descriptors":93,"./_export":97,"./_fails":98,"./_for-of":99,"./_global":100,"./_hide":102,"./_is-object":108,"./_meta":116,"./_object-dp":119,"./_redefine-all":131,"./_set-to-string-tag":135}],90:[function(e,t,r){var n=t.exports={version:"2.4.0"};"number"==typeof __e&&(__e=n)},{}],91:[function(e,t,r){var n=e("./_a-function");t.exports=function(e,t,r){if(n(e),void 0===t)return e;switch(r){case 1:return function(r){return e.call(t,r)};case 2:return function(r,n){return e.call(t,r,n)};case 3:return function(r,n,o){return e.call(t,r,n,o)}}return function(){return e.apply(t,arguments)}}},{"./_a-function":75}],92:[function(e,t,r){t.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},{}],93:[function(e,t,r){t.exports=!e("./_fails")(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},{"./_fails":98}],94:[function(e,t,r){var n=e("./_is-object"),o=e("./_global").document,a=n(o)&&n(o.createElement);t.exports=function(e){return a?o.createElement(e):{}}},{"./_global":100,"./_is-object":108}],95:[function(e,t,r){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},{}],96:[function(e,t,r){var n=e("./_object-keys"),o=e("./_object-gops"),a=e("./_object-pie");t.exports=function(e){var t=n(e),r=o.f;if(r)for(var i,s=r(e),u=a.f,l=0;s.length>l;)u.call(e,i=s[l++])&&t.push(i);return t}},{"./_object-gops":124,"./_object-keys":127,"./_object-pie":128}],97:[function(e,t,r){var n=e("./_global"),o=e("./_core"),a=e("./_ctx"),i=e("./_hide"),s="prototype",u=function(e,t,r){var l,c,f,d=e&u.F,h=e&u.G,p=e&u.S,_=e&u.P,v=e&u.B,y=e&u.W,b=h?o:o[t]||(o[t]={}),g=b[s],m=h?n:p?n[t]:(n[t]||{})[s];h&&(r=t);for(l in r)c=!d&&m&&void 0!==m[l],c&&l in b||(f=c?m[l]:r[l],b[l]=h&&"function"!=typeof m[l]?r[l]:v&&c?a(f,n):y&&m[l]==f?function(e){var t=function(t,r,n){if(this instanceof e){switch(arguments.length){case 0:return new e;case 1:return new e(t);case 2:return new e(t,r)}return new e(t,r,n)}return e.apply(this,arguments)};return t[s]=e[s],t}(f):_&&"function"==typeof f?a(Function.call,f):f,_&&((b.virtual||(b.virtual={}))[l]=f,e&u.R&&g&&!g[l]&&i(g,l,f)))};u.F=1,u.G=2,u.S=4,u.P=8,u.B=16,u.W=32,u.U=64,u.R=128,t.exports=u},{"./_core":90,"./_ctx":91,"./_global":100,"./_hide":102}],98:[function(e,t,r){t.exports=function(e){try{return!!e()}catch(t){return!0}}},{}],99:[function(e,t,r){var n=e("./_ctx"),o=e("./_iter-call"),a=e("./_is-array-iter"),i=e("./_an-object"),s=e("./_to-length"),u=e("./core.get-iterator-method"),l={},c={},r=t.exports=function(e,t,r,f,d){var h,p,_,v,y=d?function(){return e}:u(e),b=n(r,f,t?2:1),g=0;if("function"!=typeof y)throw TypeError(e+" is not iterable!");if(a(y)){for(h=s(e.length);h>g;g++)if(v=t?b(i(p=e[g])[0],p[1]):b(e[g]),v===l||v===c)return v}else for(_=y.call(e);!(p=_.next()).done;)if(v=o(_,b,p.value,t),v===l||v===c)return v};r.BREAK=l,r.RETURN=c},{"./_an-object":78,"./_ctx":91,"./_is-array-iter":106,"./_iter-call":109,"./_to-length":142,"./core.get-iterator-method":149}],100:[function(e,t,r){var n=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},{}],101:[function(e,t,r){var n={}.hasOwnProperty;t.exports=function(e,t){return n.call(e,t)}},{}],102:[function(e,t,r){var n=e("./_object-dp"),o=e("./_property-desc");t.exports=e("./_descriptors")?function(e,t,r){return n.f(e,t,o(1,r))}:function(e,t,r){return e[t]=r,e}},{"./_descriptors":93,"./_object-dp":119,"./_property-desc":130}],103:[function(e,t,r){t.exports=e("./_global").document&&document.documentElement},{"./_global":100}],104:[function(e,t,r){t.exports=!e("./_descriptors")&&!e("./_fails")(function(){return 7!=Object.defineProperty(e("./_dom-create")("div"),"a",{get:function(){return 7}}).a})},{"./_descriptors":93,"./_dom-create":94,"./_fails":98}],105:[function(e,t,r){var n=e("./_cof");t.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==n(e)?e.split(""):Object(e)}},{"./_cof":85}],106:[function(e,t,r){var n=e("./_iterators"),o=e("./_wks")("iterator"),a=Array.prototype;t.exports=function(e){return void 0!==e&&(n.Array===e||a[o]===e)}},{"./_iterators":113,"./_wks":148}],107:[function(e,t,r){var n=e("./_cof");t.exports=Array.isArray||function(e){return"Array"==n(e)}},{"./_cof":85}],108:[function(e,t,r){t.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},{}],109:[function(e,t,r){var n=e("./_an-object");t.exports=function(e,t,r,o){try{return o?t(n(r)[0],r[1]):t(r)}catch(a){var i=e["return"];throw void 0!==i&&n(i.call(e)),a}}},{"./_an-object":78}],110:[function(e,t,r){"use strict";var n=e("./_object-create"),o=e("./_property-desc"),a=e("./_set-to-string-tag"),i={};e("./_hide")(i,e("./_wks")("iterator"),function(){return this}),t.exports=function(e,t,r){e.prototype=n(i,{next:o(1,r)}),a(e,t+" Iterator")}},{"./_hide":102,"./_object-create":118,"./_property-desc":130,"./_set-to-string-tag":135,"./_wks":148}],111:[function(e,t,r){"use strict";var n=e("./_library"),o=e("./_export"),a=e("./_redefine"),i=e("./_hide"),s=e("./_has"),u=e("./_iterators"),l=e("./_iter-create"),c=e("./_set-to-string-tag"),f=e("./_object-gpo"),d=e("./_wks")("iterator"),h=!([].keys&&"next"in[].keys()),p="@@iterator",_="keys",v="values",y=function(){return this};t.exports=function(e,t,r,b,g,m,C){l(r,t,b);var j,k,O,w=function(e){if(!h&&e in A)return A[e];switch(e){case _:return function(){return new r(this,e)};case v:return function(){return new r(this,e)}}return function(){return new r(this,e)}},S=t+" Iterator",E=g==v,P=!1,A=e.prototype,I=A[d]||A[p]||g&&A[g],T=I||w(g),N=g?E?w("entries"):T:void 0,R="Array"==t?A.entries||I:I;if(R&&(O=f(R.call(new e)),O!==Object.prototype&&(c(O,S,!0),n||s(O,d)||i(O,d,y))),E&&I&&I.name!==v&&(P=!0,T=function(){return I.call(this)}),n&&!C||!h&&!P&&A[d]||i(A,d,T),u[t]=T,u[S]=y,g)if(j={values:E?T:w(v),keys:m?T:w(_),entries:N},C)for(k in j)k in A||a(A,k,j[k]);else o(o.P+o.F*(h||P),t,j);return j}},{"./_export":97,"./_has":101,"./_hide":102,"./_iter-create":110,"./_iterators":113,"./_library":115,"./_object-gpo":125,"./_redefine":132,"./_set-to-string-tag":135,"./_wks":148}],112:[function(e,t,r){t.exports=function(e,t){return{value:t,done:!!e}}},{}],113:[function(e,t,r){t.exports={}},{}],114:[function(e,t,r){var n=e("./_object-keys"),o=e("./_to-iobject");t.exports=function(e,t){for(var r,a=o(e),i=n(a),s=i.length,u=0;s>u;)if(a[r=i[u++]]===t)return r}},{"./_object-keys":127,"./_to-iobject":141}],115:[function(e,t,r){t.exports=!0},{}],116:[function(e,t,r){var n=e("./_uid")("meta"),o=e("./_is-object"),a=e("./_has"),i=e("./_object-dp").f,s=0,u=Object.isExtensible||function(){return!0},l=!e("./_fails")(function(){return u(Object.preventExtensions({}))}),c=function(e){i(e,n,{value:{i:"O"+ ++s,w:{}}})},f=function(e,t){if(!o(e))return"symbol"==typeof e?e:("string"==typeof e?"S":"P")+e;if(!a(e,n)){if(!u(e))return"F";if(!t)return"E";c(e)}return e[n].i},d=function(e,t){if(!a(e,n)){if(!u(e))return!0;if(!t)return!1;c(e)}return e[n].w},h=function(e){return l&&p.NEED&&u(e)&&!a(e,n)&&c(e),e},p=t.exports={KEY:n,NEED:!1,fastKey:f,getWeak:d,onFreeze:h}},{"./_fails":98,"./_has":101,"./_is-object":108,"./_object-dp":119,"./_uid":145}],117:[function(e,t,r){"use strict";var n=e("./_object-keys"),o=e("./_object-gops"),a=e("./_object-pie"),i=e("./_to-object"),s=e("./_iobject"),u=Object.assign;t.exports=!u||e("./_fails")(function(){var e={},t={},r=Symbol(),n="abcdefghijklmnopqrst";return e[r]=7,n.split("").forEach(function(e){t[e]=e}),7!=u({},e)[r]||Object.keys(u({},t)).join("")!=n})?function(e,t){for(var r=i(e),u=arguments.length,l=1,c=o.f,f=a.f;u>l;)for(var d,h=s(arguments[l++]),p=c?n(h).concat(c(h)):n(h),_=p.length,v=0;_>v;)f.call(h,d=p[v++])&&(r[d]=h[d]);return r}:u},{"./_fails":98,"./_iobject":105,"./_object-gops":124,"./_object-keys":127,"./_object-pie":128,"./_to-object":143}],118:[function(e,t,r){var n=e("./_an-object"),o=e("./_object-dps"),a=e("./_enum-bug-keys"),i=e("./_shared-key")("IE_PROTO"),s=function(){},u="prototype",l=function(){var t,r=e("./_dom-create")("iframe"),n=a.length,o="<",i=">";for(r.style.display="none",e("./_html").appendChild(r),r.src="javascript:",t=r.contentWindow.document,t.open(),t.write(o+"script"+i+"document.F=Object"+o+"/script"+i),t.close(),l=t.F;n--;)delete l[u][a[n]];return l()};t.exports=Object.create||function(e,t){var r;return null!==e?(s[u]=n(e),r=new s,s[u]=null,r[i]=e):r=l(),void 0===t?r:o(r,t)}},{"./_an-object":78,"./_dom-create":94,"./_enum-bug-keys":95,"./_html":103,"./_object-dps":120,"./_shared-key":136}],119:[function(e,t,r){var n=e("./_an-object"),o=e("./_ie8-dom-define"),a=e("./_to-primitive"),i=Object.defineProperty;r.f=e("./_descriptors")?Object.defineProperty:function(e,t,r){if(n(e),t=a(t,!0),n(r),o)try{return i(e,t,r)}catch(s){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(e[t]=r.value),e}},{"./_an-object":78,"./_descriptors":93,"./_ie8-dom-define":104,"./_to-primitive":144}],120:[function(e,t,r){var n=e("./_object-dp"),o=e("./_an-object"),a=e("./_object-keys");t.exports=e("./_descriptors")?Object.defineProperties:function(e,t){o(e);for(var r,i=a(t),s=i.length,u=0;s>u;)n.f(e,r=i[u++],t[r]);return e}},{"./_an-object":78,"./_descriptors":93,"./_object-dp":119,"./_object-keys":127}],121:[function(e,t,r){var n=e("./_object-pie"),o=e("./_property-desc"),a=e("./_to-iobject"),i=e("./_to-primitive"),s=e("./_has"),u=e("./_ie8-dom-define"),l=Object.getOwnPropertyDescriptor;r.f=e("./_descriptors")?l:function(e,t){if(e=a(e),t=i(t,!0),u)try{return l(e,t)}catch(r){}if(s(e,t))return o(!n.f.call(e,t),e[t])}},{"./_descriptors":93,"./_has":101,"./_ie8-dom-define":104,"./_object-pie":128,"./_property-desc":130,"./_to-iobject":141,"./_to-primitive":144}],122:[function(e,t,r){var n=e("./_to-iobject"),o=e("./_object-gopn").f,a={}.toString,i="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],s=function(e){try{return o(e)}catch(t){return i.slice()}};t.exports.f=function(e){return i&&"[object Window]"==a.call(e)?s(e):o(n(e))}},{"./_object-gopn":123,"./_to-iobject":141}],123:[function(e,t,r){var n=e("./_object-keys-internal"),o=e("./_enum-bug-keys").concat("length","prototype");r.f=Object.getOwnPropertyNames||function(e){return n(e,o)}},{"./_enum-bug-keys":95,"./_object-keys-internal":126}],124:[function(e,t,r){r.f=Object.getOwnPropertySymbols},{}],125:[function(e,t,r){var n=e("./_has"),o=e("./_to-object"),a=e("./_shared-key")("IE_PROTO"),i=Object.prototype;t.exports=Object.getPrototypeOf||function(e){return e=o(e),n(e,a)?e[a]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?i:null}},{"./_has":101,"./_shared-key":136,"./_to-object":143}],126:[function(e,t,r){var n=e("./_has"),o=e("./_to-iobject"),a=e("./_array-includes")(!1),i=e("./_shared-key")("IE_PROTO");t.exports=function(e,t){var r,s=o(e),u=0,l=[];for(r in s)r!=i&&n(s,r)&&l.push(r);for(;t.length>u;)n(s,r=t[u++])&&(~a(l,r)||l.push(r));return l}},{"./_array-includes":80,"./_has":101,"./_shared-key":136,"./_to-iobject":141}],127:[function(e,t,r){var n=e("./_object-keys-internal"),o=e("./_enum-bug-keys");t.exports=Object.keys||function(e){return n(e,o)}},{"./_enum-bug-keys":95,"./_object-keys-internal":126}],128:[function(e,t,r){r.f={}.propertyIsEnumerable},{}],129:[function(e,t,r){var n=e("./_export"),o=e("./_core"),a=e("./_fails");t.exports=function(e,t){var r=(o.Object||{})[e]||Object[e],i={};i[e]=t(r),n(n.S+n.F*a(function(){r(1)}),"Object",i)}},{"./_core":90,"./_export":97,"./_fails":98}],130:[function(e,t,r){t.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},{}],131:[function(e,t,r){var n=e("./_hide");t.exports=function(e,t,r){for(var o in t)r&&e[o]?e[o]=t[o]:n(e,o,t[o]);return e}},{"./_hide":102}],132:[function(e,t,r){t.exports=e("./_hide")},{"./_hide":102}],133:[function(e,t,r){var n=e("./_is-object"),o=e("./_an-object"),a=function(e,t){if(o(e),!n(t)&&null!==t)throw TypeError(t+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,r,n){try{n=e("./_ctx")(Function.call,e("./_object-gopd").f(Object.prototype,"__proto__").set,2),n(t,[]),r=!(t instanceof Array)}catch(o){r=!0}return function(e,t){return a(e,t),r?e.__proto__=t:n(e,t),e}}({},!1):void 0),check:a}},{"./_an-object":78,"./_ctx":91,"./_is-object":108,"./_object-gopd":121}],134:[function(e,t,r){"use strict";var n=e("./_global"),o=e("./_core"),a=e("./_object-dp"),i=e("./_descriptors"),s=e("./_wks")("species");t.exports=function(e){var t="function"==typeof o[e]?o[e]:n[e];i&&t&&!t[s]&&a.f(t,s,{configurable:!0,get:function(){return this}})}},{"./_core":90,"./_descriptors":93,"./_global":100,"./_object-dp":119,"./_wks":148}],135:[function(e,t,r){var n=e("./_object-dp").f,o=e("./_has"),a=e("./_wks")("toStringTag");t.exports=function(e,t,r){e&&!o(e=r?e:e.prototype,a)&&n(e,a,{configurable:!0,value:t})}},{"./_has":101,"./_object-dp":119,"./_wks":148}],136:[function(e,t,r){var n=e("./_shared")("keys"),o=e("./_uid");t.exports=function(e){return n[e]||(n[e]=o(e))}},{"./_shared":137,"./_uid":145}],137:[function(e,t,r){var n=e("./_global"),o="__core-js_shared__",a=n[o]||(n[o]={});t.exports=function(e){return a[e]||(a[e]={})}},{"./_global":100}],138:[function(e,t,r){var n=e("./_to-integer"),o=e("./_defined");t.exports=function(e){return function(t,r){var a,i,s=String(o(t)),u=n(r),l=s.length;return u<0||u>=l?e?"":void 0:(a=s.charCodeAt(u),a<55296||a>56319||u+1===l||(i=s.charCodeAt(u+1))<56320||i>57343?e?s.charAt(u):a:e?s.slice(u,u+2):(a-55296<<10)+(i-56320)+65536)}}},{"./_defined":92,"./_to-integer":140}],139:[function(e,t,r){var n=e("./_to-integer"),o=Math.max,a=Math.min;t.exports=function(e,t){return e=n(e),e<0?o(e+t,0):a(e,t)}},{"./_to-integer":140}],140:[function(e,t,r){var n=Math.ceil,o=Math.floor;t.exports=function(e){return isNaN(e=+e)?0:(e>0?o:n)(e)}},{}],141:[function(e,t,r){var n=e("./_iobject"),o=e("./_defined");t.exports=function(e){return n(o(e))}},{"./_defined":92,"./_iobject":105}],142:[function(e,t,r){var n=e("./_to-integer"),o=Math.min;t.exports=function(e){return e>0?o(n(e),9007199254740991):0}},{"./_to-integer":140}],143:[function(e,t,r){var n=e("./_defined");t.exports=function(e){return Object(n(e))}},{"./_defined":92}],144:[function(e,t,r){var n=e("./_is-object");t.exports=function(e,t){if(!n(e))return e;var r,o;if(t&&"function"==typeof(r=e.toString)&&!n(o=r.call(e)))return o;if("function"==typeof(r=e.valueOf)&&!n(o=r.call(e)))return o;if(!t&&"function"==typeof(r=e.toString)&&!n(o=r.call(e)))return o;throw TypeError("Can't convert object to primitive value")}},{"./_is-object":108}],145:[function(e,t,r){var n=0,o=Math.random();t.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+o).toString(36))}},{}],146:[function(e,t,r){var n=e("./_global"),o=e("./_core"),a=e("./_library"),i=e("./_wks-ext"),s=e("./_object-dp").f;t.exports=function(e){var t=o.Symbol||(o.Symbol=a?{}:n.Symbol||{});"_"==e.charAt(0)||e in t||s(t,e,{value:i.f(e)})}},{"./_core":90,"./_global":100,"./_library":115,"./_object-dp":119,"./_wks-ext":147}],147:[function(e,t,r){r.f=e("./_wks")},{"./_wks":148}],148:[function(e,t,r){var n=e("./_shared")("wks"),o=e("./_uid"),a=e("./_global").Symbol,i="function"==typeof a,s=t.exports=function(e){return n[e]||(n[e]=i&&a[e]||(i?a:o)("Symbol."+e))};s.store=n},{"./_global":100,"./_shared":137,"./_uid":145}],149:[function(e,t,r){var n=e("./_classof"),o=e("./_wks")("iterator"),a=e("./_iterators");t.exports=e("./_core").getIteratorMethod=function(e){if(void 0!=e)return e[o]||e["@@iterator"]||a[n(e)]}},{"./_classof":84,"./_core":90,"./_iterators":113,"./_wks":148}],150:[function(e,t,r){var n=e("./_an-object"),o=e("./core.get-iterator-method");t.exports=e("./_core").getIterator=function(e){var t=o(e);if("function"!=typeof t)throw TypeError(e+" is not iterable!");return n(t.call(e))}},{"./_an-object":78,"./_core":90,"./core.get-iterator-method":149}],151:[function(e,t,r){"use strict";var n=e("./_add-to-unscopables"),o=e("./_iter-step"),a=e("./_iterators"),i=e("./_to-iobject");t.exports=e("./_iter-define")(Array,"Array",function(e,t){this._t=i(e),this._i=0,this._k=t},function(){var e=this._t,t=this._k,r=this._i++;return!e||r>=e.length?(this._t=void 0,o(1)):"keys"==t?o(0,r):"values"==t?o(0,e[r]):o(0,[r,e[r]])},"values"),a.Arguments=a.Array,n("keys"),n("values"),n("entries")},{"./_add-to-unscopables":76,"./_iter-define":111,"./_iter-step":112,"./_iterators":113,"./_to-iobject":141}],152:[function(e,t,r){"use strict";var n=e("./_collection-strong");t.exports=e("./_collection")("Map",function(e){return function(){return e(this,arguments.length>0?arguments[0]:void 0)}},{get:function(e){var t=n.getEntry(this,e);return t&&t.v},set:function(e,t){return n.def(this,0===e?0:e,t)}},n,!0)},{"./_collection":89,"./_collection-strong":86}],153:[function(e,t,r){var n=e("./_export");n(n.S,"Object",{create:e("./_object-create")})},{"./_export":97,"./_object-create":118}],154:[function(e,t,r){var n=e("./_export");n(n.S+n.F*!e("./_descriptors"),"Object",{defineProperty:e("./_object-dp").f})},{"./_descriptors":93,"./_export":97,"./_object-dp":119}],155:[function(e,t,r){var n=e("./_is-object"),o=e("./_meta").onFreeze;e("./_object-sap")("freeze",function(e){return function(t){return e&&n(t)?e(o(t)):t}})},{"./_is-object":108,"./_meta":116,"./_object-sap":129}],156:[function(e,t,r){var n=e("./_to-iobject"),o=e("./_object-gopd").f;e("./_object-sap")("getOwnPropertyDescriptor",function(){return function(e,t){return o(n(e),t)}})},{"./_object-gopd":121,"./_object-sap":129,"./_to-iobject":141}],157:[function(e,t,r){var n=e("./_to-object"),o=e("./_object-gpo");e("./_object-sap")("getPrototypeOf",function(){return function(e){return o(n(e))}})},{"./_object-gpo":125,"./_object-sap":129,"./_to-object":143}],158:[function(e,t,r){var n=e("./_to-object"),o=e("./_object-keys");e("./_object-sap")("keys",function(){return function(e){return o(n(e))}})},{"./_object-keys":127,"./_object-sap":129,"./_to-object":143}],159:[function(e,t,r){var n=e("./_export");n(n.S,"Object",{setPrototypeOf:e("./_set-proto").set})},{"./_export":97,"./_set-proto":133}],160:[function(e,t,r){},{}],161:[function(e,t,r){"use strict";var n=e("./_string-at")(!0);e("./_iter-define")(String,"String",function(e){this._t=String(e),this._i=0},function(){var e,t=this._t,r=this._i;return r>=t.length?{value:void 0,done:!0}:(e=n(t,r),this._i+=e.length,{value:e,done:!1})})},{"./_iter-define":111,"./_string-at":138}],162:[function(e,t,r){"use strict";var n=e("./_global"),o=e("./_has"),a=e("./_descriptors"),i=e("./_export"),s=e("./_redefine"),u=e("./_meta").KEY,l=e("./_fails"),c=e("./_shared"),f=e("./_set-to-string-tag"),d=e("./_uid"),h=e("./_wks"),p=e("./_wks-ext"),_=e("./_wks-define"),v=e("./_keyof"),y=e("./_enum-keys"),b=e("./_is-array"),g=e("./_an-object"),m=e("./_to-iobject"),C=e("./_to-primitive"),j=e("./_property-desc"),k=e("./_object-create"),O=e("./_object-gopn-ext"),w=e("./_object-gopd"),S=e("./_object-dp"),E=e("./_object-keys"),P=w.f,A=S.f,I=O.f,T=n.Symbol,N=n.JSON,R=N&&N.stringify,M="prototype",x=h("_hidden"),D=h("toPrimitive"),L={}.propertyIsEnumerable,U=c("symbol-registry"),F=c("symbols"),K=c("op-symbols"),q=Object[M],J="function"==typeof T,W=n.QObject,Q=!W||!W[M]||!W[M].findChild,B=a&&l(function(){return 7!=k(A({},"a",{get:function(){return A(this,"a",{value:7}).a}})).a})?function(e,t,r){var n=P(q,t);n&&delete q[t],A(e,t,r),n&&e!==q&&A(q,t,n)}:A,V=function(e){var t=F[e]=k(T[M]);return t._k=e,t},G=J&&"symbol"==typeof T.iterator?function(e){return"symbol"==typeof e}:function(e){return e instanceof T},z=function(e,t,r){return e===q&&z(K,t,r),g(e),t=C(t,!0),g(r),o(F,t)?(r.enumerable?(o(e,x)&&e[x][t]&&(e[x][t]=!1),r=k(r,{enumerable:j(0,!1)})):(o(e,x)||A(e,x,j(1,{})),e[x][t]=!0),B(e,t,r)):A(e,t,r)},Y=function(e,t){g(e);for(var r,n=y(t=m(t)),o=0,a=n.length;a>o;)z(e,r=n[o++],t[r]);return e},H=function(e,t){return void 0===t?k(e):Y(k(e),t); +},$=function(e){var t=L.call(this,e=C(e,!0));return!(this===q&&o(F,e)&&!o(K,e))&&(!(t||!o(this,e)||!o(F,e)||o(this,x)&&this[x][e])||t)},X=function(e,t){if(e=m(e),t=C(t,!0),e!==q||!o(F,t)||o(K,t)){var r=P(e,t);return!r||!o(F,t)||o(e,x)&&e[x][t]||(r.enumerable=!0),r}},Z=function(e){for(var t,r=I(m(e)),n=[],a=0;r.length>a;)o(F,t=r[a++])||t==x||t==u||n.push(t);return n},ee=function(e){for(var t,r=e===q,n=I(r?K:m(e)),a=[],i=0;n.length>i;)!o(F,t=n[i++])||r&&!o(q,t)||a.push(F[t]);return a};J||(T=function(){if(this instanceof T)throw TypeError("Symbol is not a constructor!");var e=d(arguments.length>0?arguments[0]:void 0),t=function(r){this===q&&t.call(K,r),o(this,x)&&o(this[x],e)&&(this[x][e]=!1),B(this,e,j(1,r))};return a&&Q&&B(q,e,{configurable:!0,set:t}),V(e)},s(T[M],"toString",function(){return this._k}),w.f=X,S.f=z,e("./_object-gopn").f=O.f=Z,e("./_object-pie").f=$,e("./_object-gops").f=ee,a&&!e("./_library")&&s(q,"propertyIsEnumerable",$,!0),p.f=function(e){return V(h(e))}),i(i.G+i.W+i.F*!J,{Symbol:T});for(var te="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),re=0;te.length>re;)h(te[re++]);for(var te=E(h.store),re=0;te.length>re;)_(te[re++]);i(i.S+i.F*!J,"Symbol",{"for":function(e){return o(U,e+="")?U[e]:U[e]=T(e)},keyFor:function(e){if(G(e))return v(U,e);throw TypeError(e+" is not a symbol!")},useSetter:function(){Q=!0},useSimple:function(){Q=!1}}),i(i.S+i.F*!J,"Object",{create:H,defineProperty:z,defineProperties:Y,getOwnPropertyDescriptor:X,getOwnPropertyNames:Z,getOwnPropertySymbols:ee}),N&&i(i.S+i.F*(!J||l(function(){var e=T();return"[null]"!=R([e])||"{}"!=R({a:e})||"{}"!=R(Object(e))})),"JSON",{stringify:function(e){if(void 0!==e&&!G(e)){for(var t,r,n=[e],o=1;arguments.length>o;)n.push(arguments[o++]);return t=n[1],"function"==typeof t&&(r=t),!r&&b(t)||(t=function(e,t){if(r&&(t=r.call(this,e,t)),!G(t))return t}),n[1]=t,R.apply(N,n)}}}),T[M][D]||e("./_hide")(T[M],D,T[M].valueOf),f(T,"Symbol"),f(Math,"Math",!0),f(n.JSON,"JSON",!0)},{"./_an-object":78,"./_descriptors":93,"./_enum-keys":96,"./_export":97,"./_fails":98,"./_global":100,"./_has":101,"./_hide":102,"./_is-array":107,"./_keyof":114,"./_library":115,"./_meta":116,"./_object-create":118,"./_object-dp":119,"./_object-gopd":121,"./_object-gopn":123,"./_object-gopn-ext":122,"./_object-gops":124,"./_object-keys":127,"./_object-pie":128,"./_property-desc":130,"./_redefine":132,"./_set-to-string-tag":135,"./_shared":137,"./_to-iobject":141,"./_to-primitive":144,"./_uid":145,"./_wks":148,"./_wks-define":146,"./_wks-ext":147}],163:[function(e,t,r){"use strict";var n,o=e("./_array-methods")(0),a=e("./_redefine"),i=e("./_meta"),s=e("./_object-assign"),u=e("./_collection-weak"),l=e("./_is-object"),c=i.getWeak,f=Object.isExtensible,d=u.ufstore,h={},p=function(e){return function(){return e(this,arguments.length>0?arguments[0]:void 0)}},_={get:function(e){if(l(e)){var t=c(e);return t===!0?d(this).get(e):t?t[this._i]:void 0}},set:function(e,t){return u.def(this,e,t)}},v=t.exports=e("./_collection")("WeakMap",p,_,u,!0,!0);7!=(new v).set((Object.freeze||Object)(h),7).get(h)&&(n=u.getConstructor(p),s(n.prototype,_),i.NEED=!0,o(["delete","has","get","set"],function(e){var t=v.prototype,r=t[e];a(t,e,function(t,o){if(l(t)&&!f(t)){this._f||(this._f=new n);var a=this._f[e](t,o);return"set"==e?this:a}return r.call(this,t,o)})}))},{"./_array-methods":81,"./_collection":89,"./_collection-weak":88,"./_is-object":108,"./_meta":116,"./_object-assign":117,"./_redefine":132}],164:[function(e,t,r){var n=e("./_export");n(n.P+n.R,"Map",{toJSON:e("./_collection-to-json")("Map")})},{"./_collection-to-json":87,"./_export":97}],165:[function(e,t,r){e("./_wks-define")("asyncIterator")},{"./_wks-define":146}],166:[function(e,t,r){e("./_wks-define")("observable")},{"./_wks-define":146}],167:[function(e,t,r){e("./es6.array.iterator");for(var n=e("./_global"),o=e("./_hide"),a=e("./_iterators"),i=e("./_wks")("toStringTag"),s=["NodeList","DOMTokenList","MediaList","StyleSheetList","CSSRuleList"],u=0;u<5;u++){var l=s[u],c=n[l],f=c&&c.prototype;f&&!f[i]&&o(f,i,l),a[l]=a.Array}},{"./_global":100,"./_hide":102,"./_iterators":113,"./_wks":148,"./es6.array.iterator":151}],168:[function(e,t,r){arguments[4][160][0].apply(r,arguments)},{dup:160}],169:[function(e,t,r){function n(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function o(e){return"function"==typeof e}function a(e){return"number"==typeof e}function i(e){return"object"==typeof e&&null!==e}function s(e){return void 0===e}t.exports=n,n.EventEmitter=n,n.prototype._events=void 0,n.prototype._maxListeners=void 0,n.defaultMaxListeners=10,n.prototype.setMaxListeners=function(e){if(!a(e)||e<0||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},n.prototype.emit=function(e){var t,r,n,a,u,l;if(this._events||(this._events={}),"error"===e&&(!this._events.error||i(this._events.error)&&!this._events.error.length)){if(t=arguments[1],t instanceof Error)throw t;throw TypeError('Uncaught, unspecified "error" event.')}if(r=this._events[e],s(r))return!1;if(o(r))switch(arguments.length){case 1:r.call(this);break;case 2:r.call(this,arguments[1]);break;case 3:r.call(this,arguments[1],arguments[2]);break;default:for(n=arguments.length,a=new Array(n-1),u=1;u0&&this._events[e].length>r&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace())}return this},n.prototype.on=n.prototype.addListener,n.prototype.once=function(e,t){function r(){this.removeListener(e,r),n||(n=!0,t.apply(this,arguments))}if(!o(t))throw TypeError("listener must be a function");var n=!1;return r.listener=t,this.on(e,r),this},n.prototype.removeListener=function(e,t){var r,n,a,s;if(!o(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(r=this._events[e],a=r.length,n=-1,r===t||o(r.listener)&&r.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(i(r)){for(s=a;s-- >0;)if(r[s]===t||r[s].listener&&r[s].listener===t){n=s;break}if(n<0)return this;1===r.length?(r.length=0,delete this._events[e]):r.splice(n,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},n.prototype.removeAllListeners=function(e){var t,r;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(r=this._events[e],o(r))this.removeListener(e,r);else for(;r.length;)this.removeListener(e,r[r.length-1]);return delete this._events[e],this},n.prototype.listeners=function(e){var t;return t=this._events&&this._events[e]?o(this._events[e])?[this._events[e]]:this._events[e].slice():[]},n.listenerCount=function(e,t){var r;return r=e._events&&e._events[t]?o(e._events[t])?1:e._events[t].length:0}},{}]},{},[10])(10)}); \ No newline at end of file diff --git a/lib/browser/Analytics.js b/lib/browser/Analytics.js new file mode 100644 index 000000000..e5bddf088 --- /dev/null +++ b/lib/browser/Analytics.js @@ -0,0 +1,89 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.track = track; + +var _CoreManager = require('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Parse.Analytics provides an interface to Parse's logging and analytics + * backend. + * + * @class Parse.Analytics + * @static + */ + +/** + * Tracks the occurrence of a custom event with additional dimensions. + * Parse will store a data point at the time of invocation with the given + * event name. + * + * Dimensions will allow segmentation of the occurrences of this custom + * event. Keys and values should be {@code String}s, and will throw + * otherwise. + * + * To track a user signup along with additional metadata, consider the + * following: + *
      + * 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.

      + */ + +var ParseACL = function () { + function ParseACL(arg1) { + (0, _classCallCheck3.default)(this, ParseACL); + + this.permissionsById = {}; + if (arg1 && (typeof arg1 === 'undefined' ? 'undefined' : (0, _typeof3.default)(arg1)) === 'object') { + if (arg1 instanceof _ParseUser2.default) { + this.setReadAccess(arg1, true); + this.setWriteAccess(arg1, true); + } else { + for (var userId in arg1) { + var accessList = arg1[userId]; + if (typeof userId !== 'string') { + throw new TypeError('Tried to create an ACL with an invalid user id.'); + } + this.permissionsById[userId] = {}; + for (var permission in accessList) { + var allowed = accessList[permission]; + if (permission !== 'read' && permission !== 'write') { + throw new TypeError('Tried to create an ACL with an invalid permission type.'); + } + if (typeof allowed !== 'boolean') { + throw new TypeError('Tried to create an ACL with an invalid permission value.'); + } + this.permissionsById[userId][permission] = allowed; + } + } + } + } else if (typeof arg1 === 'function') { + throw new TypeError('ParseACL constructed with a function. Did you forget ()?'); + } + } + + /** + * Returns a JSON-encoded version of the ACL. + * @method toJSON + * @return {Object} + */ + + (0, _createClass3.default)(ParseACL, [{ + key: 'toJSON', + value: function () { + var permissions = {}; + for (var p in this.permissionsById) { + permissions[p] = this.permissionsById[p]; + } + return permissions; + } + + /** + * Returns whether this ACL is equal to another object + * @method equals + * @param other The other object to compare to + * @return {Boolean} + */ + + }, { + key: 'equals', + value: function (other) { + if (!(other instanceof ParseACL)) { + return false; + } + var users = (0, _keys2.default)(this.permissionsById); + var otherUsers = (0, _keys2.default)(other.permissionsById); + if (users.length !== otherUsers.length) { + return false; + } + for (var u in this.permissionsById) { + if (!other.permissionsById[u]) { + return false; + } + if (this.permissionsById[u].read !== other.permissionsById[u].read) { + return false; + } + if (this.permissionsById[u].write !== other.permissionsById[u].write) { + return false; + } + } + return true; + } + }, { + key: '_setAccess', + value: function (accessType, userId, allowed) { + if (userId instanceof _ParseUser2.default) { + userId = userId.id; + } else if (userId instanceof _ParseRole2.default) { + var name = userId.getName(); + if (!name) { + throw new TypeError('Role must have a name'); + } + userId = 'role:' + name; + } + if (typeof userId !== 'string') { + throw new TypeError('userId must be a string.'); + } + if (typeof allowed !== 'boolean') { + throw new TypeError('allowed must be either true or false.'); + } + var permissions = this.permissionsById[userId]; + if (!permissions) { + if (!allowed) { + // The user already doesn't have this permission, so no action is needed + return; + } else { + permissions = {}; + this.permissionsById[userId] = permissions; + } + } + + if (allowed) { + this.permissionsById[userId][accessType] = true; + } else { + delete permissions[accessType]; + if ((0, _keys2.default)(permissions).length === 0) { + delete this.permissionsById[userId]; + } + } + } + }, { + key: '_getAccess', + value: function (accessType, userId) { + if (userId instanceof _ParseUser2.default) { + userId = userId.id; + if (!userId) { + throw new Error('Cannot get access for a ParseUser without an ID'); + } + } else if (userId instanceof _ParseRole2.default) { + var name = userId.getName(); + if (!name) { + throw new TypeError('Role must have a name'); + } + userId = 'role:' + name; + } + var permissions = this.permissionsById[userId]; + if (!permissions) { + return false; + } + return !!permissions[accessType]; + } + + /** + * Sets whether the given user is allowed to read this object. + * @method setReadAccess + * @param userId An instance of Parse.User or its objectId. + * @param {Boolean} allowed Whether that user should have read access. + */ + + }, { + key: 'setReadAccess', + value: function (userId, allowed) { + this._setAccess('read', userId, allowed); + } + + /** + * Get whether the given user id is *explicitly* allowed to read this object. + * Even if this returns false, the user may still be able to access it if + * getPublicReadAccess returns true or a role that the user belongs to has + * write access. + * @method getReadAccess + * @param userId An instance of Parse.User or its objectId, or a Parse.Role. + * @return {Boolean} + */ + + }, { + key: 'getReadAccess', + value: function (userId) { + return this._getAccess('read', userId); + } + + /** + * Sets whether the given user id is allowed to write this object. + * @method setWriteAccess + * @param userId An instance of Parse.User or its objectId, or a Parse.Role.. + * @param {Boolean} allowed Whether that user should have write access. + */ + + }, { + key: 'setWriteAccess', + value: function (userId, allowed) { + this._setAccess('write', userId, allowed); + } + + /** + * Gets whether the given user id is *explicitly* allowed to write this object. + * Even if this returns false, the user may still be able to write it if + * getPublicWriteAccess returns true or a role that the user belongs to has + * write access. + * @method getWriteAccess + * @param userId An instance of Parse.User or its objectId, or a Parse.Role. + * @return {Boolean} + */ + + }, { + key: 'getWriteAccess', + value: function (userId) { + return this._getAccess('write', userId); + } + + /** + * Sets whether the public is allowed to read this object. + * @method setPublicReadAccess + * @param {Boolean} allowed + */ + + }, { + key: 'setPublicReadAccess', + value: function (allowed) { + this.setReadAccess(PUBLIC_KEY, allowed); + } + + /** + * Gets whether the public is allowed to read this object. + * @method getPublicReadAccess + * @return {Boolean} + */ + + }, { + key: 'getPublicReadAccess', + value: function () { + return this.getReadAccess(PUBLIC_KEY); + } + + /** + * Sets whether the public is allowed to write this object. + * @method setPublicWriteAccess + * @param {Boolean} allowed + */ + + }, { + key: 'setPublicWriteAccess', + value: function (allowed) { + this.setWriteAccess(PUBLIC_KEY, allowed); + } + + /** + * Gets whether the public is allowed to write this object. + * @method getPublicWriteAccess + * @return {Boolean} + */ + + }, { + key: 'getPublicWriteAccess', + value: function () { + return this.getWriteAccess(PUBLIC_KEY); + } + + /** + * Gets whether users belonging to the given role are allowed + * to read this object. Even if this returns false, the role may + * still be able to write it if a parent role has read access. + * + * @method getRoleReadAccess + * @param role The name of the role, or a Parse.Role object. + * @return {Boolean} true if the role has read access. false otherwise. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + + }, { + key: 'getRoleReadAccess', + value: function (role) { + if (role instanceof _ParseRole2.default) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + return this.getReadAccess('role:' + role); + } + + /** + * Gets whether users belonging to the given role are allowed + * to write this object. Even if this returns false, the role may + * still be able to write it if a parent role has write access. + * + * @method getRoleWriteAccess + * @param role The name of the role, or a Parse.Role object. + * @return {Boolean} true if the role has write access. false otherwise. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + + }, { + key: 'getRoleWriteAccess', + value: function (role) { + if (role instanceof _ParseRole2.default) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + return this.getWriteAccess('role:' + role); + } + + /** + * Sets whether users belonging to the given role are allowed + * to read this object. + * + * @method setRoleReadAccess + * @param role The name of the role, or a Parse.Role object. + * @param {Boolean} allowed Whether the given role can read this object. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + + }, { + key: 'setRoleReadAccess', + value: function (role, allowed) { + if (role instanceof _ParseRole2.default) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + this.setReadAccess('role:' + role, allowed); + } + + /** + * Sets whether users belonging to the given role are allowed + * to write this object. + * + * @method setRoleWriteAccess + * @param role The name of the role, or a Parse.Role object. + * @param {Boolean} allowed Whether the given role can write this object. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + + }, { + key: 'setRoleWriteAccess', + value: function (role, allowed) { + if (role instanceof _ParseRole2.default) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + this.setWriteAccess('role:' + role, allowed); + } + }]); + return ParseACL; +}(); + +exports.default = ParseACL; \ No newline at end of file diff --git a/lib/browser/ParseConfig.js b/lib/browser/ParseConfig.js new file mode 100644 index 000000000..925e3a864 --- /dev/null +++ b/lib/browser/ParseConfig.js @@ -0,0 +1,228 @@ +'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); + +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 _decode = require('./decode'); + +var _decode2 = _interopRequireDefault(_decode); + +var _encode = require('./encode'); + +var _encode2 = _interopRequireDefault(_encode); + +var _escape2 = require('./escape'); + +var _escape3 = _interopRequireDefault(_escape2); + +var _ParseError = require('./ParseError'); + +var _ParseError2 = _interopRequireDefault(_ParseError); + +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 }; +} + +/** + * Parse.Config is a local representation of configuration data that + * can be set from the Parse dashboard. + * + * @class Parse.Config + * @constructor + */ + +var ParseConfig = function () { + function ParseConfig() { + (0, _classCallCheck3.default)(this, ParseConfig); + + this.attributes = {}; + this._escapedAttributes = {}; + } + + /** + * Gets the value of an attribute. + * @method get + * @param {String} attr The name of an attribute. + */ + + (0, _createClass3.default)(ParseConfig, [{ + key: 'get', + value: function (attr) { + return this.attributes[attr]; + } + + /** + * Gets the HTML-escaped value of an attribute. + * @method escape + * @param {String} attr The name of an attribute. + */ + + }, { + key: 'escape', + value: function (attr) { + var html = this._escapedAttributes[attr]; + if (html) { + return html; + } + var val = this.attributes[attr]; + var escaped = ''; + if (val != null) { + escaped = (0, _escape3.default)(val.toString()); + } + this._escapedAttributes[attr] = escaped; + return escaped; + } + + /** + * Retrieves the most recently-fetched configuration object, either from + * memory or from local storage if necessary. + * + * @method current + * @static + * @return {Config} The most recently-fetched Parse.Config if it + * exists, else an empty Parse.Config. + */ + + }], [{ + key: 'current', + value: function () { + var controller = _CoreManager2.default.getConfigController(); + return controller.current(); + } + + /** + * Gets a new configuration object from the server. + * @method get + * @static + * @param {Object} options A Backbone-style options object. + * Valid options are:
        + *
      • success: Function to call when the get completes successfully. + *
      • error: Function to call when the get fails. + *
      + * @return {Parse.Promise} A promise that is resolved with a newly-created + * configuration object when the get completes. + */ + + }, { + key: 'get', + value: function (options) { + options = options || {}; + + var controller = _CoreManager2.default.getConfigController(); + return controller.get()._thenRunCallbacks(options); + } + }]); + return ParseConfig; +}(); /** + * 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 = ParseConfig; + +var currentConfig = null; + +var CURRENT_CONFIG_KEY = 'currentConfig'; + +function decodePayload(data) { + try { + var json = JSON.parse(data); + if (json && (typeof json === 'undefined' ? 'undefined' : (0, _typeof3.default)(json)) === 'object') { + return (0, _decode2.default)(json); + } + } catch (e) { + return null; + } +} + +var DefaultController = { + current: function () { + if (currentConfig) { + return currentConfig; + } + + var config = new ParseConfig(); + var storagePath = _Storage2.default.generatePath(CURRENT_CONFIG_KEY); + var configData; + if (!_Storage2.default.async()) { + configData = _Storage2.default.getItem(storagePath); + + if (configData) { + var attributes = decodePayload(configData); + if (attributes) { + config.attributes = attributes; + currentConfig = config; + } + } + return config; + } + // Return a promise for async storage controllers + return _Storage2.default.getItemAsync(storagePath).then(function (configData) { + if (configData) { + var attributes = decodePayload(configData); + if (attributes) { + config.attributes = attributes; + currentConfig = config; + } + } + return config; + }); + }, + get: function () { + var RESTController = _CoreManager2.default.getRESTController(); + + return RESTController.request('GET', 'config', {}, {}).then(function (response) { + if (!response || !response.params) { + var error = new _ParseError2.default(_ParseError2.default.INVALID_JSON, 'Config JSON response invalid.'); + return _ParsePromise2.default.error(error); + } + + var config = new ParseConfig(); + config.attributes = {}; + for (var attr in response.params) { + config.attributes[attr] = (0, _decode2.default)(response.params[attr]); + } + currentConfig = config; + return _Storage2.default.setItemAsync(_Storage2.default.generatePath(CURRENT_CONFIG_KEY), (0, _stringify2.default)(response.params)).then(function () { + return config; + }); + }); + } +}; + +_CoreManager2.default.setConfigController(DefaultController); \ No newline at end of file diff --git a/lib/browser/ParseError.js b/lib/browser/ParseError.js new file mode 100644 index 000000000..b3bad4512 --- /dev/null +++ b/lib/browser/ParseError.js @@ -0,0 +1,507 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +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. + */ + +/** + * Constructs a new Parse.Error object with the given code and message. + * @class Parse.Error + * @constructor + * @param {Number} code An error code constant from 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:
        + *
      • success: A Backbone-style success callback. + *
      • error: An Backbone-style error callback. + *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
      • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
      + * @return {Parse.Promise} A promise that is fulfilled when the fetch + * completes. + */ + + }, { + key: 'fetch', + value: function (options) { + options = options || {}; + var fetchOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + fetchOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + fetchOptions.sessionToken = options.sessionToken; + } + var controller = _CoreManager2.default.getObjectController(); + return controller.fetch(this, true, fetchOptions)._thenRunCallbacks(options); + } + + /** + * Set a hash of model attributes, and save the model to the server. + * updatedAt will be updated when the request returns. + * You can either call it as:
      +     *   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:
        + *
      • success: A Backbone-style success callback. + *
      • error: An Backbone-style error callback. + *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
      • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
      + * @return {Parse.Promise} A promise that is fulfilled when the save + * completes. + */ + + }, { + key: 'save', + value: function (arg1, arg2, arg3) { + var _this = this; + + var attrs; + var options; + if ((typeof arg1 === 'undefined' ? 'undefined' : (0, _typeof3.default)(arg1)) === 'object' || typeof arg1 === 'undefined') { + attrs = arg1; + if ((typeof arg2 === 'undefined' ? 'undefined' : (0, _typeof3.default)(arg2)) === 'object') { + options = arg2; + } + } else { + attrs = {}; + attrs[arg1] = arg2; + options = arg3; + } + + // Support save({ success: function() {}, error: function() {} }) + if (!options && attrs) { + options = {}; + if (typeof attrs.success === 'function') { + options.success = attrs.success; + delete attrs.success; + } + if (typeof attrs.error === 'function') { + options.error = attrs.error; + delete attrs.error; + } + } + + if (attrs) { + var validation = this.validate(attrs); + if (validation) { + if (options && typeof options.error === 'function') { + options.error(this, validation); + } + return _ParsePromise2.default.error(validation); + } + this.set(attrs, options); + } + + options = options || {}; + var saveOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + saveOptions.useMasterKey = !!options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken') && typeof options.sessionToken === 'string') { + saveOptions.sessionToken = options.sessionToken; + } + + var controller = _CoreManager2.default.getObjectController(); + var unsaved = (0, _unsavedChildren2.default)(this); + return controller.save(unsaved, saveOptions).then(function () { + return controller.save(_this, saveOptions); + })._thenRunCallbacks(options, this); + } + + /** + * Destroy this model on the server if it was already persisted. + * If `wait: true` is passed, waits for the server to respond + * before removal. + * + * @method destroy + * @param {Object} options A Backbone-style callback object. + * Valid options are:
        + *
      • success: A Backbone-style success callback + *
      • error: An Backbone-style error callback. + *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
      • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
      + * @return {Parse.Promise} A promise that is fulfilled when the destroy + * completes. + */ + + }, { + key: 'destroy', + value: function (options) { + options = options || {}; + var destroyOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + destroyOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + destroyOptions.sessionToken = options.sessionToken; + } + if (!this.id) { + return _ParsePromise2.default.as()._thenRunCallbacks(options); + } + return _CoreManager2.default.getObjectController().destroy(this, destroyOptions)._thenRunCallbacks(options); + } + + /** Static methods **/ + + }, { + key: 'attributes', + get: function () { + var stateController = _CoreManager2.default.getObjectStateController(); + return (0, _freeze2.default)(stateController.estimateAttributes(this._getStateIdentifier())); + } + + /** + * The first time this object was saved on the server. + * @property createdAt + * @type Date + */ + + }, { + key: 'createdAt', + get: function () { + return this._getServerData().createdAt; + } + + /** + * The last time this object was updated on the server. + * @property updatedAt + * @type Date + */ + + }, { + key: 'updatedAt', + get: function () { + return this._getServerData().updatedAt; + } + }], [{ + key: '_clearAllState', + value: function () { + var stateController = _CoreManager2.default.getObjectStateController(); + stateController.clearAllState(); + } + + /** + * Fetches the given list of Parse.Object. + * If any error is encountered, stops and calls the error handler. + * + *
      +     *   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:
        + *
      • success: A Backbone-style success callback. + *
      • error: An Backbone-style error callback. + *
      + */ + + }, { + key: 'fetchAll', + value: function (list, options) { + var options = options || {}; + + var queryOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + queryOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + queryOptions.sessionToken = options.sessionToken; + } + return _CoreManager2.default.getObjectController().fetch(list, true, queryOptions)._thenRunCallbacks(options); + } + + /** + * Fetches the given list of Parse.Object if needed. + * If any error is encountered, stops and calls the error handler. + * + *
      +     *   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:
        + *
      • success: A Backbone-style success callback. + *
      • error: An Backbone-style error callback. + *
      + */ + + }, { + key: 'fetchAllIfNeeded', + value: function (list, options) { + var options = options || {}; + + var queryOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + queryOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + queryOptions.sessionToken = options.sessionToken; + } + return _CoreManager2.default.getObjectController().fetch(list, false, queryOptions)._thenRunCallbacks(options); + } + + /** + * Destroy the given list of models on the server if it was already persisted. + * + *

      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: + * + *

        + *
      • A Parse.Error.AGGREGATE_ERROR. This object's "errors" property is an + * array of other Parse.Error objects. Each error object in this array + * has an "object" property that references the object that could not be + * deleted (for instance, because that object could not be found).
      • + *
      • A non-aggregate Parse.Error. This indicates a serious error that + * caused the delete operation to be aborted partway through (for + * instance, a connection failure in the middle of the delete).
      • + *
      + * + *
      +     *   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:
        + *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
      • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
      + * @return {Parse.Promise} A promise that is fulfilled when the destroyAll + * completes. + */ + + }, { + key: 'destroyAll', + value: function (list, options) { + var options = options || {}; + + var destroyOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + destroyOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + destroyOptions.sessionToken = options.sessionToken; + } + return _CoreManager2.default.getObjectController().destroy(list, destroyOptions)._thenRunCallbacks(options); + } + + /** + * Saves the given list of Parse.Object. + * If any error is encountered, stops and calls the error handler. + * + *
      +     *   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:
        + *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
      • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
      + */ + + }, { + key: 'saveAll', + value: function (list, options) { + var options = options || {}; + + var saveOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + saveOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + saveOptions.sessionToken = options.sessionToken; + } + return _CoreManager2.default.getObjectController().save(list, saveOptions)._thenRunCallbacks(options); + } + + /** + * Creates a reference to a subclass of Parse.Object with the given id. This + * does not exist on Parse.Object, only on subclasses. + * + *

      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:
        + *
      • success: A Backbone-style success callback + *
      • error: An Backbone-style error callback. + *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
      • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
      + * + * @return {Parse.Promise} A promise that is resolved with the result when + * the query completes. + */ + + }, { + key: 'get', + value: function (objectId, options) { + this.equalTo('objectId', objectId); + + var firstOptions = {}; + if (options && options.hasOwnProperty('useMasterKey')) { + firstOptions.useMasterKey = options.useMasterKey; + } + if (options && options.hasOwnProperty('sessionToken')) { + firstOptions.sessionToken = options.sessionToken; + } + + return this.first(firstOptions).then(function (response) { + if (response) { + return response; + } + + var errorObject = new _ParseError2.default(_ParseError2.default.OBJECT_NOT_FOUND, 'Object not found.'); + return _ParsePromise2.default.error(errorObject); + })._thenRunCallbacks(options, null); + } + + /** + * Retrieves a list of ParseObjects that satisfy this query. + * Either options.success or options.error is called when the find + * completes. + * + * @method find + * @param {Object} options A Backbone-style options object. Valid options + * are:
        + *
      • success: Function to call when the find completes successfully. + *
      • error: Function to call when the find fails. + *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
      • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
      + * + * @return {Parse.Promise} A promise that is resolved with the results when + * the query completes. + */ + + }, { + key: 'find', + value: function (options) { + var _this2 = this; + + options = options || {}; + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var controller = _CoreManager2.default.getQueryController(); + + var select = this._select; + + return controller.find(this.className, this.toJSON(), findOptions).then(function (response) { + return response.results.map(function (data) { + // In cases of relations, the server may send back a className + // on the top level of the payload + var override = response.className || _this2.className; + if (!data.className) { + data.className = override; + } + + // Make sure the data object contains keys for all objects that + // have been requested with a select, so that our cached state + // updates correctly. + if (select) { + handleSelectResult(data, select); + } + + return _ParseObject2.default.fromJSON(data, !select); + }); + })._thenRunCallbacks(options); + } + + /** + * Counts the number of objects that match this query. + * Either options.success or options.error is called when the count + * completes. + * + * @method count + * @param {Object} options A Backbone-style options object. Valid options + * are:
        + *
      • success: Function to call when the count completes successfully. + *
      • error: Function to call when the find fails. + *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
      • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
      + * + * @return {Parse.Promise} A promise that is resolved with the count when + * the query completes. + */ + + }, { + key: 'count', + value: function (options) { + options = options || {}; + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var controller = _CoreManager2.default.getQueryController(); + + var params = this.toJSON(); + params.limit = 0; + params.count = 1; + + return controller.find(this.className, params, findOptions).then(function (result) { + return result.count; + })._thenRunCallbacks(options); + } + + /** + * Retrieves at most one Parse.Object that satisfies this query. + * + * Either options.success or options.error is called when it completes. + * success is passed the object if there is one. otherwise, undefined. + * + * @method first + * @param {Object} options A Backbone-style options object. Valid options + * are:
        + *
      • success: Function to call when the find completes successfully. + *
      • error: Function to call when the find fails. + *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
      • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
      + * + * @return {Parse.Promise} A promise that is resolved with the object when + * the query completes. + */ + + }, { + key: 'first', + value: function (options) { + var _this3 = this; + + options = options || {}; + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var controller = _CoreManager2.default.getQueryController(); + + var params = this.toJSON(); + params.limit = 1; + + var select = this._select; + + return controller.find(this.className, params, findOptions).then(function (response) { + var objects = response.results; + if (!objects[0]) { + return undefined; + } + if (!objects[0].className) { + objects[0].className = _this3.className; + } + + // Make sure the data object contains keys for all objects that + // have been requested with a select, so that our cached state + // updates correctly. + if (select) { + handleSelectResult(objects[0], select); + } + + return _ParseObject2.default.fromJSON(objects[0], !select); + })._thenRunCallbacks(options); + } + + /** + * Iterates over each result of a query, calling a callback for each one. If + * the callback returns a promise, the iteration will not continue until + * that promise has been fulfilled. If the callback returns a rejected + * promise, then iteration will stop with that error. The items are + * processed in an unspecified order. The query may not have any sort order, + * and may not use limit or skip. + * @method each + * @param {Function} callback Callback that will be called with each result + * of the query. + * @param {Object} options A Backbone-style options object. Valid options + * are:
        + *
      • success: Function to call when the iteration completes successfully. + *
      • error: Function to call when the iteration fails. + *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
      • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
      + * @return {Parse.Promise} A promise that will be fulfilled once the + * iteration has completed. + */ + + }, { + key: 'each', + value: function (callback, options) { + options = options || {}; + + if (this._order || this._skip || this._limit >= 0) { + return _ParsePromise2.default.error('Cannot iterate on a query with sort, skip, or limit.')._thenRunCallbacks(options); + } + + new _ParsePromise2.default(); + + + var query = new ParseQuery(this.className); + // We can override the batch size from the options. + // This is undocumented, but useful for testing. + query._limit = options.batchSize || 100; + query._include = this._include.map(function (i) { + return i; + }); + if (this._select) { + query._select = this._select.map(function (s) { + return s; + }); + } + + query._where = {}; + for (var attr in this._where) { + var val = this._where[attr]; + if (Array.isArray(val)) { + query._where[attr] = val.map(function (v) { + return v; + }); + } else if (val && (typeof val === 'undefined' ? 'undefined' : (0, _typeof3.default)(val)) === 'object') { + var conditionMap = {}; + query._where[attr] = conditionMap; + for (var cond in val) { + conditionMap[cond] = val[cond]; + } + } else { + query._where[attr] = val; + } + } + + query.ascending('objectId'); + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var finished = false; + return _ParsePromise2.default._continueWhile(function () { + return !finished; + }, function () { + return query.find(findOptions).then(function (results) { + var callbacksDone = _ParsePromise2.default.as(); + results.forEach(function (result) { + callbacksDone = callbacksDone.then(function () { + return callback(result); + }); + }); + + return callbacksDone.then(function () { + if (results.length >= query._limit) { + query.greaterThan('objectId', results[results.length - 1].id); + } else { + finished = true; + } + }); + }); + })._thenRunCallbacks(options); + } + + /** Query Conditions **/ + + /** + * Adds a constraint to the query that requires a particular key's value to + * be equal to the provided value. + * @method equalTo + * @param {String} key The key to check. + * @param value The value that the Parse.Object must contain. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'equalTo', + value: function (key, value) { + if (typeof value === 'undefined') { + return this.doesNotExist(key); + } + + this._where[key] = (0, _encode2.default)(value, false, true); + return this; + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be not equal to the provided value. + * @method notEqualTo + * @param {String} key The key to check. + * @param value The value that must not be equalled. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'notEqualTo', + value: function (key, value) { + return this._addCondition(key, '$ne', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be less than the provided value. + * @method lessThan + * @param {String} key The key to check. + * @param value The value that provides an upper bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'lessThan', + value: function (key, value) { + return this._addCondition(key, '$lt', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be greater than the provided value. + * @method greaterThan + * @param {String} key The key to check. + * @param value The value that provides an lower bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'greaterThan', + value: function (key, value) { + return this._addCondition(key, '$gt', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be less than or equal to the provided value. + * @method lessThanOrEqualTo + * @param {String} key The key to check. + * @param value The value that provides an upper bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'lessThanOrEqualTo', + value: function (key, value) { + return this._addCondition(key, '$lte', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be greater than or equal to the provided value. + * @method greaterThanOrEqualTo + * @param {String} key The key to check. + * @param value The value that provides an lower bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'greaterThanOrEqualTo', + value: function (key, value) { + return this._addCondition(key, '$gte', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be contained in the provided list of values. + * @method containedIn + * @param {String} key The key to check. + * @param {Array} values The values that will match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'containedIn', + value: function (key, value) { + return this._addCondition(key, '$in', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * not be contained in the provided list of values. + * @method notContainedIn + * @param {String} key The key to check. + * @param {Array} values The values that will not match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'notContainedIn', + value: function (key, value) { + return this._addCondition(key, '$nin', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * contain each one of the provided list of values. + * @method containsAll + * @param {String} key The key to check. This key's value must be an array. + * @param {Array} values The values that will match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'containsAll', + value: function (key, values) { + return this._addCondition(key, '$all', values); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * contain each one of the provided list of values starting with given strings. + * @method containsAllStartingWith + * @param {String} key The key to check. This key's value must be an array. + * @param {Array} values The string values that will match as starting string. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'containsAllStartingWith', + value: function (key, values) { + var _this = this; + if (!Array.isArray(values)) { + values = [values]; + } + + values = values.map(function (value) { + return { "$regex": _this._regexStartWith(value) }; + }); + + return this.containsAll(key, values); + } + + /** + * Adds a constraint for finding objects that contain the given key. + * @method exists + * @param {String} key The key that should exist. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'exists', + value: function (key) { + return this._addCondition(key, '$exists', true); + } + + /** + * Adds a constraint for finding objects that do not contain a given key. + * @method doesNotExist + * @param {String} key The key that should not exist + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'doesNotExist', + value: function (key) { + return this._addCondition(key, '$exists', false); + } + + /** + * Adds a regular expression constraint for finding string values that match + * the provided regular expression. + * This may be slow for large datasets. + * @method matches + * @param {String} key The key that the string to match is stored in. + * @param {RegExp} regex The regular expression pattern to match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'matches', + value: function (key, regex, modifiers) { + this._addCondition(key, '$regex', regex); + if (!modifiers) { + modifiers = ''; + } + if (regex.ignoreCase) { + modifiers += 'i'; + } + if (regex.multiline) { + modifiers += 'm'; + } + if (modifiers.length) { + this._addCondition(key, '$options', modifiers); + } + return this; + } + + /** + * Adds a constraint that requires that a key's value matches a Parse.Query + * constraint. + * @method matchesQuery + * @param {String} key The key that the contains the object to match the + * query. + * @param {Parse.Query} query The query that should match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'matchesQuery', + value: function (key, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$inQuery', queryJSON); + } + + /** + * Adds a constraint that requires that a key's value not matches a + * Parse.Query constraint. + * @method doesNotMatchQuery + * @param {String} key The key that the contains the object to match the + * query. + * @param {Parse.Query} query The query that should not match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'doesNotMatchQuery', + value: function (key, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$notInQuery', queryJSON); + } + + /** + * Adds a constraint that requires that a key's value matches a value in + * an object returned by a different Parse.Query. + * @method matchesKeyInQuery + * @param {String} key The key that contains the value that is being + * matched. + * @param {String} queryKey The key in the objects returned by the query to + * match against. + * @param {Parse.Query} query The query to run. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'matchesKeyInQuery', + value: function (key, queryKey, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$select', { + key: queryKey, + query: queryJSON + }); + } + + /** + * Adds a constraint that requires that a key's value not match a value in + * an object returned by a different Parse.Query. + * @method doesNotMatchKeyInQuery + * @param {String} key The key that contains the value that is being + * excluded. + * @param {String} queryKey The key in the objects returned by the query to + * match against. + * @param {Parse.Query} query The query to run. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'doesNotMatchKeyInQuery', + value: function (key, queryKey, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$dontSelect', { + key: queryKey, + query: queryJSON + }); + } + + /** + * Adds a constraint for finding string values that contain a provided + * string. This may be slow for large datasets. + * @method contains + * @param {String} key The key that the string to match is stored in. + * @param {String} substring The substring that the value must contain. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'contains', + value: function (key, value) { + if (typeof value !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + return this._addCondition(key, '$regex', quote(value)); + } + + /** + * Adds a constraint for finding string values that start with a provided + * string. This query will use the backend index, so it will be fast even + * for large datasets. + * @method startsWith + * @param {String} key The key that the string to match is stored in. + * @param {String} prefix The substring that the value must start with. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'startsWith', + value: function (key, value) { + if (typeof value !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + return this._addCondition(key, '$regex', this._regexStartWith(value)); + } + + /** + * Adds a constraint for finding string values that end with a provided + * string. This will be slow for large datasets. + * @method endsWith + * @param {String} key The key that the string to match is stored in. + * @param {String} suffix The substring that the value must end with. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'endsWith', + value: function (key, value) { + if (typeof value !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + return this._addCondition(key, '$regex', quote(value) + '$'); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given. + * @method near + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'near', + value: function (key, point) { + if (!(point instanceof _ParseGeoPoint2.default)) { + // Try to cast it as a GeoPoint + point = new _ParseGeoPoint2.default(point); + } + return this._addCondition(key, '$nearSphere', point); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * @method withinRadians + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in radians) of results to + * return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'withinRadians', + value: function (key, point, distance) { + this.near(key, point); + return this._addCondition(key, '$maxDistance', distance); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * Radius of earth used is 3958.8 miles. + * @method withinMiles + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in miles) of results to + * return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'withinMiles', + value: function (key, point, distance) { + return this.withinRadians(key, point, distance / 3958.8); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * Radius of earth used is 6371.0 kilometers. + * @method withinKilometers + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in kilometers) of results + * to return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'withinKilometers', + value: function (key, point, distance) { + return this.withinRadians(key, point, distance / 6371.0); + } + + /** + * Adds a constraint to the query that requires a particular key's + * coordinates be contained within a given rectangular geographic bounding + * box. + * @method withinGeoBox + * @param {String} key The key to be constrained. + * @param {Parse.GeoPoint} southwest + * The lower-left inclusive corner of the box. + * @param {Parse.GeoPoint} northeast + * The upper-right inclusive corner of the box. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'withinGeoBox', + value: function (key, southwest, northeast) { + if (!(southwest instanceof _ParseGeoPoint2.default)) { + southwest = new _ParseGeoPoint2.default(southwest); + } + if (!(northeast instanceof _ParseGeoPoint2.default)) { + northeast = new _ParseGeoPoint2.default(northeast); + } + this._addCondition(key, '$within', { '$box': [southwest, northeast] }); + return this; + } + + /** Query Orderings **/ + + /** + * Sorts the results in ascending order by the given key. + * + * @method ascending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'ascending', + value: function () { + this._order = []; + + for (var _len = arguments.length, keys = Array(_len), _key = 0; _key < _len; _key++) { + keys[_key] = arguments[_key]; + } + + return this.addAscending.apply(this, keys); + } + + /** + * Sorts the results in ascending order by the given key, + * but can also add secondary sort descriptors without overwriting _order. + * + * @method addAscending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'addAscending', + value: function () { + var _this4 = this; + + if (!this._order) { + this._order = []; + } + + for (var _len2 = arguments.length, keys = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + keys[_key2] = arguments[_key2]; + } + + keys.forEach(function (key) { + if (Array.isArray(key)) { + key = key.join(); + } + _this4._order = _this4._order.concat(key.replace(/\s/g, '').split(',')); + }); + + return this; + } + + /** + * Sorts the results in descending order by the given key. + * + * @method descending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'descending', + value: function () { + this._order = []; + + for (var _len3 = arguments.length, keys = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + keys[_key3] = arguments[_key3]; + } + + return this.addDescending.apply(this, keys); + } + + /** + * Sorts the results in descending order by the given key, + * but can also add secondary sort descriptors without overwriting _order. + * + * @method addDescending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'addDescending', + value: function () { + var _this5 = this; + + if (!this._order) { + this._order = []; + } + + for (var _len4 = arguments.length, keys = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { + keys[_key4] = arguments[_key4]; + } + + keys.forEach(function (key) { + if (Array.isArray(key)) { + key = key.join(); + } + _this5._order = _this5._order.concat(key.replace(/\s/g, '').split(',').map(function (k) { + return '-' + k; + })); + }); + + return this; + } + + /** Query Options **/ + + /** + * Sets the number of results to skip before returning any results. + * This is useful for pagination. + * Default is to skip zero results. + * @method skip + * @param {Number} n the number of results to skip. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'skip', + value: function (n) { + if (typeof n !== 'number' || n < 0) { + throw new Error('You can only skip by a positive number'); + } + this._skip = n; + return this; + } + + /** + * Sets the limit of the number of results to return. The default limit is + * 100, with a maximum of 1000 results being returned at a time. + * @method limit + * @param {Number} n the number of results to limit to. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'limit', + value: function (n) { + if (typeof n !== 'number') { + throw new Error('You can only set the limit to a numeric value'); + } + this._limit = n; + return this; + } + + /** + * Includes nested Parse.Objects for the provided key. You can use dot + * notation to specify which fields in the included object are also fetched. + * @method include + * @param {String} key The name of the key to include. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'include', + value: function () { + var _this6 = this; + + for (var _len5 = arguments.length, keys = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { + keys[_key5] = arguments[_key5]; + } + + keys.forEach(function (key) { + if (Array.isArray(key)) { + _this6._include = _this6._include.concat(key); + } else { + _this6._include.push(key); + } + }); + return this; + } + + /** + * Restricts the fields of the returned Parse.Objects to include only the + * provided keys. If this is called multiple times, then all of the keys + * specified in each of the calls will be included. + * @method select + * @param {Array} keys The names of the keys to include. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'select', + value: function () { + var _this7 = this; + + if (!this._select) { + this._select = []; + } + + for (var _len6 = arguments.length, keys = Array(_len6), _key6 = 0; _key6 < _len6; _key6++) { + keys[_key6] = arguments[_key6]; + } + + keys.forEach(function (key) { + if (Array.isArray(key)) { + _this7._select = _this7._select.concat(key); + } else { + _this7._select.push(key); + } + }); + return this; + } + + /** + * Subscribe this query to get liveQuery updates + * @method subscribe + * @return {LiveQuerySubscription} Returns the liveQuerySubscription, it's an event emitter + * which can be used to get liveQuery updates. + */ + + }, { + key: 'subscribe', + value: function () { + var controller = _CoreManager2.default.getLiveQueryController(); + return controller.subscribe(this); + } + + /** + * Constructs a Parse.Query that is the OR of the passed in queries. For + * example: + *
      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 if current 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 using current. + * + *

      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: + *
        + *
      1. channels - An Array of channels to push to.
      2. + *
      3. push_time - A Date object for when to send the push.
      4. + *
      5. expiration_time - A Date object for when to expire + * the push.
      6. + *
      7. expiration_interval - The seconds from now to expire the push.
      8. + *
      9. where - A Parse.Query over Parse.Installation that is used to match + * a set of installations to push to.
      10. + *
      11. data - The data to send as part of the push
      12. + *
          + * @param {Object} options An object that has an optional success function, + * that takes no arguments and will be called on a successful push, and + * an error function that takes a Parse.Error and will be called if the push + * failed. + * @return {Parse.Promise} A promise that is fulfilled when the push request + * completes. + */ +/** + * 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 send(data, options) { + options = options || {}; + + if (data.where && data.where instanceof _ParseQuery2.default) { + data.where = data.where.toJSON().where; + } + + if (data.push_time && (0, _typeof3.default)(data.push_time) === 'object') { + data.push_time = data.push_time.toJSON(); + } + + if (data.expiration_time && (0, _typeof3.default)(data.expiration_time) === 'object') { + data.expiration_time = data.expiration_time.toJSON(); + } + + if (data.expiration_time && data.expiration_interval) { + throw new Error('expiration_time and expiration_interval cannot both be set.'); + } + + return _CoreManager2.default.getPushController().send(data, { + useMasterKey: options.useMasterKey + })._thenRunCallbacks(options); +} + +var DefaultController = { + send: function (data, options) { + var RESTController = _CoreManager2.default.getRESTController(); + + var request = RESTController.request('POST', 'push', data, { useMasterKey: !!options.useMasterKey }); + + return request._thenRunCallbacks(options); + } +}; + +_CoreManager2.default.setPushController(DefaultController); \ No newline at end of file diff --git a/lib/browser/RESTController.js b/lib/browser/RESTController.js new file mode 100644 index 000000000..f8ea70e2f --- /dev/null +++ b/lib/browser/RESTController.js @@ -0,0 +1,248 @@ +'use strict'; + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +var _stringify = require('babel-runtime/core-js/json/stringify'); + +var _stringify2 = _interopRequireDefault(_stringify); + +var _CoreManager = require('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +var _ParseError = require('./ParseError'); + +var _ParseError2 = _interopRequireDefault(_ParseError); + +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 }; +} + +/** + * 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 XHR = null; +if (typeof XMLHttpRequest !== 'undefined') { + XHR = XMLHttpRequest; +} + + +var useXDomainRequest = false; +if (typeof XDomainRequest !== 'undefined' && !('withCredentials' in new XMLHttpRequest())) { + useXDomainRequest = true; +} + +function ajaxIE9(method, url, data) { + var promise = new _ParsePromise2.default(); + var xdr = new XDomainRequest(); + xdr.onload = function () { + var response; + try { + response = JSON.parse(xdr.responseText); + } catch (e) { + promise.reject(e); + } + if (response) { + promise.resolve(response); + } + }; + xdr.onerror = xdr.ontimeout = function () { + // Let's fake a real error message. + var fakeResponse = { + responseText: (0, _stringify2.default)({ + code: _ParseError2.default.X_DOMAIN_REQUEST, + error: 'IE\'s XDomainRequest does not supply error info.' + }) + }; + promise.reject(fakeResponse); + }; + xdr.onprogress = function () {}; + xdr.open(method, url); + xdr.send(data); + return promise; +} + +var RESTController = { + ajax: function (method, url, data, headers) { + if (useXDomainRequest) { + return ajaxIE9(method, url, data, headers); + } + + var promise = new _ParsePromise2.default(); + var attempts = 0; + + (function dispatch() { + if (XHR == null) { + throw new Error('Cannot make a request: No definition of XMLHttpRequest was found.'); + } + var handled = false; + var xhr = new XHR(); + + xhr.onreadystatechange = function () { + if (xhr.readyState !== 4 || handled) { + return; + } + handled = true; + + if (xhr.status >= 200 && xhr.status < 300) { + var response; + try { + response = JSON.parse(xhr.responseText); + } catch (e) { + promise.reject(e.toString()); + } + if (response) { + promise.resolve(response, xhr.status, xhr); + } + } else if (xhr.status >= 500 || xhr.status === 0) { + // retry on 5XX or node-xmlhttprequest error + if (++attempts < _CoreManager2.default.get('REQUEST_ATTEMPT_LIMIT')) { + // Exponentially-growing random delay + var delay = Math.round(Math.random() * 125 * Math.pow(2, attempts)); + setTimeout(dispatch, delay); + } else if (xhr.status === 0) { + promise.reject('Unable to connect to the Parse API'); + } else { + // After the retry limit is reached, fail + promise.reject(xhr); + } + } else { + promise.reject(xhr); + } + }; + + headers = headers || {}; + if (typeof headers['Content-Type'] !== 'string') { + headers['Content-Type'] = 'text/plain'; // Avoid pre-flight + } + if (_CoreManager2.default.get('IS_NODE')) { + headers['User-Agent'] = 'Parse/' + _CoreManager2.default.get('VERSION') + ' (NodeJS ' + process.versions.node + ')'; + } + + xhr.open(method, url, true); + for (var h in headers) { + xhr.setRequestHeader(h, headers[h]); + } + xhr.send(data); + })(); + + return promise; + }, + request: function (method, path, data, options) { + options = options || {}; + var url = _CoreManager2.default.get('SERVER_URL'); + if (url[url.length - 1] !== '/') { + url += '/'; + } + url += path; + + var payload = {}; + if (data && (typeof data === 'undefined' ? 'undefined' : (0, _typeof3.default)(data)) === 'object') { + for (var k in data) { + payload[k] = data[k]; + } + } + + if (method !== 'POST') { + payload._method = method; + method = 'POST'; + } + + payload._ApplicationId = _CoreManager2.default.get('APPLICATION_ID'); + var jsKey = _CoreManager2.default.get('JAVASCRIPT_KEY'); + if (jsKey) { + payload._JavaScriptKey = jsKey; + } + payload._ClientVersion = _CoreManager2.default.get('VERSION'); + + var useMasterKey = options.useMasterKey; + if (typeof useMasterKey === 'undefined') { + useMasterKey = _CoreManager2.default.get('USE_MASTER_KEY'); + } + if (useMasterKey) { + if (_CoreManager2.default.get('MASTER_KEY')) { + delete payload._JavaScriptKey; + payload._MasterKey = _CoreManager2.default.get('MASTER_KEY'); + } else { + throw new Error('Cannot use the Master Key, it has not been provided.'); + } + } + + if (_CoreManager2.default.get('FORCE_REVOCABLE_SESSION')) { + payload._RevocableSession = '1'; + } + + var installationId = options.installationId; + var installationIdPromise; + if (installationId && typeof installationId === 'string') { + installationIdPromise = _ParsePromise2.default.as(installationId); + } else { + var installationController = _CoreManager2.default.getInstallationController(); + installationIdPromise = installationController.currentInstallationId(); + } + + return installationIdPromise.then(function (iid) { + payload._InstallationId = iid; + var userController = _CoreManager2.default.getUserController(); + if (options && typeof options.sessionToken === 'string') { + return _ParsePromise2.default.as(options.sessionToken); + } else if (userController) { + return userController.currentUserAsync().then(function (user) { + if (user) { + return _ParsePromise2.default.as(user.getSessionToken()); + } + return _ParsePromise2.default.as(null); + }); + } + return _ParsePromise2.default.as(null); + }).then(function (token) { + if (token) { + payload._SessionToken = token; + } + + var payloadString = (0, _stringify2.default)(payload); + + return RESTController.ajax(method, url, payloadString); + }).then(null, function (response) { + // Transform the error into an instance of ParseError by trying to parse + // the error string as JSON + var error; + if (response && response.responseText) { + try { + var errorJSON = JSON.parse(response.responseText); + error = new _ParseError2.default(errorJSON.code, errorJSON.error); + } catch (e) { + // If we fail to parse the error text, that's okay. + error = new _ParseError2.default(_ParseError2.default.INVALID_JSON, 'Received an error with invalid JSON from Parse: ' + response.responseText); + } + } else { + error = new _ParseError2.default(_ParseError2.default.CONNECTION_FAILED, 'XMLHttpRequest failed: ' + (0, _stringify2.default)(response)); + } + + return _ParsePromise2.default.error(error); + }); + }, + _setXHR: function (xhr) { + XHR = xhr; + } +}; + +module.exports = RESTController; \ No newline at end of file diff --git a/lib/browser/SingleInstanceStateController.js b/lib/browser/SingleInstanceStateController.js new file mode 100644 index 000000000..545dd4c5d --- /dev/null +++ b/lib/browser/SingleInstanceStateController.js @@ -0,0 +1,160 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getState = getState; +exports.initializeState = initializeState; +exports.removeState = removeState; +exports.getServerData = getServerData; +exports.setServerData = setServerData; +exports.getPendingOps = getPendingOps; +exports.setPendingOp = setPendingOp; +exports.pushPendingState = pushPendingState; +exports.popPendingState = popPendingState; +exports.mergeFirstPendingState = mergeFirstPendingState; +exports.getObjectCache = getObjectCache; +exports.estimateAttribute = estimateAttribute; +exports.estimateAttributes = estimateAttributes; +exports.commitServerChanges = commitServerChanges; +exports.enqueueTask = enqueueTask; +exports.clearAllState = clearAllState; +exports.duplicateState = duplicateState; + +var _ObjectStateMutations = require('./ObjectStateMutations'); + +var ObjectStateMutations = _interopRequireWildcard(_ObjectStateMutations); + +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; + } +} + +var objectState = {}; /** + * 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 getState(obj) { + var classData = objectState[obj.className]; + if (classData) { + return classData[obj.id] || null; + } + return null; +} + +function initializeState(obj, initial) { + var state = getState(obj); + if (state) { + return state; + } + if (!objectState[obj.className]) { + objectState[obj.className] = {}; + } + if (!initial) { + initial = ObjectStateMutations.defaultState(); + } + state = objectState[obj.className][obj.id] = initial; + return state; +} + +function removeState(obj) { + var state = getState(obj); + if (state === null) { + return null; + } + delete objectState[obj.className][obj.id]; + return state; +} + +function getServerData(obj) { + var state = getState(obj); + if (state) { + return state.serverData; + } + return {}; +} + +function setServerData(obj, attributes) { + var serverData = initializeState(obj).serverData; + ObjectStateMutations.setServerData(serverData, attributes); +} + +function getPendingOps(obj) { + var state = getState(obj); + if (state) { + return state.pendingOps; + } + return [{}]; +} + +function setPendingOp(obj, attr, op) { + var pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.setPendingOp(pendingOps, attr, op); +} + +function pushPendingState(obj) { + var pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.pushPendingState(pendingOps); +} + +function popPendingState(obj) { + var pendingOps = initializeState(obj).pendingOps; + return ObjectStateMutations.popPendingState(pendingOps); +} + +function mergeFirstPendingState(obj) { + var pendingOps = getPendingOps(obj); + ObjectStateMutations.mergeFirstPendingState(pendingOps); +} + +function getObjectCache(obj) { + var state = getState(obj); + if (state) { + return state.objectCache; + } + return {}; +} + +function estimateAttribute(obj, attr) { + var serverData = getServerData(obj); + var pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttribute(serverData, pendingOps, obj.className, obj.id, attr); +} + +function estimateAttributes(obj) { + var serverData = getServerData(obj); + var pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttributes(serverData, pendingOps, obj.className, obj.id); +} + +function commitServerChanges(obj, changes) { + var state = initializeState(obj); + ObjectStateMutations.commitServerChanges(state.serverData, state.objectCache, changes); +} + +function enqueueTask(obj, task) { + var state = initializeState(obj); + return state.tasks.enqueue(task); +} + +function clearAllState() { + objectState = {}; +} + +function duplicateState(source, dest) { + dest.id = source.id; +} \ No newline at end of file diff --git a/lib/browser/Storage.js b/lib/browser/Storage.js new file mode 100644 index 000000000..e5260e608 --- /dev/null +++ b/lib/browser/Storage.js @@ -0,0 +1,93 @@ +'use strict'; + +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. + * + * + */ + +module.exports = { + async: function () { + var controller = _CoreManager2.default.getStorageController(); + return !!controller.async; + }, + getItem: function (path) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + throw new Error('Synchronous storage is not supported by the current storage controller'); + } + return controller.getItem(path); + }, + getItemAsync: function (path) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + return controller.getItemAsync(path); + } + return _ParsePromise2.default.as(controller.getItem(path)); + }, + setItem: function (path, value) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + throw new Error('Synchronous storage is not supported by the current storage controller'); + } + return controller.setItem(path, value); + }, + setItemAsync: function (path, value) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + return controller.setItemAsync(path, value); + } + return _ParsePromise2.default.as(controller.setItem(path, value)); + }, + removeItem: function (path) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + throw new Error('Synchronous storage is not supported by the current storage controller'); + } + return controller.removeItem(path); + }, + removeItemAsync: function (path) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + return controller.removeItemAsync(path); + } + return _ParsePromise2.default.as(controller.removeItem(path)); + }, + generatePath: function (path) { + if (!_CoreManager2.default.get('APPLICATION_ID')) { + throw new Error('You need to call Parse.initialize before using Parse.'); + } + if (typeof path !== 'string') { + throw new Error('Tried to get a Storage path that was not a String.'); + } + if (path[0] === '/') { + path = path.substr(1); + } + return 'Parse/' + _CoreManager2.default.get('APPLICATION_ID') + '/' + path; + }, + _clear: function () { + var controller = _CoreManager2.default.getStorageController(); + if (controller.hasOwnProperty('clear')) { + controller.clear(); + } + } +}; + +_CoreManager2.default.setStorageController(require('./StorageController.browser')); \ No newline at end of file diff --git a/lib/browser/StorageController.browser.js b/lib/browser/StorageController.browser.js new file mode 100644 index 000000000..d372924a0 --- /dev/null +++ b/lib/browser/StorageController.browser.js @@ -0,0 +1,41 @@ +'use strict'; + +var _ParsePromise = require('./ParsePromise'); + +var _ParsePromise2 = _interopRequireDefault(_ParsePromise); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +var StorageController = { + async: 0, + + getItem: function (path) { + return localStorage.getItem(path); + }, + setItem: function (path, value) { + try { + localStorage.setItem(path, value); + } catch (e) { + // Quota exceeded, possibly due to Safari Private Browsing mode + } + }, + removeItem: function (path) { + localStorage.removeItem(path); + }, + clear: function () { + localStorage.clear(); + } +}; /** + * 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. + * + * + */ + +module.exports = StorageController; \ No newline at end of file diff --git a/lib/browser/StorageController.default.js b/lib/browser/StorageController.default.js new file mode 100644 index 000000000..460c1e2ca --- /dev/null +++ b/lib/browser/StorageController.default.js @@ -0,0 +1,41 @@ +"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. + * + * + */ + +// When there is no native storage interface, we default to an in-memory map + +var memMap = {}; +var StorageController = { + async: 0, + + getItem: function (path) { + if (memMap.hasOwnProperty(path)) { + return memMap[path]; + } + return null; + }, + setItem: function (path, value) { + memMap[path] = String(value); + }, + removeItem: function (path) { + delete memMap[path]; + }, + clear: function () { + for (var key in memMap) { + if (memMap.hasOwnProperty(key)) { + delete memMap[key]; + } + } + } +}; + +module.exports = StorageController; \ No newline at end of file diff --git a/lib/browser/StorageController.react-native.js b/lib/browser/StorageController.react-native.js new file mode 100644 index 000000000..b92ae6141 --- /dev/null +++ b/lib/browser/StorageController.react-native.js @@ -0,0 +1,67 @@ +'use strict'; + +var _ParsePromise = require('./ParsePromise'); + +var _ParsePromise2 = _interopRequireDefault(_ParsePromise); + +var _reactNative = require('react-native/Libraries/react-native/react-native.js'); + +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 StorageController = { + async: 1, + + getItemAsync: function (path) { + var p = new _ParsePromise2.default(); + _reactNative.AsyncStorage.getItem(path, function (err, value) { + if (err) { + p.reject(err); + } else { + p.resolve(value); + } + }); + return p; + }, + setItemAsync: function (path, value) { + var p = new _ParsePromise2.default(); + _reactNative.AsyncStorage.setItem(path, value, function (err) { + if (err) { + p.reject(err); + } else { + p.resolve(value); + } + }); + return p; + }, + removeItemAsync: function (path) { + var p = new _ParsePromise2.default(); + _reactNative.AsyncStorage.removeItem(path, function (err) { + if (err) { + p.reject(err); + } else { + p.resolve(); + } + }); + return p; + }, + clear: function () { + _reactNative.AsyncStorage.clear(); + } +}; +// RN packager nonsense + + +module.exports = StorageController; \ No newline at end of file diff --git a/lib/browser/TaskQueue.js b/lib/browser/TaskQueue.js new file mode 100644 index 000000000..4bf6bffd0 --- /dev/null +++ b/lib/browser/TaskQueue.js @@ -0,0 +1,77 @@ +'use strict'; + +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 }; +} + +var TaskQueue = function () { + function TaskQueue() { + (0, _classCallCheck3.default)(this, TaskQueue); + + this.queue = []; + } + + (0, _createClass3.default)(TaskQueue, [{ + key: 'enqueue', + value: function (task) { + var _this = this; + + var taskComplete = new _ParsePromise2.default(); + this.queue.push({ + task: task, + _completion: taskComplete + }); + if (this.queue.length === 1) { + task().then(function () { + _this._dequeue(); + taskComplete.resolve(); + }, function (error) { + _this._dequeue(); + taskComplete.reject(error); + }); + } + return taskComplete; + } + }, { + key: '_dequeue', + value: function () { + var _this2 = this; + + this.queue.shift(); + if (this.queue.length) { + var next = this.queue[0]; + next.task().then(function () { + _this2._dequeue(); + next._completion.resolve(); + }, function (error) { + _this2._dequeue(); + next._completion.reject(error); + }); + } + } + }]); + return TaskQueue; +}(); /** + * 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. + * + * + */ + +module.exports = TaskQueue; \ No newline at end of file diff --git a/lib/browser/UniqueInstanceStateController.js b/lib/browser/UniqueInstanceStateController.js new file mode 100644 index 000000000..12b8ab04d --- /dev/null +++ b/lib/browser/UniqueInstanceStateController.js @@ -0,0 +1,189 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _weakMap = require('babel-runtime/core-js/weak-map'); + +var _weakMap2 = _interopRequireDefault(_weakMap); + +exports.getState = getState; +exports.initializeState = initializeState; +exports.removeState = removeState; +exports.getServerData = getServerData; +exports.setServerData = setServerData; +exports.getPendingOps = getPendingOps; +exports.setPendingOp = setPendingOp; +exports.pushPendingState = pushPendingState; +exports.popPendingState = popPendingState; +exports.mergeFirstPendingState = mergeFirstPendingState; +exports.getObjectCache = getObjectCache; +exports.estimateAttribute = estimateAttribute; +exports.estimateAttributes = estimateAttributes; +exports.commitServerChanges = commitServerChanges; +exports.enqueueTask = enqueueTask; +exports.duplicateState = duplicateState; +exports.clearAllState = clearAllState; + +var _ObjectStateMutations = require('./ObjectStateMutations'); + +var ObjectStateMutations = _interopRequireWildcard(_ObjectStateMutations); + +var _TaskQueue = require('./TaskQueue'); + +var _TaskQueue2 = _interopRequireDefault(_TaskQueue); + +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 }; +} + +/** + * 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 objectState = new _weakMap2.default(); + +function getState(obj) { + var classData = objectState.get(obj); + return classData || null; +} + +function initializeState(obj, initial) { + var state = getState(obj); + if (state) { + return state; + } + if (!initial) { + initial = { + serverData: {}, + pendingOps: [{}], + objectCache: {}, + tasks: new _TaskQueue2.default(), + existed: false + }; + } + state = initial; + objectState.set(obj, state); + return state; +} + +function removeState(obj) { + var state = getState(obj); + if (state === null) { + return null; + } + objectState.delete(obj); + return state; +} + +function getServerData(obj) { + var state = getState(obj); + if (state) { + return state.serverData; + } + return {}; +} + +function setServerData(obj, attributes) { + var serverData = initializeState(obj).serverData; + ObjectStateMutations.setServerData(serverData, attributes); +} + +function getPendingOps(obj) { + var state = getState(obj); + if (state) { + return state.pendingOps; + } + return [{}]; +} + +function setPendingOp(obj, attr, op) { + var pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.setPendingOp(pendingOps, attr, op); +} + +function pushPendingState(obj) { + var pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.pushPendingState(pendingOps); +} + +function popPendingState(obj) { + var pendingOps = initializeState(obj).pendingOps; + return ObjectStateMutations.popPendingState(pendingOps); +} + +function mergeFirstPendingState(obj) { + var pendingOps = getPendingOps(obj); + ObjectStateMutations.mergeFirstPendingState(pendingOps); +} + +function getObjectCache(obj) { + var state = getState(obj); + if (state) { + return state.objectCache; + } + return {}; +} + +function estimateAttribute(obj, attr) { + var serverData = getServerData(obj); + var pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttribute(serverData, pendingOps, obj.className, obj.id, attr); +} + +function estimateAttributes(obj) { + var serverData = getServerData(obj); + var pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttributes(serverData, pendingOps, obj.className, obj.id); +} + +function commitServerChanges(obj, changes) { + var state = initializeState(obj); + ObjectStateMutations.commitServerChanges(state.serverData, state.objectCache, changes); +} + +function enqueueTask(obj, task) { + var state = initializeState(obj); + return state.tasks.enqueue(task); +} + +function duplicateState(source, dest) { + var oldState = initializeState(source); + var newState = initializeState(dest); + for (var key in oldState.serverData) { + newState.serverData[key] = oldState.serverData[key]; + } + for (var index = 0; index < oldState.pendingOps.length; index++) { + for (var _key in oldState.pendingOps[index]) { + newState.pendingOps[index][_key] = oldState.pendingOps[index][_key]; + } + } + for (var _key2 in oldState.objectCache) { + newState.objectCache[_key2] = oldState.objectCache[_key2]; + } + newState.existed = oldState.existed; +} + +function clearAllState() { + objectState = new _weakMap2.default(); +} \ No newline at end of file diff --git a/lib/browser/arrayContainsObject.js b/lib/browser/arrayContainsObject.js new file mode 100644 index 000000000..e9d7949c9 --- /dev/null +++ b/lib/browser/arrayContainsObject.js @@ -0,0 +1,35 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = arrayContainsObject; + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +function arrayContainsObject(array, object) { + if (array.indexOf(object) > -1) { + return true; + } + for (var i = 0; i < array.length; i++) { + if (array[i] instanceof _ParseObject2.default && array[i].className === object.className && array[i]._getId() === object._getId()) { + return true; + } + } + return 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. + * + * + */ \ No newline at end of file diff --git a/lib/browser/canBeSerialized.js b/lib/browser/canBeSerialized.js new file mode 100644 index 000000000..e7b7e2282 --- /dev/null +++ b/lib/browser/canBeSerialized.js @@ -0,0 +1,82 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +exports.default = canBeSerialized; + +var _ParseFile = require('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseRelation = require('./ParseRelation'); + +var _ParseRelation2 = _interopRequireDefault(_ParseRelation); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +function canBeSerialized(obj) { + if (!(obj instanceof _ParseObject2.default)) { + return true; + } + var attributes = obj.attributes; + for (var attr in attributes) { + var val = attributes[attr]; + if (!canBeSerializedHelper(val)) { + return false; + } + } + return true; +} /** + * 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 canBeSerializedHelper(value) { + if ((typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) !== 'object') { + return true; + } + if (value instanceof _ParseRelation2.default) { + return true; + } + if (value instanceof _ParseObject2.default) { + return !!value.id; + } + if (value instanceof _ParseFile2.default) { + if (value.url()) { + return true; + } + return false; + } + if (Array.isArray(value)) { + for (var i = 0; i < value.length; i++) { + if (!canBeSerializedHelper(value[i])) { + return false; + } + } + return true; + } + for (var k in value) { + if (!canBeSerializedHelper(value[k])) { + return false; + } + } + return true; +} \ No newline at end of file diff --git a/lib/browser/decode.js b/lib/browser/decode.js new file mode 100644 index 000000000..98a182789 --- /dev/null +++ b/lib/browser/decode.js @@ -0,0 +1,93 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +exports.default = decode; + +var _ParseACL = require('./ParseACL'); + +var _ParseACL2 = _interopRequireDefault(_ParseACL); + +var _ParseFile = require('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseGeoPoint = require('./ParseGeoPoint'); + +var _ParseGeoPoint2 = _interopRequireDefault(_ParseGeoPoint); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseOp = require('./ParseOp'); + +var _ParseRelation = require('./ParseRelation'); + +var _ParseRelation2 = _interopRequireDefault(_ParseRelation); + +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 decode(value) { + if (value === null || (typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) !== 'object') { + return value; + } + if (Array.isArray(value)) { + var dup = []; + value.forEach(function (v, i) { + dup[i] = decode(v); + }); + return dup; + } + if (typeof value.__op === 'string') { + return (0, _ParseOp.opFromJSON)(value); + } + if (value.__type === 'Pointer' && value.className) { + return _ParseObject2.default.fromJSON(value); + } + if (value.__type === 'Object' && value.className) { + return _ParseObject2.default.fromJSON(value); + } + if (value.__type === 'Relation') { + // The parent and key fields will be populated by the parent + var relation = new _ParseRelation2.default(null, null); + relation.targetClassName = value.className; + return relation; + } + if (value.__type === 'Date') { + return new Date(value.iso); + } + if (value.__type === 'File') { + return _ParseFile2.default.fromJSON(value); + } + if (value.__type === 'GeoPoint') { + return new _ParseGeoPoint2.default({ + latitude: value.latitude, + longitude: value.longitude + }); + } + var copy = {}; + for (var k in value) { + copy[k] = decode(value[k]); + } + return copy; +} \ No newline at end of file diff --git a/lib/browser/encode.js b/lib/browser/encode.js new file mode 100644 index 000000000..8c3aee6e3 --- /dev/null +++ b/lib/browser/encode.js @@ -0,0 +1,104 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +var _keys = require('babel-runtime/core-js/object/keys'); + +var _keys2 = _interopRequireDefault(_keys); + +exports.default = function (value, disallowObjects, forcePointers, seen) { + return encode(value, !!disallowObjects, !!forcePointers, seen || []); +}; + +var _ParseACL = require('./ParseACL'); + +var _ParseACL2 = _interopRequireDefault(_ParseACL); + +var _ParseFile = require('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseGeoPoint = require('./ParseGeoPoint'); + +var _ParseGeoPoint2 = _interopRequireDefault(_ParseGeoPoint); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseOp = require('./ParseOp'); + +var _ParseRelation = require('./ParseRelation'); + +var _ParseRelation2 = _interopRequireDefault(_ParseRelation); + +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 toString = Object.prototype.toString; + +function encode(value, disallowObjects, forcePointers, seen) { + if (value instanceof _ParseObject2.default) { + if (disallowObjects) { + throw new Error('Parse Objects not allowed here'); + } + var seenEntry = value.id ? value.className + ':' + value.id : value; + if (forcePointers || !seen || seen.indexOf(seenEntry) > -1 || value.dirty() || (0, _keys2.default)(value._getServerData()).length < 1) { + return value.toPointer(); + } + seen = seen.concat(seenEntry); + return value._toFullJSON(seen); + } + if (value instanceof _ParseOp.Op || value instanceof _ParseACL2.default || value instanceof _ParseGeoPoint2.default || value instanceof _ParseRelation2.default) { + return value.toJSON(); + } + if (value instanceof _ParseFile2.default) { + if (!value.url()) { + throw new Error('Tried to encode an unsaved file.'); + } + return value.toJSON(); + } + if (toString.call(value) === '[object Date]') { + if (isNaN(value)) { + throw new Error('Tried to encode an invalid date.'); + } + return { __type: 'Date', iso: value.toJSON() }; + } + if (toString.call(value) === '[object RegExp]' && typeof value.source === 'string') { + return value.source; + } + + if (Array.isArray(value)) { + return value.map(function (v) { + return encode(v, disallowObjects, forcePointers, seen); + }); + } + + if (value && (typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) === 'object') { + var output = {}; + for (var k in value) { + output[k] = encode(value[k], disallowObjects, forcePointers, seen); + } + return output; + } + + return value; +} \ No newline at end of file diff --git a/lib/browser/equals.js b/lib/browser/equals.js new file mode 100644 index 000000000..32edcc8a7 --- /dev/null +++ b/lib/browser/equals.js @@ -0,0 +1,84 @@ +'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); + +exports.default = equals; + +var _ParseACL = require('./ParseACL'); + +var _ParseACL2 = _interopRequireDefault(_ParseACL); + +var _ParseFile = require('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseGeoPoint = require('./ParseGeoPoint'); + +var _ParseGeoPoint2 = _interopRequireDefault(_ParseGeoPoint); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +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 equals(a, b) { + if ((typeof a === 'undefined' ? 'undefined' : (0, _typeof3.default)(a)) !== (typeof b === 'undefined' ? 'undefined' : (0, _typeof3.default)(b))) { + return false; + } + + if (!a || (typeof a === 'undefined' ? 'undefined' : (0, _typeof3.default)(a)) !== 'object') { + // a is a primitive + return a === b; + } + + if (Array.isArray(a) || Array.isArray(b)) { + if (!Array.isArray(a) || !Array.isArray(b)) { + return false; + } + if (a.length !== b.length) { + return false; + } + for (var i = a.length; i--;) { + if (!equals(a[i], b[i])) { + return false; + } + } + return true; + } + + if (a instanceof _ParseACL2.default || a instanceof _ParseFile2.default || a instanceof _ParseGeoPoint2.default || a instanceof _ParseObject2.default) { + return a.equals(b); + } + + if ((0, _keys2.default)(a).length !== (0, _keys2.default)(b).length) { + return false; + } + for (var k in a) { + if (!equals(a[k], b[k])) { + return false; + } + } + return true; +} \ No newline at end of file diff --git a/lib/browser/escape.js b/lib/browser/escape.js new file mode 100644 index 000000000..4693baaed --- /dev/null +++ b/lib/browser/escape.js @@ -0,0 +1,31 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = escape; +/** + * 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 encoded = { + '&': '&', + '<': '<', + '>': '>', + '/': '/', + '\'': ''', + '"': '"' +}; + +function escape(str) { + return str.replace(/[&<>\/'"]/g, function (char) { + return encoded[char]; + }); +} \ No newline at end of file diff --git a/lib/browser/isRevocableSession.js b/lib/browser/isRevocableSession.js new file mode 100644 index 000000000..92b8230e9 --- /dev/null +++ b/lib/browser/isRevocableSession.js @@ -0,0 +1,20 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = isRevocableSession; +/** + * 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 isRevocableSession(token) { + return token.indexOf('r:') > -1; +} \ No newline at end of file diff --git a/lib/browser/parseDate.js b/lib/browser/parseDate.js new file mode 100644 index 000000000..48e061b05 --- /dev/null +++ b/lib/browser/parseDate.js @@ -0,0 +1,34 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = parseDate; +/** + * 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 parseDate(iso8601) { + var regexp = new RegExp('^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})' + 'T' + '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})' + '(.([0-9]+))?' + 'Z$'); + var match = regexp.exec(iso8601); + if (!match) { + return null; + } + + var year = match[1] || 0; + var month = (match[2] || 1) - 1; + var day = match[3] || 0; + var hour = match[4] || 0; + var minute = match[5] || 0; + var second = match[6] || 0; + var milli = match[8] || 0; + + return new Date(Date.UTC(year, month, day, hour, minute, second, milli)); +} \ No newline at end of file diff --git a/lib/browser/unique.js b/lib/browser/unique.js new file mode 100644 index 000000000..157b3560a --- /dev/null +++ b/lib/browser/unique.js @@ -0,0 +1,45 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = unique; + +var _arrayContainsObject = require('./arrayContainsObject'); + +var _arrayContainsObject2 = _interopRequireDefault(_arrayContainsObject); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +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 unique(arr) { + var uniques = []; + arr.forEach(function (value) { + if (value instanceof _ParseObject2.default) { + if (!(0, _arrayContainsObject2.default)(uniques, value)) { + uniques.push(value); + } + } else { + if (uniques.indexOf(value) < 0) { + uniques.push(value); + } + } + }); + return uniques; +} \ No newline at end of file diff --git a/lib/browser/unsavedChildren.js b/lib/browser/unsavedChildren.js new file mode 100644 index 000000000..073efd70b --- /dev/null +++ b/lib/browser/unsavedChildren.js @@ -0,0 +1,102 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +exports.default = unsavedChildren; + +var _ParseFile = require('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseRelation = require('./ParseRelation'); + +var _ParseRelation2 = _interopRequireDefault(_ParseRelation); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Return an array of unsaved children, which are either Parse Objects or Files. + * If it encounters any dirty Objects without Ids, it will throw an exception. + */ +function unsavedChildren(obj, allowDeepUnsaved) { + var encountered = { + objects: {}, + files: [] + }; + var identifier = obj.className + ':' + obj._getId(); + encountered.objects[identifier] = obj.dirty() ? obj : true; + var attributes = obj.attributes; + for (var attr in attributes) { + if ((0, _typeof3.default)(attributes[attr]) === 'object') { + traverse(attributes[attr], encountered, false, !!allowDeepUnsaved); + } + } + var unsaved = []; + for (var id in encountered.objects) { + if (id !== identifier && encountered.objects[id] !== true) { + unsaved.push(encountered.objects[id]); + } + } + return unsaved.concat(encountered.files); +} /** + * 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 traverse(obj, encountered, shouldThrow, allowDeepUnsaved) { + if (obj instanceof _ParseObject2.default) { + if (!obj.id && shouldThrow) { + throw new Error('Cannot create a pointer to an unsaved Object.'); + } + var identifier = obj.className + ':' + obj._getId(); + if (!encountered.objects[identifier]) { + encountered.objects[identifier] = obj.dirty() ? obj : true; + var attributes = obj.attributes; + for (var attr in attributes) { + if ((0, _typeof3.default)(attributes[attr]) === 'object') { + traverse(attributes[attr], encountered, !allowDeepUnsaved, allowDeepUnsaved); + } + } + } + return; + } + if (obj instanceof _ParseFile2.default) { + if (!obj.url() && encountered.files.indexOf(obj) < 0) { + encountered.files.push(obj); + } + return; + } + if (obj instanceof _ParseRelation2.default) { + return; + } + if (Array.isArray(obj)) { + obj.forEach(function (el) { + if ((typeof el === 'undefined' ? 'undefined' : (0, _typeof3.default)(el)) === 'object') { + traverse(el, encountered, shouldThrow, allowDeepUnsaved); + } + }); + } + for (var k in obj) { + if ((0, _typeof3.default)(obj[k]) === 'object') { + traverse(obj[k], encountered, shouldThrow, allowDeepUnsaved); + } + } +} \ No newline at end of file diff --git a/lib/node/Analytics.js b/lib/node/Analytics.js new file mode 100644 index 000000000..e5bddf088 --- /dev/null +++ b/lib/node/Analytics.js @@ -0,0 +1,89 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.track = track; + +var _CoreManager = require('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Parse.Analytics provides an interface to Parse's logging and analytics + * backend. + * + * @class Parse.Analytics + * @static + */ + +/** + * Tracks the occurrence of a custom event with additional dimensions. + * Parse will store a data point at the time of invocation with the given + * event name. + * + * Dimensions will allow segmentation of the occurrences of this custom + * event. Keys and values should be {@code String}s, and will throw + * otherwise. + * + * To track a user signup along with additional metadata, consider the + * following: + *
          + * 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.

          + */ + +var ParseACL = function () { + function ParseACL(arg1) { + (0, _classCallCheck3.default)(this, ParseACL); + + this.permissionsById = {}; + if (arg1 && (typeof arg1 === 'undefined' ? 'undefined' : (0, _typeof3.default)(arg1)) === 'object') { + if (arg1 instanceof _ParseUser2.default) { + this.setReadAccess(arg1, true); + this.setWriteAccess(arg1, true); + } else { + for (var userId in arg1) { + var accessList = arg1[userId]; + if (typeof userId !== 'string') { + throw new TypeError('Tried to create an ACL with an invalid user id.'); + } + this.permissionsById[userId] = {}; + for (var permission in accessList) { + var allowed = accessList[permission]; + if (permission !== 'read' && permission !== 'write') { + throw new TypeError('Tried to create an ACL with an invalid permission type.'); + } + if (typeof allowed !== 'boolean') { + throw new TypeError('Tried to create an ACL with an invalid permission value.'); + } + this.permissionsById[userId][permission] = allowed; + } + } + } + } else if (typeof arg1 === 'function') { + throw new TypeError('ParseACL constructed with a function. Did you forget ()?'); + } + } + + /** + * Returns a JSON-encoded version of the ACL. + * @method toJSON + * @return {Object} + */ + + (0, _createClass3.default)(ParseACL, [{ + key: 'toJSON', + value: function () { + var permissions = {}; + for (var p in this.permissionsById) { + permissions[p] = this.permissionsById[p]; + } + return permissions; + } + + /** + * Returns whether this ACL is equal to another object + * @method equals + * @param other The other object to compare to + * @return {Boolean} + */ + + }, { + key: 'equals', + value: function (other) { + if (!(other instanceof ParseACL)) { + return false; + } + var users = (0, _keys2.default)(this.permissionsById); + var otherUsers = (0, _keys2.default)(other.permissionsById); + if (users.length !== otherUsers.length) { + return false; + } + for (var u in this.permissionsById) { + if (!other.permissionsById[u]) { + return false; + } + if (this.permissionsById[u].read !== other.permissionsById[u].read) { + return false; + } + if (this.permissionsById[u].write !== other.permissionsById[u].write) { + return false; + } + } + return true; + } + }, { + key: '_setAccess', + value: function (accessType, userId, allowed) { + if (userId instanceof _ParseUser2.default) { + userId = userId.id; + } else if (userId instanceof _ParseRole2.default) { + var name = userId.getName(); + if (!name) { + throw new TypeError('Role must have a name'); + } + userId = 'role:' + name; + } + if (typeof userId !== 'string') { + throw new TypeError('userId must be a string.'); + } + if (typeof allowed !== 'boolean') { + throw new TypeError('allowed must be either true or false.'); + } + var permissions = this.permissionsById[userId]; + if (!permissions) { + if (!allowed) { + // The user already doesn't have this permission, so no action is needed + return; + } else { + permissions = {}; + this.permissionsById[userId] = permissions; + } + } + + if (allowed) { + this.permissionsById[userId][accessType] = true; + } else { + delete permissions[accessType]; + if ((0, _keys2.default)(permissions).length === 0) { + delete this.permissionsById[userId]; + } + } + } + }, { + key: '_getAccess', + value: function (accessType, userId) { + if (userId instanceof _ParseUser2.default) { + userId = userId.id; + if (!userId) { + throw new Error('Cannot get access for a ParseUser without an ID'); + } + } else if (userId instanceof _ParseRole2.default) { + var name = userId.getName(); + if (!name) { + throw new TypeError('Role must have a name'); + } + userId = 'role:' + name; + } + var permissions = this.permissionsById[userId]; + if (!permissions) { + return false; + } + return !!permissions[accessType]; + } + + /** + * Sets whether the given user is allowed to read this object. + * @method setReadAccess + * @param userId An instance of Parse.User or its objectId. + * @param {Boolean} allowed Whether that user should have read access. + */ + + }, { + key: 'setReadAccess', + value: function (userId, allowed) { + this._setAccess('read', userId, allowed); + } + + /** + * Get whether the given user id is *explicitly* allowed to read this object. + * Even if this returns false, the user may still be able to access it if + * getPublicReadAccess returns true or a role that the user belongs to has + * write access. + * @method getReadAccess + * @param userId An instance of Parse.User or its objectId, or a Parse.Role. + * @return {Boolean} + */ + + }, { + key: 'getReadAccess', + value: function (userId) { + return this._getAccess('read', userId); + } + + /** + * Sets whether the given user id is allowed to write this object. + * @method setWriteAccess + * @param userId An instance of Parse.User or its objectId, or a Parse.Role.. + * @param {Boolean} allowed Whether that user should have write access. + */ + + }, { + key: 'setWriteAccess', + value: function (userId, allowed) { + this._setAccess('write', userId, allowed); + } + + /** + * Gets whether the given user id is *explicitly* allowed to write this object. + * Even if this returns false, the user may still be able to write it if + * getPublicWriteAccess returns true or a role that the user belongs to has + * write access. + * @method getWriteAccess + * @param userId An instance of Parse.User or its objectId, or a Parse.Role. + * @return {Boolean} + */ + + }, { + key: 'getWriteAccess', + value: function (userId) { + return this._getAccess('write', userId); + } + + /** + * Sets whether the public is allowed to read this object. + * @method setPublicReadAccess + * @param {Boolean} allowed + */ + + }, { + key: 'setPublicReadAccess', + value: function (allowed) { + this.setReadAccess(PUBLIC_KEY, allowed); + } + + /** + * Gets whether the public is allowed to read this object. + * @method getPublicReadAccess + * @return {Boolean} + */ + + }, { + key: 'getPublicReadAccess', + value: function () { + return this.getReadAccess(PUBLIC_KEY); + } + + /** + * Sets whether the public is allowed to write this object. + * @method setPublicWriteAccess + * @param {Boolean} allowed + */ + + }, { + key: 'setPublicWriteAccess', + value: function (allowed) { + this.setWriteAccess(PUBLIC_KEY, allowed); + } + + /** + * Gets whether the public is allowed to write this object. + * @method getPublicWriteAccess + * @return {Boolean} + */ + + }, { + key: 'getPublicWriteAccess', + value: function () { + return this.getWriteAccess(PUBLIC_KEY); + } + + /** + * Gets whether users belonging to the given role are allowed + * to read this object. Even if this returns false, the role may + * still be able to write it if a parent role has read access. + * + * @method getRoleReadAccess + * @param role The name of the role, or a Parse.Role object. + * @return {Boolean} true if the role has read access. false otherwise. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + + }, { + key: 'getRoleReadAccess', + value: function (role) { + if (role instanceof _ParseRole2.default) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + return this.getReadAccess('role:' + role); + } + + /** + * Gets whether users belonging to the given role are allowed + * to write this object. Even if this returns false, the role may + * still be able to write it if a parent role has write access. + * + * @method getRoleWriteAccess + * @param role The name of the role, or a Parse.Role object. + * @return {Boolean} true if the role has write access. false otherwise. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + + }, { + key: 'getRoleWriteAccess', + value: function (role) { + if (role instanceof _ParseRole2.default) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + return this.getWriteAccess('role:' + role); + } + + /** + * Sets whether users belonging to the given role are allowed + * to read this object. + * + * @method setRoleReadAccess + * @param role The name of the role, or a Parse.Role object. + * @param {Boolean} allowed Whether the given role can read this object. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + + }, { + key: 'setRoleReadAccess', + value: function (role, allowed) { + if (role instanceof _ParseRole2.default) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + this.setReadAccess('role:' + role, allowed); + } + + /** + * Sets whether users belonging to the given role are allowed + * to write this object. + * + * @method setRoleWriteAccess + * @param role The name of the role, or a Parse.Role object. + * @param {Boolean} allowed Whether the given role can write this object. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + + }, { + key: 'setRoleWriteAccess', + value: function (role, allowed) { + if (role instanceof _ParseRole2.default) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + this.setWriteAccess('role:' + role, allowed); + } + }]); + return ParseACL; +}(); + +exports.default = ParseACL; \ No newline at end of file diff --git a/lib/node/ParseConfig.js b/lib/node/ParseConfig.js new file mode 100644 index 000000000..925e3a864 --- /dev/null +++ b/lib/node/ParseConfig.js @@ -0,0 +1,228 @@ +'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); + +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 _decode = require('./decode'); + +var _decode2 = _interopRequireDefault(_decode); + +var _encode = require('./encode'); + +var _encode2 = _interopRequireDefault(_encode); + +var _escape2 = require('./escape'); + +var _escape3 = _interopRequireDefault(_escape2); + +var _ParseError = require('./ParseError'); + +var _ParseError2 = _interopRequireDefault(_ParseError); + +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 }; +} + +/** + * Parse.Config is a local representation of configuration data that + * can be set from the Parse dashboard. + * + * @class Parse.Config + * @constructor + */ + +var ParseConfig = function () { + function ParseConfig() { + (0, _classCallCheck3.default)(this, ParseConfig); + + this.attributes = {}; + this._escapedAttributes = {}; + } + + /** + * Gets the value of an attribute. + * @method get + * @param {String} attr The name of an attribute. + */ + + (0, _createClass3.default)(ParseConfig, [{ + key: 'get', + value: function (attr) { + return this.attributes[attr]; + } + + /** + * Gets the HTML-escaped value of an attribute. + * @method escape + * @param {String} attr The name of an attribute. + */ + + }, { + key: 'escape', + value: function (attr) { + var html = this._escapedAttributes[attr]; + if (html) { + return html; + } + var val = this.attributes[attr]; + var escaped = ''; + if (val != null) { + escaped = (0, _escape3.default)(val.toString()); + } + this._escapedAttributes[attr] = escaped; + return escaped; + } + + /** + * Retrieves the most recently-fetched configuration object, either from + * memory or from local storage if necessary. + * + * @method current + * @static + * @return {Config} The most recently-fetched Parse.Config if it + * exists, else an empty Parse.Config. + */ + + }], [{ + key: 'current', + value: function () { + var controller = _CoreManager2.default.getConfigController(); + return controller.current(); + } + + /** + * Gets a new configuration object from the server. + * @method get + * @static + * @param {Object} options A Backbone-style options object. + * Valid options are:
            + *
          • success: Function to call when the get completes successfully. + *
          • error: Function to call when the get fails. + *
          + * @return {Parse.Promise} A promise that is resolved with a newly-created + * configuration object when the get completes. + */ + + }, { + key: 'get', + value: function (options) { + options = options || {}; + + var controller = _CoreManager2.default.getConfigController(); + return controller.get()._thenRunCallbacks(options); + } + }]); + return ParseConfig; +}(); /** + * 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 = ParseConfig; + +var currentConfig = null; + +var CURRENT_CONFIG_KEY = 'currentConfig'; + +function decodePayload(data) { + try { + var json = JSON.parse(data); + if (json && (typeof json === 'undefined' ? 'undefined' : (0, _typeof3.default)(json)) === 'object') { + return (0, _decode2.default)(json); + } + } catch (e) { + return null; + } +} + +var DefaultController = { + current: function () { + if (currentConfig) { + return currentConfig; + } + + var config = new ParseConfig(); + var storagePath = _Storage2.default.generatePath(CURRENT_CONFIG_KEY); + var configData; + if (!_Storage2.default.async()) { + configData = _Storage2.default.getItem(storagePath); + + if (configData) { + var attributes = decodePayload(configData); + if (attributes) { + config.attributes = attributes; + currentConfig = config; + } + } + return config; + } + // Return a promise for async storage controllers + return _Storage2.default.getItemAsync(storagePath).then(function (configData) { + if (configData) { + var attributes = decodePayload(configData); + if (attributes) { + config.attributes = attributes; + currentConfig = config; + } + } + return config; + }); + }, + get: function () { + var RESTController = _CoreManager2.default.getRESTController(); + + return RESTController.request('GET', 'config', {}, {}).then(function (response) { + if (!response || !response.params) { + var error = new _ParseError2.default(_ParseError2.default.INVALID_JSON, 'Config JSON response invalid.'); + return _ParsePromise2.default.error(error); + } + + var config = new ParseConfig(); + config.attributes = {}; + for (var attr in response.params) { + config.attributes[attr] = (0, _decode2.default)(response.params[attr]); + } + currentConfig = config; + return _Storage2.default.setItemAsync(_Storage2.default.generatePath(CURRENT_CONFIG_KEY), (0, _stringify2.default)(response.params)).then(function () { + return config; + }); + }); + } +}; + +_CoreManager2.default.setConfigController(DefaultController); \ No newline at end of file diff --git a/lib/node/ParseError.js b/lib/node/ParseError.js new file mode 100644 index 000000000..b3bad4512 --- /dev/null +++ b/lib/node/ParseError.js @@ -0,0 +1,507 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +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. + */ + +/** + * Constructs a new Parse.Error object with the given code and message. + * @class Parse.Error + * @constructor + * @param {Number} code An error code constant from 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:
            + *
          • success: A Backbone-style success callback. + *
          • error: An Backbone-style error callback. + *
          • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
          • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
          + * @return {Parse.Promise} A promise that is fulfilled when the fetch + * completes. + */ + + }, { + key: 'fetch', + value: function (options) { + options = options || {}; + var fetchOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + fetchOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + fetchOptions.sessionToken = options.sessionToken; + } + var controller = _CoreManager2.default.getObjectController(); + return controller.fetch(this, true, fetchOptions)._thenRunCallbacks(options); + } + + /** + * Set a hash of model attributes, and save the model to the server. + * updatedAt will be updated when the request returns. + * You can either call it as:
          +     *   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:
            + *
          • success: A Backbone-style success callback. + *
          • error: An Backbone-style error callback. + *
          • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
          • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
          + * @return {Parse.Promise} A promise that is fulfilled when the save + * completes. + */ + + }, { + key: 'save', + value: function (arg1, arg2, arg3) { + var _this = this; + + var attrs; + var options; + if ((typeof arg1 === 'undefined' ? 'undefined' : (0, _typeof3.default)(arg1)) === 'object' || typeof arg1 === 'undefined') { + attrs = arg1; + if ((typeof arg2 === 'undefined' ? 'undefined' : (0, _typeof3.default)(arg2)) === 'object') { + options = arg2; + } + } else { + attrs = {}; + attrs[arg1] = arg2; + options = arg3; + } + + // Support save({ success: function() {}, error: function() {} }) + if (!options && attrs) { + options = {}; + if (typeof attrs.success === 'function') { + options.success = attrs.success; + delete attrs.success; + } + if (typeof attrs.error === 'function') { + options.error = attrs.error; + delete attrs.error; + } + } + + if (attrs) { + var validation = this.validate(attrs); + if (validation) { + if (options && typeof options.error === 'function') { + options.error(this, validation); + } + return _ParsePromise2.default.error(validation); + } + this.set(attrs, options); + } + + options = options || {}; + var saveOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + saveOptions.useMasterKey = !!options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken') && typeof options.sessionToken === 'string') { + saveOptions.sessionToken = options.sessionToken; + } + + var controller = _CoreManager2.default.getObjectController(); + var unsaved = (0, _unsavedChildren2.default)(this); + return controller.save(unsaved, saveOptions).then(function () { + return controller.save(_this, saveOptions); + })._thenRunCallbacks(options, this); + } + + /** + * Destroy this model on the server if it was already persisted. + * If `wait: true` is passed, waits for the server to respond + * before removal. + * + * @method destroy + * @param {Object} options A Backbone-style callback object. + * Valid options are:
            + *
          • success: A Backbone-style success callback + *
          • error: An Backbone-style error callback. + *
          • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
          • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
          + * @return {Parse.Promise} A promise that is fulfilled when the destroy + * completes. + */ + + }, { + key: 'destroy', + value: function (options) { + options = options || {}; + var destroyOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + destroyOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + destroyOptions.sessionToken = options.sessionToken; + } + if (!this.id) { + return _ParsePromise2.default.as()._thenRunCallbacks(options); + } + return _CoreManager2.default.getObjectController().destroy(this, destroyOptions)._thenRunCallbacks(options); + } + + /** Static methods **/ + + }, { + key: 'attributes', + get: function () { + var stateController = _CoreManager2.default.getObjectStateController(); + return (0, _freeze2.default)(stateController.estimateAttributes(this._getStateIdentifier())); + } + + /** + * The first time this object was saved on the server. + * @property createdAt + * @type Date + */ + + }, { + key: 'createdAt', + get: function () { + return this._getServerData().createdAt; + } + + /** + * The last time this object was updated on the server. + * @property updatedAt + * @type Date + */ + + }, { + key: 'updatedAt', + get: function () { + return this._getServerData().updatedAt; + } + }], [{ + key: '_clearAllState', + value: function () { + var stateController = _CoreManager2.default.getObjectStateController(); + stateController.clearAllState(); + } + + /** + * Fetches the given list of Parse.Object. + * If any error is encountered, stops and calls the error handler. + * + *
          +     *   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:
            + *
          • success: A Backbone-style success callback. + *
          • error: An Backbone-style error callback. + *
          + */ + + }, { + key: 'fetchAll', + value: function (list, options) { + var options = options || {}; + + var queryOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + queryOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + queryOptions.sessionToken = options.sessionToken; + } + return _CoreManager2.default.getObjectController().fetch(list, true, queryOptions)._thenRunCallbacks(options); + } + + /** + * Fetches the given list of Parse.Object if needed. + * If any error is encountered, stops and calls the error handler. + * + *
          +     *   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:
            + *
          • success: A Backbone-style success callback. + *
          • error: An Backbone-style error callback. + *
          + */ + + }, { + key: 'fetchAllIfNeeded', + value: function (list, options) { + var options = options || {}; + + var queryOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + queryOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + queryOptions.sessionToken = options.sessionToken; + } + return _CoreManager2.default.getObjectController().fetch(list, false, queryOptions)._thenRunCallbacks(options); + } + + /** + * Destroy the given list of models on the server if it was already persisted. + * + *

          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: + * + *

            + *
          • A Parse.Error.AGGREGATE_ERROR. This object's "errors" property is an + * array of other Parse.Error objects. Each error object in this array + * has an "object" property that references the object that could not be + * deleted (for instance, because that object could not be found).
          • + *
          • A non-aggregate Parse.Error. This indicates a serious error that + * caused the delete operation to be aborted partway through (for + * instance, a connection failure in the middle of the delete).
          • + *
          + * + *
          +     *   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:
            + *
          • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
          • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
          + * @return {Parse.Promise} A promise that is fulfilled when the destroyAll + * completes. + */ + + }, { + key: 'destroyAll', + value: function (list, options) { + var options = options || {}; + + var destroyOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + destroyOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + destroyOptions.sessionToken = options.sessionToken; + } + return _CoreManager2.default.getObjectController().destroy(list, destroyOptions)._thenRunCallbacks(options); + } + + /** + * Saves the given list of Parse.Object. + * If any error is encountered, stops and calls the error handler. + * + *
          +     *   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:
            + *
          • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
          • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
          + */ + + }, { + key: 'saveAll', + value: function (list, options) { + var options = options || {}; + + var saveOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + saveOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + saveOptions.sessionToken = options.sessionToken; + } + return _CoreManager2.default.getObjectController().save(list, saveOptions)._thenRunCallbacks(options); + } + + /** + * Creates a reference to a subclass of Parse.Object with the given id. This + * does not exist on Parse.Object, only on subclasses. + * + *

          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:
            + *
          • success: A Backbone-style success callback + *
          • error: An Backbone-style error callback. + *
          • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
          • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
          + * + * @return {Parse.Promise} A promise that is resolved with the result when + * the query completes. + */ + + }, { + key: 'get', + value: function (objectId, options) { + this.equalTo('objectId', objectId); + + var firstOptions = {}; + if (options && options.hasOwnProperty('useMasterKey')) { + firstOptions.useMasterKey = options.useMasterKey; + } + if (options && options.hasOwnProperty('sessionToken')) { + firstOptions.sessionToken = options.sessionToken; + } + + return this.first(firstOptions).then(function (response) { + if (response) { + return response; + } + + var errorObject = new _ParseError2.default(_ParseError2.default.OBJECT_NOT_FOUND, 'Object not found.'); + return _ParsePromise2.default.error(errorObject); + })._thenRunCallbacks(options, null); + } + + /** + * Retrieves a list of ParseObjects that satisfy this query. + * Either options.success or options.error is called when the find + * completes. + * + * @method find + * @param {Object} options A Backbone-style options object. Valid options + * are:
            + *
          • success: Function to call when the find completes successfully. + *
          • error: Function to call when the find fails. + *
          • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
          • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
          + * + * @return {Parse.Promise} A promise that is resolved with the results when + * the query completes. + */ + + }, { + key: 'find', + value: function (options) { + var _this2 = this; + + options = options || {}; + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var controller = _CoreManager2.default.getQueryController(); + + var select = this._select; + + return controller.find(this.className, this.toJSON(), findOptions).then(function (response) { + return response.results.map(function (data) { + // In cases of relations, the server may send back a className + // on the top level of the payload + var override = response.className || _this2.className; + if (!data.className) { + data.className = override; + } + + // Make sure the data object contains keys for all objects that + // have been requested with a select, so that our cached state + // updates correctly. + if (select) { + handleSelectResult(data, select); + } + + return _ParseObject2.default.fromJSON(data, !select); + }); + })._thenRunCallbacks(options); + } + + /** + * Counts the number of objects that match this query. + * Either options.success or options.error is called when the count + * completes. + * + * @method count + * @param {Object} options A Backbone-style options object. Valid options + * are:
            + *
          • success: Function to call when the count completes successfully. + *
          • error: Function to call when the find fails. + *
          • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
          • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
          + * + * @return {Parse.Promise} A promise that is resolved with the count when + * the query completes. + */ + + }, { + key: 'count', + value: function (options) { + options = options || {}; + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var controller = _CoreManager2.default.getQueryController(); + + var params = this.toJSON(); + params.limit = 0; + params.count = 1; + + return controller.find(this.className, params, findOptions).then(function (result) { + return result.count; + })._thenRunCallbacks(options); + } + + /** + * Retrieves at most one Parse.Object that satisfies this query. + * + * Either options.success or options.error is called when it completes. + * success is passed the object if there is one. otherwise, undefined. + * + * @method first + * @param {Object} options A Backbone-style options object. Valid options + * are:
            + *
          • success: Function to call when the find completes successfully. + *
          • error: Function to call when the find fails. + *
          • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
          • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
          + * + * @return {Parse.Promise} A promise that is resolved with the object when + * the query completes. + */ + + }, { + key: 'first', + value: function (options) { + var _this3 = this; + + options = options || {}; + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var controller = _CoreManager2.default.getQueryController(); + + var params = this.toJSON(); + params.limit = 1; + + var select = this._select; + + return controller.find(this.className, params, findOptions).then(function (response) { + var objects = response.results; + if (!objects[0]) { + return undefined; + } + if (!objects[0].className) { + objects[0].className = _this3.className; + } + + // Make sure the data object contains keys for all objects that + // have been requested with a select, so that our cached state + // updates correctly. + if (select) { + handleSelectResult(objects[0], select); + } + + return _ParseObject2.default.fromJSON(objects[0], !select); + })._thenRunCallbacks(options); + } + + /** + * Iterates over each result of a query, calling a callback for each one. If + * the callback returns a promise, the iteration will not continue until + * that promise has been fulfilled. If the callback returns a rejected + * promise, then iteration will stop with that error. The items are + * processed in an unspecified order. The query may not have any sort order, + * and may not use limit or skip. + * @method each + * @param {Function} callback Callback that will be called with each result + * of the query. + * @param {Object} options A Backbone-style options object. Valid options + * are:
            + *
          • success: Function to call when the iteration completes successfully. + *
          • error: Function to call when the iteration fails. + *
          • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
          • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
          + * @return {Parse.Promise} A promise that will be fulfilled once the + * iteration has completed. + */ + + }, { + key: 'each', + value: function (callback, options) { + options = options || {}; + + if (this._order || this._skip || this._limit >= 0) { + return _ParsePromise2.default.error('Cannot iterate on a query with sort, skip, or limit.')._thenRunCallbacks(options); + } + + new _ParsePromise2.default(); + + + var query = new ParseQuery(this.className); + // We can override the batch size from the options. + // This is undocumented, but useful for testing. + query._limit = options.batchSize || 100; + query._include = this._include.map(function (i) { + return i; + }); + if (this._select) { + query._select = this._select.map(function (s) { + return s; + }); + } + + query._where = {}; + for (var attr in this._where) { + var val = this._where[attr]; + if (Array.isArray(val)) { + query._where[attr] = val.map(function (v) { + return v; + }); + } else if (val && (typeof val === 'undefined' ? 'undefined' : (0, _typeof3.default)(val)) === 'object') { + var conditionMap = {}; + query._where[attr] = conditionMap; + for (var cond in val) { + conditionMap[cond] = val[cond]; + } + } else { + query._where[attr] = val; + } + } + + query.ascending('objectId'); + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var finished = false; + return _ParsePromise2.default._continueWhile(function () { + return !finished; + }, function () { + return query.find(findOptions).then(function (results) { + var callbacksDone = _ParsePromise2.default.as(); + results.forEach(function (result) { + callbacksDone = callbacksDone.then(function () { + return callback(result); + }); + }); + + return callbacksDone.then(function () { + if (results.length >= query._limit) { + query.greaterThan('objectId', results[results.length - 1].id); + } else { + finished = true; + } + }); + }); + })._thenRunCallbacks(options); + } + + /** Query Conditions **/ + + /** + * Adds a constraint to the query that requires a particular key's value to + * be equal to the provided value. + * @method equalTo + * @param {String} key The key to check. + * @param value The value that the Parse.Object must contain. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'equalTo', + value: function (key, value) { + if (typeof value === 'undefined') { + return this.doesNotExist(key); + } + + this._where[key] = (0, _encode2.default)(value, false, true); + return this; + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be not equal to the provided value. + * @method notEqualTo + * @param {String} key The key to check. + * @param value The value that must not be equalled. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'notEqualTo', + value: function (key, value) { + return this._addCondition(key, '$ne', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be less than the provided value. + * @method lessThan + * @param {String} key The key to check. + * @param value The value that provides an upper bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'lessThan', + value: function (key, value) { + return this._addCondition(key, '$lt', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be greater than the provided value. + * @method greaterThan + * @param {String} key The key to check. + * @param value The value that provides an lower bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'greaterThan', + value: function (key, value) { + return this._addCondition(key, '$gt', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be less than or equal to the provided value. + * @method lessThanOrEqualTo + * @param {String} key The key to check. + * @param value The value that provides an upper bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'lessThanOrEqualTo', + value: function (key, value) { + return this._addCondition(key, '$lte', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be greater than or equal to the provided value. + * @method greaterThanOrEqualTo + * @param {String} key The key to check. + * @param value The value that provides an lower bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'greaterThanOrEqualTo', + value: function (key, value) { + return this._addCondition(key, '$gte', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be contained in the provided list of values. + * @method containedIn + * @param {String} key The key to check. + * @param {Array} values The values that will match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'containedIn', + value: function (key, value) { + return this._addCondition(key, '$in', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * not be contained in the provided list of values. + * @method notContainedIn + * @param {String} key The key to check. + * @param {Array} values The values that will not match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'notContainedIn', + value: function (key, value) { + return this._addCondition(key, '$nin', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * contain each one of the provided list of values. + * @method containsAll + * @param {String} key The key to check. This key's value must be an array. + * @param {Array} values The values that will match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'containsAll', + value: function (key, values) { + return this._addCondition(key, '$all', values); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * contain each one of the provided list of values starting with given strings. + * @method containsAllStartingWith + * @param {String} key The key to check. This key's value must be an array. + * @param {Array} values The string values that will match as starting string. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'containsAllStartingWith', + value: function (key, values) { + var _this = this; + if (!Array.isArray(values)) { + values = [values]; + } + + values = values.map(function (value) { + return { "$regex": _this._regexStartWith(value) }; + }); + + return this.containsAll(key, values); + } + + /** + * Adds a constraint for finding objects that contain the given key. + * @method exists + * @param {String} key The key that should exist. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'exists', + value: function (key) { + return this._addCondition(key, '$exists', true); + } + + /** + * Adds a constraint for finding objects that do not contain a given key. + * @method doesNotExist + * @param {String} key The key that should not exist + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'doesNotExist', + value: function (key) { + return this._addCondition(key, '$exists', false); + } + + /** + * Adds a regular expression constraint for finding string values that match + * the provided regular expression. + * This may be slow for large datasets. + * @method matches + * @param {String} key The key that the string to match is stored in. + * @param {RegExp} regex The regular expression pattern to match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'matches', + value: function (key, regex, modifiers) { + this._addCondition(key, '$regex', regex); + if (!modifiers) { + modifiers = ''; + } + if (regex.ignoreCase) { + modifiers += 'i'; + } + if (regex.multiline) { + modifiers += 'm'; + } + if (modifiers.length) { + this._addCondition(key, '$options', modifiers); + } + return this; + } + + /** + * Adds a constraint that requires that a key's value matches a Parse.Query + * constraint. + * @method matchesQuery + * @param {String} key The key that the contains the object to match the + * query. + * @param {Parse.Query} query The query that should match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'matchesQuery', + value: function (key, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$inQuery', queryJSON); + } + + /** + * Adds a constraint that requires that a key's value not matches a + * Parse.Query constraint. + * @method doesNotMatchQuery + * @param {String} key The key that the contains the object to match the + * query. + * @param {Parse.Query} query The query that should not match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'doesNotMatchQuery', + value: function (key, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$notInQuery', queryJSON); + } + + /** + * Adds a constraint that requires that a key's value matches a value in + * an object returned by a different Parse.Query. + * @method matchesKeyInQuery + * @param {String} key The key that contains the value that is being + * matched. + * @param {String} queryKey The key in the objects returned by the query to + * match against. + * @param {Parse.Query} query The query to run. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'matchesKeyInQuery', + value: function (key, queryKey, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$select', { + key: queryKey, + query: queryJSON + }); + } + + /** + * Adds a constraint that requires that a key's value not match a value in + * an object returned by a different Parse.Query. + * @method doesNotMatchKeyInQuery + * @param {String} key The key that contains the value that is being + * excluded. + * @param {String} queryKey The key in the objects returned by the query to + * match against. + * @param {Parse.Query} query The query to run. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'doesNotMatchKeyInQuery', + value: function (key, queryKey, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$dontSelect', { + key: queryKey, + query: queryJSON + }); + } + + /** + * Adds a constraint for finding string values that contain a provided + * string. This may be slow for large datasets. + * @method contains + * @param {String} key The key that the string to match is stored in. + * @param {String} substring The substring that the value must contain. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'contains', + value: function (key, value) { + if (typeof value !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + return this._addCondition(key, '$regex', quote(value)); + } + + /** + * Adds a constraint for finding string values that start with a provided + * string. This query will use the backend index, so it will be fast even + * for large datasets. + * @method startsWith + * @param {String} key The key that the string to match is stored in. + * @param {String} prefix The substring that the value must start with. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'startsWith', + value: function (key, value) { + if (typeof value !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + return this._addCondition(key, '$regex', this._regexStartWith(value)); + } + + /** + * Adds a constraint for finding string values that end with a provided + * string. This will be slow for large datasets. + * @method endsWith + * @param {String} key The key that the string to match is stored in. + * @param {String} suffix The substring that the value must end with. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'endsWith', + value: function (key, value) { + if (typeof value !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + return this._addCondition(key, '$regex', quote(value) + '$'); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given. + * @method near + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'near', + value: function (key, point) { + if (!(point instanceof _ParseGeoPoint2.default)) { + // Try to cast it as a GeoPoint + point = new _ParseGeoPoint2.default(point); + } + return this._addCondition(key, '$nearSphere', point); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * @method withinRadians + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in radians) of results to + * return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'withinRadians', + value: function (key, point, distance) { + this.near(key, point); + return this._addCondition(key, '$maxDistance', distance); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * Radius of earth used is 3958.8 miles. + * @method withinMiles + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in miles) of results to + * return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'withinMiles', + value: function (key, point, distance) { + return this.withinRadians(key, point, distance / 3958.8); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * Radius of earth used is 6371.0 kilometers. + * @method withinKilometers + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in kilometers) of results + * to return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'withinKilometers', + value: function (key, point, distance) { + return this.withinRadians(key, point, distance / 6371.0); + } + + /** + * Adds a constraint to the query that requires a particular key's + * coordinates be contained within a given rectangular geographic bounding + * box. + * @method withinGeoBox + * @param {String} key The key to be constrained. + * @param {Parse.GeoPoint} southwest + * The lower-left inclusive corner of the box. + * @param {Parse.GeoPoint} northeast + * The upper-right inclusive corner of the box. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'withinGeoBox', + value: function (key, southwest, northeast) { + if (!(southwest instanceof _ParseGeoPoint2.default)) { + southwest = new _ParseGeoPoint2.default(southwest); + } + if (!(northeast instanceof _ParseGeoPoint2.default)) { + northeast = new _ParseGeoPoint2.default(northeast); + } + this._addCondition(key, '$within', { '$box': [southwest, northeast] }); + return this; + } + + /** Query Orderings **/ + + /** + * Sorts the results in ascending order by the given key. + * + * @method ascending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'ascending', + value: function () { + this._order = []; + + for (var _len = arguments.length, keys = Array(_len), _key = 0; _key < _len; _key++) { + keys[_key] = arguments[_key]; + } + + return this.addAscending.apply(this, keys); + } + + /** + * Sorts the results in ascending order by the given key, + * but can also add secondary sort descriptors without overwriting _order. + * + * @method addAscending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'addAscending', + value: function () { + var _this4 = this; + + if (!this._order) { + this._order = []; + } + + for (var _len2 = arguments.length, keys = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + keys[_key2] = arguments[_key2]; + } + + keys.forEach(function (key) { + if (Array.isArray(key)) { + key = key.join(); + } + _this4._order = _this4._order.concat(key.replace(/\s/g, '').split(',')); + }); + + return this; + } + + /** + * Sorts the results in descending order by the given key. + * + * @method descending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'descending', + value: function () { + this._order = []; + + for (var _len3 = arguments.length, keys = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + keys[_key3] = arguments[_key3]; + } + + return this.addDescending.apply(this, keys); + } + + /** + * Sorts the results in descending order by the given key, + * but can also add secondary sort descriptors without overwriting _order. + * + * @method addDescending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'addDescending', + value: function () { + var _this5 = this; + + if (!this._order) { + this._order = []; + } + + for (var _len4 = arguments.length, keys = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { + keys[_key4] = arguments[_key4]; + } + + keys.forEach(function (key) { + if (Array.isArray(key)) { + key = key.join(); + } + _this5._order = _this5._order.concat(key.replace(/\s/g, '').split(',').map(function (k) { + return '-' + k; + })); + }); + + return this; + } + + /** Query Options **/ + + /** + * Sets the number of results to skip before returning any results. + * This is useful for pagination. + * Default is to skip zero results. + * @method skip + * @param {Number} n the number of results to skip. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'skip', + value: function (n) { + if (typeof n !== 'number' || n < 0) { + throw new Error('You can only skip by a positive number'); + } + this._skip = n; + return this; + } + + /** + * Sets the limit of the number of results to return. The default limit is + * 100, with a maximum of 1000 results being returned at a time. + * @method limit + * @param {Number} n the number of results to limit to. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'limit', + value: function (n) { + if (typeof n !== 'number') { + throw new Error('You can only set the limit to a numeric value'); + } + this._limit = n; + return this; + } + + /** + * Includes nested Parse.Objects for the provided key. You can use dot + * notation to specify which fields in the included object are also fetched. + * @method include + * @param {String} key The name of the key to include. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'include', + value: function () { + var _this6 = this; + + for (var _len5 = arguments.length, keys = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { + keys[_key5] = arguments[_key5]; + } + + keys.forEach(function (key) { + if (Array.isArray(key)) { + _this6._include = _this6._include.concat(key); + } else { + _this6._include.push(key); + } + }); + return this; + } + + /** + * Restricts the fields of the returned Parse.Objects to include only the + * provided keys. If this is called multiple times, then all of the keys + * specified in each of the calls will be included. + * @method select + * @param {Array} keys The names of the keys to include. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + + }, { + key: 'select', + value: function () { + var _this7 = this; + + if (!this._select) { + this._select = []; + } + + for (var _len6 = arguments.length, keys = Array(_len6), _key6 = 0; _key6 < _len6; _key6++) { + keys[_key6] = arguments[_key6]; + } + + keys.forEach(function (key) { + if (Array.isArray(key)) { + _this7._select = _this7._select.concat(key); + } else { + _this7._select.push(key); + } + }); + return this; + } + + /** + * Subscribe this query to get liveQuery updates + * @method subscribe + * @return {LiveQuerySubscription} Returns the liveQuerySubscription, it's an event emitter + * which can be used to get liveQuery updates. + */ + + }, { + key: 'subscribe', + value: function () { + var controller = _CoreManager2.default.getLiveQueryController(); + return controller.subscribe(this); + } + + /** + * Constructs a Parse.Query that is the OR of the passed in queries. For + * example: + *
          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 if current 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 using current. + * + *

          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: + *
            + *
          1. channels - An Array of channels to push to.
          2. + *
          3. push_time - A Date object for when to send the push.
          4. + *
          5. expiration_time - A Date object for when to expire + * the push.
          6. + *
          7. expiration_interval - The seconds from now to expire the push.
          8. + *
          9. where - A Parse.Query over Parse.Installation that is used to match + * a set of installations to push to.
          10. + *
          11. data - The data to send as part of the push
          12. + *
              + * @param {Object} options An object that has an optional success function, + * that takes no arguments and will be called on a successful push, and + * an error function that takes a Parse.Error and will be called if the push + * failed. + * @return {Parse.Promise} A promise that is fulfilled when the push request + * completes. + */ +/** + * 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 send(data, options) { + options = options || {}; + + if (data.where && data.where instanceof _ParseQuery2.default) { + data.where = data.where.toJSON().where; + } + + if (data.push_time && (0, _typeof3.default)(data.push_time) === 'object') { + data.push_time = data.push_time.toJSON(); + } + + if (data.expiration_time && (0, _typeof3.default)(data.expiration_time) === 'object') { + data.expiration_time = data.expiration_time.toJSON(); + } + + if (data.expiration_time && data.expiration_interval) { + throw new Error('expiration_time and expiration_interval cannot both be set.'); + } + + return _CoreManager2.default.getPushController().send(data, { + useMasterKey: options.useMasterKey + })._thenRunCallbacks(options); +} + +var DefaultController = { + send: function (data, options) { + var RESTController = _CoreManager2.default.getRESTController(); + + var request = RESTController.request('POST', 'push', data, { useMasterKey: !!options.useMasterKey }); + + return request._thenRunCallbacks(options); + } +}; + +_CoreManager2.default.setPushController(DefaultController); \ No newline at end of file diff --git a/lib/node/RESTController.js b/lib/node/RESTController.js new file mode 100644 index 000000000..b8e0541c5 --- /dev/null +++ b/lib/node/RESTController.js @@ -0,0 +1,250 @@ +'use strict'; + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +var _stringify = require('babel-runtime/core-js/json/stringify'); + +var _stringify2 = _interopRequireDefault(_stringify); + +var _CoreManager = require('./CoreManager'); + +var _CoreManager2 = _interopRequireDefault(_CoreManager); + +var _ParseError = require('./ParseError'); + +var _ParseError2 = _interopRequireDefault(_ParseError); + +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 }; +} + +/** + * 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 XHR = null; +if (typeof XMLHttpRequest !== 'undefined') { + XHR = XMLHttpRequest; +} + +XHR = require('xmlhttprequest').XMLHttpRequest; + + +var useXDomainRequest = false; +if (typeof XDomainRequest !== 'undefined' && !('withCredentials' in new XMLHttpRequest())) { + useXDomainRequest = true; +} + +function ajaxIE9(method, url, data) { + var promise = new _ParsePromise2.default(); + var xdr = new XDomainRequest(); + xdr.onload = function () { + var response; + try { + response = JSON.parse(xdr.responseText); + } catch (e) { + promise.reject(e); + } + if (response) { + promise.resolve(response); + } + }; + xdr.onerror = xdr.ontimeout = function () { + // Let's fake a real error message. + var fakeResponse = { + responseText: (0, _stringify2.default)({ + code: _ParseError2.default.X_DOMAIN_REQUEST, + error: 'IE\'s XDomainRequest does not supply error info.' + }) + }; + promise.reject(fakeResponse); + }; + xdr.onprogress = function () {}; + xdr.open(method, url); + xdr.send(data); + return promise; +} + +var RESTController = { + ajax: function (method, url, data, headers) { + if (useXDomainRequest) { + return ajaxIE9(method, url, data, headers); + } + + var promise = new _ParsePromise2.default(); + var attempts = 0; + + (function dispatch() { + if (XHR == null) { + throw new Error('Cannot make a request: No definition of XMLHttpRequest was found.'); + } + var handled = false; + var xhr = new XHR(); + + xhr.onreadystatechange = function () { + if (xhr.readyState !== 4 || handled) { + return; + } + handled = true; + + if (xhr.status >= 200 && xhr.status < 300) { + var response; + try { + response = JSON.parse(xhr.responseText); + } catch (e) { + promise.reject(e.toString()); + } + if (response) { + promise.resolve(response, xhr.status, xhr); + } + } else if (xhr.status >= 500 || xhr.status === 0) { + // retry on 5XX or node-xmlhttprequest error + if (++attempts < _CoreManager2.default.get('REQUEST_ATTEMPT_LIMIT')) { + // Exponentially-growing random delay + var delay = Math.round(Math.random() * 125 * Math.pow(2, attempts)); + setTimeout(dispatch, delay); + } else if (xhr.status === 0) { + promise.reject('Unable to connect to the Parse API'); + } else { + // After the retry limit is reached, fail + promise.reject(xhr); + } + } else { + promise.reject(xhr); + } + }; + + headers = headers || {}; + if (typeof headers['Content-Type'] !== 'string') { + headers['Content-Type'] = 'text/plain'; // Avoid pre-flight + } + if (_CoreManager2.default.get('IS_NODE')) { + headers['User-Agent'] = 'Parse/' + _CoreManager2.default.get('VERSION') + ' (NodeJS ' + process.versions.node + ')'; + } + + xhr.open(method, url, true); + for (var h in headers) { + xhr.setRequestHeader(h, headers[h]); + } + xhr.send(data); + })(); + + return promise; + }, + request: function (method, path, data, options) { + options = options || {}; + var url = _CoreManager2.default.get('SERVER_URL'); + if (url[url.length - 1] !== '/') { + url += '/'; + } + url += path; + + var payload = {}; + if (data && (typeof data === 'undefined' ? 'undefined' : (0, _typeof3.default)(data)) === 'object') { + for (var k in data) { + payload[k] = data[k]; + } + } + + if (method !== 'POST') { + payload._method = method; + method = 'POST'; + } + + payload._ApplicationId = _CoreManager2.default.get('APPLICATION_ID'); + var jsKey = _CoreManager2.default.get('JAVASCRIPT_KEY'); + if (jsKey) { + payload._JavaScriptKey = jsKey; + } + payload._ClientVersion = _CoreManager2.default.get('VERSION'); + + var useMasterKey = options.useMasterKey; + if (typeof useMasterKey === 'undefined') { + useMasterKey = _CoreManager2.default.get('USE_MASTER_KEY'); + } + if (useMasterKey) { + if (_CoreManager2.default.get('MASTER_KEY')) { + delete payload._JavaScriptKey; + payload._MasterKey = _CoreManager2.default.get('MASTER_KEY'); + } else { + throw new Error('Cannot use the Master Key, it has not been provided.'); + } + } + + if (_CoreManager2.default.get('FORCE_REVOCABLE_SESSION')) { + payload._RevocableSession = '1'; + } + + var installationId = options.installationId; + var installationIdPromise; + if (installationId && typeof installationId === 'string') { + installationIdPromise = _ParsePromise2.default.as(installationId); + } else { + var installationController = _CoreManager2.default.getInstallationController(); + installationIdPromise = installationController.currentInstallationId(); + } + + return installationIdPromise.then(function (iid) { + payload._InstallationId = iid; + var userController = _CoreManager2.default.getUserController(); + if (options && typeof options.sessionToken === 'string') { + return _ParsePromise2.default.as(options.sessionToken); + } else if (userController) { + return userController.currentUserAsync().then(function (user) { + if (user) { + return _ParsePromise2.default.as(user.getSessionToken()); + } + return _ParsePromise2.default.as(null); + }); + } + return _ParsePromise2.default.as(null); + }).then(function (token) { + if (token) { + payload._SessionToken = token; + } + + var payloadString = (0, _stringify2.default)(payload); + + return RESTController.ajax(method, url, payloadString); + }).then(null, function (response) { + // Transform the error into an instance of ParseError by trying to parse + // the error string as JSON + var error; + if (response && response.responseText) { + try { + var errorJSON = JSON.parse(response.responseText); + error = new _ParseError2.default(errorJSON.code, errorJSON.error); + } catch (e) { + // If we fail to parse the error text, that's okay. + error = new _ParseError2.default(_ParseError2.default.INVALID_JSON, 'Received an error with invalid JSON from Parse: ' + response.responseText); + } + } else { + error = new _ParseError2.default(_ParseError2.default.CONNECTION_FAILED, 'XMLHttpRequest failed: ' + (0, _stringify2.default)(response)); + } + + return _ParsePromise2.default.error(error); + }); + }, + _setXHR: function (xhr) { + XHR = xhr; + } +}; + +module.exports = RESTController; \ No newline at end of file diff --git a/lib/node/SingleInstanceStateController.js b/lib/node/SingleInstanceStateController.js new file mode 100644 index 000000000..545dd4c5d --- /dev/null +++ b/lib/node/SingleInstanceStateController.js @@ -0,0 +1,160 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getState = getState; +exports.initializeState = initializeState; +exports.removeState = removeState; +exports.getServerData = getServerData; +exports.setServerData = setServerData; +exports.getPendingOps = getPendingOps; +exports.setPendingOp = setPendingOp; +exports.pushPendingState = pushPendingState; +exports.popPendingState = popPendingState; +exports.mergeFirstPendingState = mergeFirstPendingState; +exports.getObjectCache = getObjectCache; +exports.estimateAttribute = estimateAttribute; +exports.estimateAttributes = estimateAttributes; +exports.commitServerChanges = commitServerChanges; +exports.enqueueTask = enqueueTask; +exports.clearAllState = clearAllState; +exports.duplicateState = duplicateState; + +var _ObjectStateMutations = require('./ObjectStateMutations'); + +var ObjectStateMutations = _interopRequireWildcard(_ObjectStateMutations); + +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; + } +} + +var objectState = {}; /** + * 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 getState(obj) { + var classData = objectState[obj.className]; + if (classData) { + return classData[obj.id] || null; + } + return null; +} + +function initializeState(obj, initial) { + var state = getState(obj); + if (state) { + return state; + } + if (!objectState[obj.className]) { + objectState[obj.className] = {}; + } + if (!initial) { + initial = ObjectStateMutations.defaultState(); + } + state = objectState[obj.className][obj.id] = initial; + return state; +} + +function removeState(obj) { + var state = getState(obj); + if (state === null) { + return null; + } + delete objectState[obj.className][obj.id]; + return state; +} + +function getServerData(obj) { + var state = getState(obj); + if (state) { + return state.serverData; + } + return {}; +} + +function setServerData(obj, attributes) { + var serverData = initializeState(obj).serverData; + ObjectStateMutations.setServerData(serverData, attributes); +} + +function getPendingOps(obj) { + var state = getState(obj); + if (state) { + return state.pendingOps; + } + return [{}]; +} + +function setPendingOp(obj, attr, op) { + var pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.setPendingOp(pendingOps, attr, op); +} + +function pushPendingState(obj) { + var pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.pushPendingState(pendingOps); +} + +function popPendingState(obj) { + var pendingOps = initializeState(obj).pendingOps; + return ObjectStateMutations.popPendingState(pendingOps); +} + +function mergeFirstPendingState(obj) { + var pendingOps = getPendingOps(obj); + ObjectStateMutations.mergeFirstPendingState(pendingOps); +} + +function getObjectCache(obj) { + var state = getState(obj); + if (state) { + return state.objectCache; + } + return {}; +} + +function estimateAttribute(obj, attr) { + var serverData = getServerData(obj); + var pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttribute(serverData, pendingOps, obj.className, obj.id, attr); +} + +function estimateAttributes(obj) { + var serverData = getServerData(obj); + var pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttributes(serverData, pendingOps, obj.className, obj.id); +} + +function commitServerChanges(obj, changes) { + var state = initializeState(obj); + ObjectStateMutations.commitServerChanges(state.serverData, state.objectCache, changes); +} + +function enqueueTask(obj, task) { + var state = initializeState(obj); + return state.tasks.enqueue(task); +} + +function clearAllState() { + objectState = {}; +} + +function duplicateState(source, dest) { + dest.id = source.id; +} \ No newline at end of file diff --git a/lib/node/Storage.js b/lib/node/Storage.js new file mode 100644 index 000000000..e4cd978a5 --- /dev/null +++ b/lib/node/Storage.js @@ -0,0 +1,93 @@ +'use strict'; + +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. + * + * + */ + +module.exports = { + async: function () { + var controller = _CoreManager2.default.getStorageController(); + return !!controller.async; + }, + getItem: function (path) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + throw new Error('Synchronous storage is not supported by the current storage controller'); + } + return controller.getItem(path); + }, + getItemAsync: function (path) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + return controller.getItemAsync(path); + } + return _ParsePromise2.default.as(controller.getItem(path)); + }, + setItem: function (path, value) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + throw new Error('Synchronous storage is not supported by the current storage controller'); + } + return controller.setItem(path, value); + }, + setItemAsync: function (path, value) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + return controller.setItemAsync(path, value); + } + return _ParsePromise2.default.as(controller.setItem(path, value)); + }, + removeItem: function (path) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + throw new Error('Synchronous storage is not supported by the current storage controller'); + } + return controller.removeItem(path); + }, + removeItemAsync: function (path) { + var controller = _CoreManager2.default.getStorageController(); + if (controller.async === 1) { + return controller.removeItemAsync(path); + } + return _ParsePromise2.default.as(controller.removeItem(path)); + }, + generatePath: function (path) { + if (!_CoreManager2.default.get('APPLICATION_ID')) { + throw new Error('You need to call Parse.initialize before using Parse.'); + } + if (typeof path !== 'string') { + throw new Error('Tried to get a Storage path that was not a String.'); + } + if (path[0] === '/') { + path = path.substr(1); + } + return 'Parse/' + _CoreManager2.default.get('APPLICATION_ID') + '/' + path; + }, + _clear: function () { + var controller = _CoreManager2.default.getStorageController(); + if (controller.hasOwnProperty('clear')) { + controller.clear(); + } + } +}; + +_CoreManager2.default.setStorageController(require('./StorageController.default')); \ No newline at end of file diff --git a/lib/node/StorageController.browser.js b/lib/node/StorageController.browser.js new file mode 100644 index 000000000..d372924a0 --- /dev/null +++ b/lib/node/StorageController.browser.js @@ -0,0 +1,41 @@ +'use strict'; + +var _ParsePromise = require('./ParsePromise'); + +var _ParsePromise2 = _interopRequireDefault(_ParsePromise); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +var StorageController = { + async: 0, + + getItem: function (path) { + return localStorage.getItem(path); + }, + setItem: function (path, value) { + try { + localStorage.setItem(path, value); + } catch (e) { + // Quota exceeded, possibly due to Safari Private Browsing mode + } + }, + removeItem: function (path) { + localStorage.removeItem(path); + }, + clear: function () { + localStorage.clear(); + } +}; /** + * 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. + * + * + */ + +module.exports = StorageController; \ No newline at end of file diff --git a/lib/node/StorageController.default.js b/lib/node/StorageController.default.js new file mode 100644 index 000000000..460c1e2ca --- /dev/null +++ b/lib/node/StorageController.default.js @@ -0,0 +1,41 @@ +"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. + * + * + */ + +// When there is no native storage interface, we default to an in-memory map + +var memMap = {}; +var StorageController = { + async: 0, + + getItem: function (path) { + if (memMap.hasOwnProperty(path)) { + return memMap[path]; + } + return null; + }, + setItem: function (path, value) { + memMap[path] = String(value); + }, + removeItem: function (path) { + delete memMap[path]; + }, + clear: function () { + for (var key in memMap) { + if (memMap.hasOwnProperty(key)) { + delete memMap[key]; + } + } + } +}; + +module.exports = StorageController; \ No newline at end of file diff --git a/lib/node/StorageController.react-native.js b/lib/node/StorageController.react-native.js new file mode 100644 index 000000000..b92ae6141 --- /dev/null +++ b/lib/node/StorageController.react-native.js @@ -0,0 +1,67 @@ +'use strict'; + +var _ParsePromise = require('./ParsePromise'); + +var _ParsePromise2 = _interopRequireDefault(_ParsePromise); + +var _reactNative = require('react-native/Libraries/react-native/react-native.js'); + +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 StorageController = { + async: 1, + + getItemAsync: function (path) { + var p = new _ParsePromise2.default(); + _reactNative.AsyncStorage.getItem(path, function (err, value) { + if (err) { + p.reject(err); + } else { + p.resolve(value); + } + }); + return p; + }, + setItemAsync: function (path, value) { + var p = new _ParsePromise2.default(); + _reactNative.AsyncStorage.setItem(path, value, function (err) { + if (err) { + p.reject(err); + } else { + p.resolve(value); + } + }); + return p; + }, + removeItemAsync: function (path) { + var p = new _ParsePromise2.default(); + _reactNative.AsyncStorage.removeItem(path, function (err) { + if (err) { + p.reject(err); + } else { + p.resolve(); + } + }); + return p; + }, + clear: function () { + _reactNative.AsyncStorage.clear(); + } +}; +// RN packager nonsense + + +module.exports = StorageController; \ No newline at end of file diff --git a/lib/node/TaskQueue.js b/lib/node/TaskQueue.js new file mode 100644 index 000000000..4bf6bffd0 --- /dev/null +++ b/lib/node/TaskQueue.js @@ -0,0 +1,77 @@ +'use strict'; + +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 }; +} + +var TaskQueue = function () { + function TaskQueue() { + (0, _classCallCheck3.default)(this, TaskQueue); + + this.queue = []; + } + + (0, _createClass3.default)(TaskQueue, [{ + key: 'enqueue', + value: function (task) { + var _this = this; + + var taskComplete = new _ParsePromise2.default(); + this.queue.push({ + task: task, + _completion: taskComplete + }); + if (this.queue.length === 1) { + task().then(function () { + _this._dequeue(); + taskComplete.resolve(); + }, function (error) { + _this._dequeue(); + taskComplete.reject(error); + }); + } + return taskComplete; + } + }, { + key: '_dequeue', + value: function () { + var _this2 = this; + + this.queue.shift(); + if (this.queue.length) { + var next = this.queue[0]; + next.task().then(function () { + _this2._dequeue(); + next._completion.resolve(); + }, function (error) { + _this2._dequeue(); + next._completion.reject(error); + }); + } + } + }]); + return TaskQueue; +}(); /** + * 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. + * + * + */ + +module.exports = TaskQueue; \ No newline at end of file diff --git a/lib/node/UniqueInstanceStateController.js b/lib/node/UniqueInstanceStateController.js new file mode 100644 index 000000000..12b8ab04d --- /dev/null +++ b/lib/node/UniqueInstanceStateController.js @@ -0,0 +1,189 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _weakMap = require('babel-runtime/core-js/weak-map'); + +var _weakMap2 = _interopRequireDefault(_weakMap); + +exports.getState = getState; +exports.initializeState = initializeState; +exports.removeState = removeState; +exports.getServerData = getServerData; +exports.setServerData = setServerData; +exports.getPendingOps = getPendingOps; +exports.setPendingOp = setPendingOp; +exports.pushPendingState = pushPendingState; +exports.popPendingState = popPendingState; +exports.mergeFirstPendingState = mergeFirstPendingState; +exports.getObjectCache = getObjectCache; +exports.estimateAttribute = estimateAttribute; +exports.estimateAttributes = estimateAttributes; +exports.commitServerChanges = commitServerChanges; +exports.enqueueTask = enqueueTask; +exports.duplicateState = duplicateState; +exports.clearAllState = clearAllState; + +var _ObjectStateMutations = require('./ObjectStateMutations'); + +var ObjectStateMutations = _interopRequireWildcard(_ObjectStateMutations); + +var _TaskQueue = require('./TaskQueue'); + +var _TaskQueue2 = _interopRequireDefault(_TaskQueue); + +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 }; +} + +/** + * 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 objectState = new _weakMap2.default(); + +function getState(obj) { + var classData = objectState.get(obj); + return classData || null; +} + +function initializeState(obj, initial) { + var state = getState(obj); + if (state) { + return state; + } + if (!initial) { + initial = { + serverData: {}, + pendingOps: [{}], + objectCache: {}, + tasks: new _TaskQueue2.default(), + existed: false + }; + } + state = initial; + objectState.set(obj, state); + return state; +} + +function removeState(obj) { + var state = getState(obj); + if (state === null) { + return null; + } + objectState.delete(obj); + return state; +} + +function getServerData(obj) { + var state = getState(obj); + if (state) { + return state.serverData; + } + return {}; +} + +function setServerData(obj, attributes) { + var serverData = initializeState(obj).serverData; + ObjectStateMutations.setServerData(serverData, attributes); +} + +function getPendingOps(obj) { + var state = getState(obj); + if (state) { + return state.pendingOps; + } + return [{}]; +} + +function setPendingOp(obj, attr, op) { + var pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.setPendingOp(pendingOps, attr, op); +} + +function pushPendingState(obj) { + var pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.pushPendingState(pendingOps); +} + +function popPendingState(obj) { + var pendingOps = initializeState(obj).pendingOps; + return ObjectStateMutations.popPendingState(pendingOps); +} + +function mergeFirstPendingState(obj) { + var pendingOps = getPendingOps(obj); + ObjectStateMutations.mergeFirstPendingState(pendingOps); +} + +function getObjectCache(obj) { + var state = getState(obj); + if (state) { + return state.objectCache; + } + return {}; +} + +function estimateAttribute(obj, attr) { + var serverData = getServerData(obj); + var pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttribute(serverData, pendingOps, obj.className, obj.id, attr); +} + +function estimateAttributes(obj) { + var serverData = getServerData(obj); + var pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttributes(serverData, pendingOps, obj.className, obj.id); +} + +function commitServerChanges(obj, changes) { + var state = initializeState(obj); + ObjectStateMutations.commitServerChanges(state.serverData, state.objectCache, changes); +} + +function enqueueTask(obj, task) { + var state = initializeState(obj); + return state.tasks.enqueue(task); +} + +function duplicateState(source, dest) { + var oldState = initializeState(source); + var newState = initializeState(dest); + for (var key in oldState.serverData) { + newState.serverData[key] = oldState.serverData[key]; + } + for (var index = 0; index < oldState.pendingOps.length; index++) { + for (var _key in oldState.pendingOps[index]) { + newState.pendingOps[index][_key] = oldState.pendingOps[index][_key]; + } + } + for (var _key2 in oldState.objectCache) { + newState.objectCache[_key2] = oldState.objectCache[_key2]; + } + newState.existed = oldState.existed; +} + +function clearAllState() { + objectState = new _weakMap2.default(); +} \ No newline at end of file diff --git a/lib/node/arrayContainsObject.js b/lib/node/arrayContainsObject.js new file mode 100644 index 000000000..e9d7949c9 --- /dev/null +++ b/lib/node/arrayContainsObject.js @@ -0,0 +1,35 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = arrayContainsObject; + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +function arrayContainsObject(array, object) { + if (array.indexOf(object) > -1) { + return true; + } + for (var i = 0; i < array.length; i++) { + if (array[i] instanceof _ParseObject2.default && array[i].className === object.className && array[i]._getId() === object._getId()) { + return true; + } + } + return 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. + * + * + */ \ No newline at end of file diff --git a/lib/node/canBeSerialized.js b/lib/node/canBeSerialized.js new file mode 100644 index 000000000..e7b7e2282 --- /dev/null +++ b/lib/node/canBeSerialized.js @@ -0,0 +1,82 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +exports.default = canBeSerialized; + +var _ParseFile = require('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseRelation = require('./ParseRelation'); + +var _ParseRelation2 = _interopRequireDefault(_ParseRelation); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +function canBeSerialized(obj) { + if (!(obj instanceof _ParseObject2.default)) { + return true; + } + var attributes = obj.attributes; + for (var attr in attributes) { + var val = attributes[attr]; + if (!canBeSerializedHelper(val)) { + return false; + } + } + return true; +} /** + * 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 canBeSerializedHelper(value) { + if ((typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) !== 'object') { + return true; + } + if (value instanceof _ParseRelation2.default) { + return true; + } + if (value instanceof _ParseObject2.default) { + return !!value.id; + } + if (value instanceof _ParseFile2.default) { + if (value.url()) { + return true; + } + return false; + } + if (Array.isArray(value)) { + for (var i = 0; i < value.length; i++) { + if (!canBeSerializedHelper(value[i])) { + return false; + } + } + return true; + } + for (var k in value) { + if (!canBeSerializedHelper(value[k])) { + return false; + } + } + return true; +} \ No newline at end of file diff --git a/lib/node/decode.js b/lib/node/decode.js new file mode 100644 index 000000000..98a182789 --- /dev/null +++ b/lib/node/decode.js @@ -0,0 +1,93 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +exports.default = decode; + +var _ParseACL = require('./ParseACL'); + +var _ParseACL2 = _interopRequireDefault(_ParseACL); + +var _ParseFile = require('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseGeoPoint = require('./ParseGeoPoint'); + +var _ParseGeoPoint2 = _interopRequireDefault(_ParseGeoPoint); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseOp = require('./ParseOp'); + +var _ParseRelation = require('./ParseRelation'); + +var _ParseRelation2 = _interopRequireDefault(_ParseRelation); + +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 decode(value) { + if (value === null || (typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) !== 'object') { + return value; + } + if (Array.isArray(value)) { + var dup = []; + value.forEach(function (v, i) { + dup[i] = decode(v); + }); + return dup; + } + if (typeof value.__op === 'string') { + return (0, _ParseOp.opFromJSON)(value); + } + if (value.__type === 'Pointer' && value.className) { + return _ParseObject2.default.fromJSON(value); + } + if (value.__type === 'Object' && value.className) { + return _ParseObject2.default.fromJSON(value); + } + if (value.__type === 'Relation') { + // The parent and key fields will be populated by the parent + var relation = new _ParseRelation2.default(null, null); + relation.targetClassName = value.className; + return relation; + } + if (value.__type === 'Date') { + return new Date(value.iso); + } + if (value.__type === 'File') { + return _ParseFile2.default.fromJSON(value); + } + if (value.__type === 'GeoPoint') { + return new _ParseGeoPoint2.default({ + latitude: value.latitude, + longitude: value.longitude + }); + } + var copy = {}; + for (var k in value) { + copy[k] = decode(value[k]); + } + return copy; +} \ No newline at end of file diff --git a/lib/node/encode.js b/lib/node/encode.js new file mode 100644 index 000000000..8c3aee6e3 --- /dev/null +++ b/lib/node/encode.js @@ -0,0 +1,104 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +var _keys = require('babel-runtime/core-js/object/keys'); + +var _keys2 = _interopRequireDefault(_keys); + +exports.default = function (value, disallowObjects, forcePointers, seen) { + return encode(value, !!disallowObjects, !!forcePointers, seen || []); +}; + +var _ParseACL = require('./ParseACL'); + +var _ParseACL2 = _interopRequireDefault(_ParseACL); + +var _ParseFile = require('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseGeoPoint = require('./ParseGeoPoint'); + +var _ParseGeoPoint2 = _interopRequireDefault(_ParseGeoPoint); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseOp = require('./ParseOp'); + +var _ParseRelation = require('./ParseRelation'); + +var _ParseRelation2 = _interopRequireDefault(_ParseRelation); + +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 toString = Object.prototype.toString; + +function encode(value, disallowObjects, forcePointers, seen) { + if (value instanceof _ParseObject2.default) { + if (disallowObjects) { + throw new Error('Parse Objects not allowed here'); + } + var seenEntry = value.id ? value.className + ':' + value.id : value; + if (forcePointers || !seen || seen.indexOf(seenEntry) > -1 || value.dirty() || (0, _keys2.default)(value._getServerData()).length < 1) { + return value.toPointer(); + } + seen = seen.concat(seenEntry); + return value._toFullJSON(seen); + } + if (value instanceof _ParseOp.Op || value instanceof _ParseACL2.default || value instanceof _ParseGeoPoint2.default || value instanceof _ParseRelation2.default) { + return value.toJSON(); + } + if (value instanceof _ParseFile2.default) { + if (!value.url()) { + throw new Error('Tried to encode an unsaved file.'); + } + return value.toJSON(); + } + if (toString.call(value) === '[object Date]') { + if (isNaN(value)) { + throw new Error('Tried to encode an invalid date.'); + } + return { __type: 'Date', iso: value.toJSON() }; + } + if (toString.call(value) === '[object RegExp]' && typeof value.source === 'string') { + return value.source; + } + + if (Array.isArray(value)) { + return value.map(function (v) { + return encode(v, disallowObjects, forcePointers, seen); + }); + } + + if (value && (typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) === 'object') { + var output = {}; + for (var k in value) { + output[k] = encode(value[k], disallowObjects, forcePointers, seen); + } + return output; + } + + return value; +} \ No newline at end of file diff --git a/lib/node/equals.js b/lib/node/equals.js new file mode 100644 index 000000000..32edcc8a7 --- /dev/null +++ b/lib/node/equals.js @@ -0,0 +1,84 @@ +'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); + +exports.default = equals; + +var _ParseACL = require('./ParseACL'); + +var _ParseACL2 = _interopRequireDefault(_ParseACL); + +var _ParseFile = require('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseGeoPoint = require('./ParseGeoPoint'); + +var _ParseGeoPoint2 = _interopRequireDefault(_ParseGeoPoint); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +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 equals(a, b) { + if ((typeof a === 'undefined' ? 'undefined' : (0, _typeof3.default)(a)) !== (typeof b === 'undefined' ? 'undefined' : (0, _typeof3.default)(b))) { + return false; + } + + if (!a || (typeof a === 'undefined' ? 'undefined' : (0, _typeof3.default)(a)) !== 'object') { + // a is a primitive + return a === b; + } + + if (Array.isArray(a) || Array.isArray(b)) { + if (!Array.isArray(a) || !Array.isArray(b)) { + return false; + } + if (a.length !== b.length) { + return false; + } + for (var i = a.length; i--;) { + if (!equals(a[i], b[i])) { + return false; + } + } + return true; + } + + if (a instanceof _ParseACL2.default || a instanceof _ParseFile2.default || a instanceof _ParseGeoPoint2.default || a instanceof _ParseObject2.default) { + return a.equals(b); + } + + if ((0, _keys2.default)(a).length !== (0, _keys2.default)(b).length) { + return false; + } + for (var k in a) { + if (!equals(a[k], b[k])) { + return false; + } + } + return true; +} \ No newline at end of file diff --git a/lib/node/escape.js b/lib/node/escape.js new file mode 100644 index 000000000..4693baaed --- /dev/null +++ b/lib/node/escape.js @@ -0,0 +1,31 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = escape; +/** + * 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 encoded = { + '&': '&', + '<': '<', + '>': '>', + '/': '/', + '\'': ''', + '"': '"' +}; + +function escape(str) { + return str.replace(/[&<>\/'"]/g, function (char) { + return encoded[char]; + }); +} \ No newline at end of file diff --git a/lib/node/isRevocableSession.js b/lib/node/isRevocableSession.js new file mode 100644 index 000000000..92b8230e9 --- /dev/null +++ b/lib/node/isRevocableSession.js @@ -0,0 +1,20 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = isRevocableSession; +/** + * 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 isRevocableSession(token) { + return token.indexOf('r:') > -1; +} \ No newline at end of file diff --git a/lib/node/parseDate.js b/lib/node/parseDate.js new file mode 100644 index 000000000..48e061b05 --- /dev/null +++ b/lib/node/parseDate.js @@ -0,0 +1,34 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = parseDate; +/** + * 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 parseDate(iso8601) { + var regexp = new RegExp('^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})' + 'T' + '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})' + '(.([0-9]+))?' + 'Z$'); + var match = regexp.exec(iso8601); + if (!match) { + return null; + } + + var year = match[1] || 0; + var month = (match[2] || 1) - 1; + var day = match[3] || 0; + var hour = match[4] || 0; + var minute = match[5] || 0; + var second = match[6] || 0; + var milli = match[8] || 0; + + return new Date(Date.UTC(year, month, day, hour, minute, second, milli)); +} \ No newline at end of file diff --git a/lib/node/unique.js b/lib/node/unique.js new file mode 100644 index 000000000..157b3560a --- /dev/null +++ b/lib/node/unique.js @@ -0,0 +1,45 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = unique; + +var _arrayContainsObject = require('./arrayContainsObject'); + +var _arrayContainsObject2 = _interopRequireDefault(_arrayContainsObject); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +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 unique(arr) { + var uniques = []; + arr.forEach(function (value) { + if (value instanceof _ParseObject2.default) { + if (!(0, _arrayContainsObject2.default)(uniques, value)) { + uniques.push(value); + } + } else { + if (uniques.indexOf(value) < 0) { + uniques.push(value); + } + } + }); + return uniques; +} \ No newline at end of file diff --git a/lib/node/unsavedChildren.js b/lib/node/unsavedChildren.js new file mode 100644 index 000000000..073efd70b --- /dev/null +++ b/lib/node/unsavedChildren.js @@ -0,0 +1,102 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof2 = require('babel-runtime/helpers/typeof'); + +var _typeof3 = _interopRequireDefault(_typeof2); + +exports.default = unsavedChildren; + +var _ParseFile = require('./ParseFile'); + +var _ParseFile2 = _interopRequireDefault(_ParseFile); + +var _ParseObject = require('./ParseObject'); + +var _ParseObject2 = _interopRequireDefault(_ParseObject); + +var _ParseRelation = require('./ParseRelation'); + +var _ParseRelation2 = _interopRequireDefault(_ParseRelation); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} + +/** + * Return an array of unsaved children, which are either Parse Objects or Files. + * If it encounters any dirty Objects without Ids, it will throw an exception. + */ +function unsavedChildren(obj, allowDeepUnsaved) { + var encountered = { + objects: {}, + files: [] + }; + var identifier = obj.className + ':' + obj._getId(); + encountered.objects[identifier] = obj.dirty() ? obj : true; + var attributes = obj.attributes; + for (var attr in attributes) { + if ((0, _typeof3.default)(attributes[attr]) === 'object') { + traverse(attributes[attr], encountered, false, !!allowDeepUnsaved); + } + } + var unsaved = []; + for (var id in encountered.objects) { + if (id !== identifier && encountered.objects[id] !== true) { + unsaved.push(encountered.objects[id]); + } + } + return unsaved.concat(encountered.files); +} /** + * 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 traverse(obj, encountered, shouldThrow, allowDeepUnsaved) { + if (obj instanceof _ParseObject2.default) { + if (!obj.id && shouldThrow) { + throw new Error('Cannot create a pointer to an unsaved Object.'); + } + var identifier = obj.className + ':' + obj._getId(); + if (!encountered.objects[identifier]) { + encountered.objects[identifier] = obj.dirty() ? obj : true; + var attributes = obj.attributes; + for (var attr in attributes) { + if ((0, _typeof3.default)(attributes[attr]) === 'object') { + traverse(attributes[attr], encountered, !allowDeepUnsaved, allowDeepUnsaved); + } + } + } + return; + } + if (obj instanceof _ParseFile2.default) { + if (!obj.url() && encountered.files.indexOf(obj) < 0) { + encountered.files.push(obj); + } + return; + } + if (obj instanceof _ParseRelation2.default) { + return; + } + if (Array.isArray(obj)) { + obj.forEach(function (el) { + if ((typeof el === 'undefined' ? 'undefined' : (0, _typeof3.default)(el)) === 'object') { + traverse(el, encountered, shouldThrow, allowDeepUnsaved); + } + }); + } + for (var k in obj) { + if ((0, _typeof3.default)(obj[k]) === 'object') { + traverse(obj[k], encountered, shouldThrow, allowDeepUnsaved); + } + } +} \ No newline at end of file diff --git a/lib/react-native/Analytics.js b/lib/react-native/Analytics.js new file mode 100644 index 000000000..92561a2bb --- /dev/null +++ b/lib/react-native/Analytics.js @@ -0,0 +1,78 @@ +/** + * 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'; + +/** + * Parse.Analytics provides an interface to Parse's logging and analytics + * backend. + * + * @class Parse.Analytics + * @static + */ + +/** + * Tracks the occurrence of a custom event with additional dimensions. + * Parse will store a data point at the time of invocation with the given + * event name. + * + * Dimensions will allow segmentation of the occurrences of this custom + * event. Keys and values should be {@code String}s, and will throw + * otherwise. + * + * To track a user signup along with additional metadata, consider the + * following: + *
              + * 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.

              + */ +export default class ParseACL { + + constructor(arg1) { + this.permissionsById = {}; + if (arg1 && typeof arg1 === 'object') { + if (arg1 instanceof ParseUser) { + this.setReadAccess(arg1, true); + this.setWriteAccess(arg1, true); + } else { + for (var userId in arg1) { + var accessList = arg1[userId]; + if (typeof userId !== 'string') { + throw new TypeError('Tried to create an ACL with an invalid user id.'); + } + this.permissionsById[userId] = {}; + for (var permission in accessList) { + var allowed = accessList[permission]; + if (permission !== 'read' && permission !== 'write') { + throw new TypeError('Tried to create an ACL with an invalid permission type.'); + } + if (typeof allowed !== 'boolean') { + throw new TypeError('Tried to create an ACL with an invalid permission value.'); + } + this.permissionsById[userId][permission] = allowed; + } + } + } + } else if (typeof arg1 === 'function') { + throw new TypeError('ParseACL constructed with a function. Did you forget ()?'); + } + } + + /** + * Returns a JSON-encoded version of the ACL. + * @method toJSON + * @return {Object} + */ + toJSON() { + var permissions = {}; + for (var p in this.permissionsById) { + permissions[p] = this.permissionsById[p]; + } + return permissions; + } + + /** + * Returns whether this ACL is equal to another object + * @method equals + * @param other The other object to compare to + * @return {Boolean} + */ + equals(other) { + if (!(other instanceof ParseACL)) { + return false; + } + var users = Object.keys(this.permissionsById); + var otherUsers = Object.keys(other.permissionsById); + if (users.length !== otherUsers.length) { + return false; + } + for (var u in this.permissionsById) { + if (!other.permissionsById[u]) { + return false; + } + if (this.permissionsById[u].read !== other.permissionsById[u].read) { + return false; + } + if (this.permissionsById[u].write !== other.permissionsById[u].write) { + return false; + } + } + return true; + } + + _setAccess(accessType, userId, allowed) { + if (userId instanceof ParseUser) { + userId = userId.id; + } else if (userId instanceof ParseRole) { + const name = userId.getName(); + if (!name) { + throw new TypeError('Role must have a name'); + } + userId = 'role:' + name; + } + if (typeof userId !== 'string') { + throw new TypeError('userId must be a string.'); + } + if (typeof allowed !== 'boolean') { + throw new TypeError('allowed must be either true or false.'); + } + var permissions = this.permissionsById[userId]; + if (!permissions) { + if (!allowed) { + // The user already doesn't have this permission, so no action is needed + return; + } else { + permissions = {}; + this.permissionsById[userId] = permissions; + } + } + + if (allowed) { + this.permissionsById[userId][accessType] = true; + } else { + delete permissions[accessType]; + if (Object.keys(permissions).length === 0) { + delete this.permissionsById[userId]; + } + } + } + + _getAccess(accessType, userId) { + if (userId instanceof ParseUser) { + userId = userId.id; + if (!userId) { + throw new Error('Cannot get access for a ParseUser without an ID'); + } + } else if (userId instanceof ParseRole) { + const name = userId.getName(); + if (!name) { + throw new TypeError('Role must have a name'); + } + userId = 'role:' + name; + } + var permissions = this.permissionsById[userId]; + if (!permissions) { + return false; + } + return !!permissions[accessType]; + } + + /** + * Sets whether the given user is allowed to read this object. + * @method setReadAccess + * @param userId An instance of Parse.User or its objectId. + * @param {Boolean} allowed Whether that user should have read access. + */ + setReadAccess(userId, allowed) { + this._setAccess('read', userId, allowed); + } + + /** + * Get whether the given user id is *explicitly* allowed to read this object. + * Even if this returns false, the user may still be able to access it if + * getPublicReadAccess returns true or a role that the user belongs to has + * write access. + * @method getReadAccess + * @param userId An instance of Parse.User or its objectId, or a Parse.Role. + * @return {Boolean} + */ + getReadAccess(userId) { + return this._getAccess('read', userId); + } + + /** + * Sets whether the given user id is allowed to write this object. + * @method setWriteAccess + * @param userId An instance of Parse.User or its objectId, or a Parse.Role.. + * @param {Boolean} allowed Whether that user should have write access. + */ + setWriteAccess(userId, allowed) { + this._setAccess('write', userId, allowed); + } + + /** + * Gets whether the given user id is *explicitly* allowed to write this object. + * Even if this returns false, the user may still be able to write it if + * getPublicWriteAccess returns true or a role that the user belongs to has + * write access. + * @method getWriteAccess + * @param userId An instance of Parse.User or its objectId, or a Parse.Role. + * @return {Boolean} + */ + getWriteAccess(userId) { + return this._getAccess('write', userId); + } + + /** + * Sets whether the public is allowed to read this object. + * @method setPublicReadAccess + * @param {Boolean} allowed + */ + setPublicReadAccess(allowed) { + this.setReadAccess(PUBLIC_KEY, allowed); + } + + /** + * Gets whether the public is allowed to read this object. + * @method getPublicReadAccess + * @return {Boolean} + */ + getPublicReadAccess() { + return this.getReadAccess(PUBLIC_KEY); + } + + /** + * Sets whether the public is allowed to write this object. + * @method setPublicWriteAccess + * @param {Boolean} allowed + */ + setPublicWriteAccess(allowed) { + this.setWriteAccess(PUBLIC_KEY, allowed); + } + + /** + * Gets whether the public is allowed to write this object. + * @method getPublicWriteAccess + * @return {Boolean} + */ + getPublicWriteAccess() { + return this.getWriteAccess(PUBLIC_KEY); + } + + /** + * Gets whether users belonging to the given role are allowed + * to read this object. Even if this returns false, the role may + * still be able to write it if a parent role has read access. + * + * @method getRoleReadAccess + * @param role The name of the role, or a Parse.Role object. + * @return {Boolean} true if the role has read access. false otherwise. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + getRoleReadAccess(role) { + if (role instanceof ParseRole) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + return this.getReadAccess('role:' + role); + } + + /** + * Gets whether users belonging to the given role are allowed + * to write this object. Even if this returns false, the role may + * still be able to write it if a parent role has write access. + * + * @method getRoleWriteAccess + * @param role The name of the role, or a Parse.Role object. + * @return {Boolean} true if the role has write access. false otherwise. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + getRoleWriteAccess(role) { + if (role instanceof ParseRole) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + return this.getWriteAccess('role:' + role); + } + + /** + * Sets whether users belonging to the given role are allowed + * to read this object. + * + * @method setRoleReadAccess + * @param role The name of the role, or a Parse.Role object. + * @param {Boolean} allowed Whether the given role can read this object. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + setRoleReadAccess(role, allowed) { + if (role instanceof ParseRole) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + this.setReadAccess('role:' + role, allowed); + } + + /** + * Sets whether users belonging to the given role are allowed + * to write this object. + * + * @method setRoleWriteAccess + * @param role The name of the role, or a Parse.Role object. + * @param {Boolean} allowed Whether the given role can write this object. + * @throws {TypeError} If role is neither a Parse.Role nor a String. + */ + setRoleWriteAccess(role, allowed) { + if (role instanceof ParseRole) { + // Normalize to the String name + role = role.getName(); + } + if (typeof role !== 'string') { + throw new TypeError('role must be a ParseRole or a String'); + } + this.setWriteAccess('role:' + role, allowed); + } +} \ No newline at end of file diff --git a/lib/react-native/ParseConfig.js b/lib/react-native/ParseConfig.js new file mode 100644 index 000000000..ce6cbdb45 --- /dev/null +++ b/lib/react-native/ParseConfig.js @@ -0,0 +1,168 @@ +/** + * 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 escape from './escape'; +import ParseError from './ParseError'; +import ParsePromise from './ParsePromise'; +import Storage from './Storage'; + +/** + * Parse.Config is a local representation of configuration data that + * can be set from the Parse dashboard. + * + * @class Parse.Config + * @constructor + */ + +export default class ParseConfig { + + constructor() { + this.attributes = {}; + this._escapedAttributes = {}; + } + + /** + * Gets the value of an attribute. + * @method get + * @param {String} attr The name of an attribute. + */ + get(attr) { + return this.attributes[attr]; + } + + /** + * Gets the HTML-escaped value of an attribute. + * @method escape + * @param {String} attr The name of an attribute. + */ + escape(attr) { + var html = this._escapedAttributes[attr]; + if (html) { + return html; + } + var val = this.attributes[attr]; + var escaped = ''; + if (val != null) { + escaped = escape(val.toString()); + } + this._escapedAttributes[attr] = escaped; + return escaped; + } + + /** + * Retrieves the most recently-fetched configuration object, either from + * memory or from local storage if necessary. + * + * @method current + * @static + * @return {Config} The most recently-fetched Parse.Config if it + * exists, else an empty Parse.Config. + */ + static current() { + var controller = CoreManager.getConfigController(); + return controller.current(); + } + + /** + * Gets a new configuration object from the server. + * @method get + * @static + * @param {Object} options A Backbone-style options object. + * Valid options are:
                + *
              • success: Function to call when the get completes successfully. + *
              • error: Function to call when the get fails. + *
              + * @return {Parse.Promise} A promise that is resolved with a newly-created + * configuration object when the get completes. + */ + static get(options) { + options = options || {}; + + var controller = CoreManager.getConfigController(); + return controller.get()._thenRunCallbacks(options); + } +} + +var currentConfig = null; + +var CURRENT_CONFIG_KEY = 'currentConfig'; + +function decodePayload(data) { + try { + var json = JSON.parse(data); + if (json && typeof json === 'object') { + return decode(json); + } + } catch (e) { + return null; + } +} + +var DefaultController = { + current() { + if (currentConfig) { + return currentConfig; + } + + var config = new ParseConfig(); + var storagePath = Storage.generatePath(CURRENT_CONFIG_KEY); + var configData; + if (!Storage.async()) { + configData = Storage.getItem(storagePath); + + if (configData) { + var attributes = decodePayload(configData); + if (attributes) { + config.attributes = attributes; + currentConfig = config; + } + } + return config; + } + // Return a promise for async storage controllers + return Storage.getItemAsync(storagePath).then(configData => { + if (configData) { + var attributes = decodePayload(configData); + if (attributes) { + config.attributes = attributes; + currentConfig = config; + } + } + return config; + }); + }, + + get() { + var RESTController = CoreManager.getRESTController(); + + return RESTController.request('GET', 'config', {}, {}).then(response => { + if (!response || !response.params) { + var error = new ParseError(ParseError.INVALID_JSON, 'Config JSON response invalid.'); + return ParsePromise.error(error); + } + + var config = new ParseConfig(); + config.attributes = {}; + for (var attr in response.params) { + config.attributes[attr] = decode(response.params[attr]); + } + currentConfig = config; + return Storage.setItemAsync(Storage.generatePath(CURRENT_CONFIG_KEY), JSON.stringify(response.params)).then(() => { + return config; + }); + }); + } +}; + +CoreManager.setConfigController(DefaultController); \ No newline at end of file diff --git a/lib/react-native/ParseError.js b/lib/react-native/ParseError.js new file mode 100644 index 000000000..a5bb24bc7 --- /dev/null +++ b/lib/react-native/ParseError.js @@ -0,0 +1,491 @@ +/** + * 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. + */ + +/** + * Constructs a new Parse.Error object with the given code and message. + * @class Parse.Error + * @constructor + * @param {Number} code An error code constant from 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:
                + *
              • success: A Backbone-style success callback. + *
              • error: An Backbone-style error callback. + *
              • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
              • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
              + * @return {Parse.Promise} A promise that is fulfilled when the fetch + * completes. + */ + fetch(options) { + options = options || {}; + var fetchOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + fetchOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + fetchOptions.sessionToken = options.sessionToken; + } + var controller = CoreManager.getObjectController(); + return controller.fetch(this, true, fetchOptions)._thenRunCallbacks(options); + } + + /** + * Set a hash of model attributes, and save the model to the server. + * updatedAt will be updated when the request returns. + * You can either call it as:
              +   *   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:
                + *
              • success: A Backbone-style success callback. + *
              • error: An Backbone-style error callback. + *
              • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
              • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
              + * @return {Parse.Promise} A promise that is fulfilled when the save + * completes. + */ + save(arg1, arg2, arg3) { + var attrs; + var options; + if (typeof arg1 === 'object' || typeof arg1 === 'undefined') { + attrs = arg1; + if (typeof arg2 === 'object') { + options = arg2; + } + } else { + attrs = {}; + attrs[arg1] = arg2; + options = arg3; + } + + // Support save({ success: function() {}, error: function() {} }) + if (!options && attrs) { + options = {}; + if (typeof attrs.success === 'function') { + options.success = attrs.success; + delete attrs.success; + } + if (typeof attrs.error === 'function') { + options.error = attrs.error; + delete attrs.error; + } + } + + if (attrs) { + var validation = this.validate(attrs); + if (validation) { + if (options && typeof options.error === 'function') { + options.error(this, validation); + } + return ParsePromise.error(validation); + } + this.set(attrs, options); + } + + options = options || {}; + var saveOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + saveOptions.useMasterKey = !!options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken') && typeof options.sessionToken === 'string') { + saveOptions.sessionToken = options.sessionToken; + } + + var controller = CoreManager.getObjectController(); + var unsaved = unsavedChildren(this); + return controller.save(unsaved, saveOptions).then(() => { + return controller.save(this, saveOptions); + })._thenRunCallbacks(options, this); + } + + /** + * Destroy this model on the server if it was already persisted. + * If `wait: true` is passed, waits for the server to respond + * before removal. + * + * @method destroy + * @param {Object} options A Backbone-style callback object. + * Valid options are:
                + *
              • success: A Backbone-style success callback + *
              • error: An Backbone-style error callback. + *
              • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
              • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
              + * @return {Parse.Promise} A promise that is fulfilled when the destroy + * completes. + */ + destroy(options) { + options = options || {}; + var destroyOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + destroyOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + destroyOptions.sessionToken = options.sessionToken; + } + if (!this.id) { + return ParsePromise.as()._thenRunCallbacks(options); + } + return CoreManager.getObjectController().destroy(this, destroyOptions)._thenRunCallbacks(options); + } + + /** Static methods **/ + + static _clearAllState() { + let stateController = CoreManager.getObjectStateController(); + stateController.clearAllState(); + } + + /** + * Fetches the given list of Parse.Object. + * If any error is encountered, stops and calls the error handler. + * + *
              +   *   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:
                + *
              • success: A Backbone-style success callback. + *
              • error: An Backbone-style error callback. + *
              + */ + static fetchAll(list, options) { + var options = options || {}; + + var queryOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + queryOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + queryOptions.sessionToken = options.sessionToken; + } + return CoreManager.getObjectController().fetch(list, true, queryOptions)._thenRunCallbacks(options); + } + + /** + * Fetches the given list of Parse.Object if needed. + * If any error is encountered, stops and calls the error handler. + * + *
              +   *   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:
                + *
              • success: A Backbone-style success callback. + *
              • error: An Backbone-style error callback. + *
              + */ + static fetchAllIfNeeded(list, options) { + var options = options || {}; + + var queryOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + queryOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + queryOptions.sessionToken = options.sessionToken; + } + return CoreManager.getObjectController().fetch(list, false, queryOptions)._thenRunCallbacks(options); + } + + /** + * Destroy the given list of models on the server if it was already persisted. + * + *

              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: + * + *

                + *
              • A Parse.Error.AGGREGATE_ERROR. This object's "errors" property is an + * array of other Parse.Error objects. Each error object in this array + * has an "object" property that references the object that could not be + * deleted (for instance, because that object could not be found).
              • + *
              • A non-aggregate Parse.Error. This indicates a serious error that + * caused the delete operation to be aborted partway through (for + * instance, a connection failure in the middle of the delete).
              • + *
              + * + *
              +   *   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:
                + *
              • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
              • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
              + * @return {Parse.Promise} A promise that is fulfilled when the destroyAll + * completes. + */ + static destroyAll(list, options) { + var options = options || {}; + + var destroyOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + destroyOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + destroyOptions.sessionToken = options.sessionToken; + } + return CoreManager.getObjectController().destroy(list, destroyOptions)._thenRunCallbacks(options); + } + + /** + * Saves the given list of Parse.Object. + * If any error is encountered, stops and calls the error handler. + * + *
              +   *   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:
                + *
              • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
              • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
              + */ + static saveAll(list, options) { + var options = options || {}; + + var saveOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + saveOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + saveOptions.sessionToken = options.sessionToken; + } + return CoreManager.getObjectController().save(list, saveOptions)._thenRunCallbacks(options); + } + + /** + * Creates a reference to a subclass of Parse.Object with the given id. This + * does not exist on Parse.Object, only on subclasses. + * + *

              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:
                + *
              • success: A Backbone-style success callback + *
              • error: An Backbone-style error callback. + *
              • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
              • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
              + * + * @return {Parse.Promise} A promise that is resolved with the result when + * the query completes. + */ + get(objectId, options) { + this.equalTo('objectId', objectId); + + var firstOptions = {}; + if (options && options.hasOwnProperty('useMasterKey')) { + firstOptions.useMasterKey = options.useMasterKey; + } + if (options && options.hasOwnProperty('sessionToken')) { + firstOptions.sessionToken = options.sessionToken; + } + + return this.first(firstOptions).then(response => { + if (response) { + return response; + } + + var errorObject = new ParseError(ParseError.OBJECT_NOT_FOUND, 'Object not found.'); + return ParsePromise.error(errorObject); + })._thenRunCallbacks(options, null); + } + + /** + * Retrieves a list of ParseObjects that satisfy this query. + * Either options.success or options.error is called when the find + * completes. + * + * @method find + * @param {Object} options A Backbone-style options object. Valid options + * are:
                + *
              • success: Function to call when the find completes successfully. + *
              • error: Function to call when the find fails. + *
              • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
              • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
              + * + * @return {Parse.Promise} A promise that is resolved with the results when + * the query completes. + */ + find(options) { + options = options || {}; + + let findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + let controller = CoreManager.getQueryController(); + + let select = this._select; + + return controller.find(this.className, this.toJSON(), findOptions).then(response => { + return response.results.map(data => { + // In cases of relations, the server may send back a className + // on the top level of the payload + let override = response.className || this.className; + if (!data.className) { + data.className = override; + } + + // Make sure the data object contains keys for all objects that + // have been requested with a select, so that our cached state + // updates correctly. + if (select) { + handleSelectResult(data, select); + } + + return ParseObject.fromJSON(data, !select); + }); + })._thenRunCallbacks(options); + } + + /** + * Counts the number of objects that match this query. + * Either options.success or options.error is called when the count + * completes. + * + * @method count + * @param {Object} options A Backbone-style options object. Valid options + * are:
                + *
              • success: Function to call when the count completes successfully. + *
              • error: Function to call when the find fails. + *
              • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
              • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
              + * + * @return {Parse.Promise} A promise that is resolved with the count when + * the query completes. + */ + count(options) { + options = options || {}; + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var controller = CoreManager.getQueryController(); + + var params = this.toJSON(); + params.limit = 0; + params.count = 1; + + return controller.find(this.className, params, findOptions).then(result => { + return result.count; + })._thenRunCallbacks(options); + } + + /** + * Retrieves at most one Parse.Object that satisfies this query. + * + * Either options.success or options.error is called when it completes. + * success is passed the object if there is one. otherwise, undefined. + * + * @method first + * @param {Object} options A Backbone-style options object. Valid options + * are:
                + *
              • success: Function to call when the find completes successfully. + *
              • error: Function to call when the find fails. + *
              • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
              • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
              + * + * @return {Parse.Promise} A promise that is resolved with the object when + * the query completes. + */ + first(options) { + options = options || {}; + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var controller = CoreManager.getQueryController(); + + var params = this.toJSON(); + params.limit = 1; + + var select = this._select; + + return controller.find(this.className, params, findOptions).then(response => { + var objects = response.results; + if (!objects[0]) { + return undefined; + } + if (!objects[0].className) { + objects[0].className = this.className; + } + + // Make sure the data object contains keys for all objects that + // have been requested with a select, so that our cached state + // updates correctly. + if (select) { + handleSelectResult(objects[0], select); + } + + return ParseObject.fromJSON(objects[0], !select); + })._thenRunCallbacks(options); + } + + /** + * Iterates over each result of a query, calling a callback for each one. If + * the callback returns a promise, the iteration will not continue until + * that promise has been fulfilled. If the callback returns a rejected + * promise, then iteration will stop with that error. The items are + * processed in an unspecified order. The query may not have any sort order, + * and may not use limit or skip. + * @method each + * @param {Function} callback Callback that will be called with each result + * of the query. + * @param {Object} options A Backbone-style options object. Valid options + * are:
                + *
              • success: Function to call when the iteration completes successfully. + *
              • error: Function to call when the iteration fails. + *
              • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
              • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
              + * @return {Parse.Promise} A promise that will be fulfilled once the + * iteration has completed. + */ + each(callback, options) { + options = options || {}; + + if (this._order || this._skip || this._limit >= 0) { + return ParsePromise.error('Cannot iterate on a query with sort, skip, or limit.')._thenRunCallbacks(options); + } + + new ParsePromise(); + + + var query = new ParseQuery(this.className); + // We can override the batch size from the options. + // This is undocumented, but useful for testing. + query._limit = options.batchSize || 100; + query._include = this._include.map(i => { + return i; + }); + if (this._select) { + query._select = this._select.map(s => { + return s; + }); + } + + query._where = {}; + for (var attr in this._where) { + var val = this._where[attr]; + if (Array.isArray(val)) { + query._where[attr] = val.map(v => { + return v; + }); + } else if (val && typeof val === 'object') { + var conditionMap = {}; + query._where[attr] = conditionMap; + for (var cond in val) { + conditionMap[cond] = val[cond]; + } + } else { + query._where[attr] = val; + } + } + + query.ascending('objectId'); + + var findOptions = {}; + if (options.hasOwnProperty('useMasterKey')) { + findOptions.useMasterKey = options.useMasterKey; + } + if (options.hasOwnProperty('sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var finished = false; + return ParsePromise._continueWhile(() => { + return !finished; + }, () => { + return query.find(findOptions).then(results => { + var callbacksDone = ParsePromise.as(); + results.forEach(result => { + callbacksDone = callbacksDone.then(() => { + return callback(result); + }); + }); + + return callbacksDone.then(() => { + if (results.length >= query._limit) { + query.greaterThan('objectId', results[results.length - 1].id); + } else { + finished = true; + } + }); + }); + })._thenRunCallbacks(options); + } + + /** Query Conditions **/ + + /** + * Adds a constraint to the query that requires a particular key's value to + * be equal to the provided value. + * @method equalTo + * @param {String} key The key to check. + * @param value The value that the Parse.Object must contain. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + equalTo(key, value) { + if (typeof value === 'undefined') { + return this.doesNotExist(key); + } + + this._where[key] = encode(value, false, true); + return this; + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be not equal to the provided value. + * @method notEqualTo + * @param {String} key The key to check. + * @param value The value that must not be equalled. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + notEqualTo(key, value) { + return this._addCondition(key, '$ne', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be less than the provided value. + * @method lessThan + * @param {String} key The key to check. + * @param value The value that provides an upper bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + lessThan(key, value) { + return this._addCondition(key, '$lt', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be greater than the provided value. + * @method greaterThan + * @param {String} key The key to check. + * @param value The value that provides an lower bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + greaterThan(key, value) { + return this._addCondition(key, '$gt', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be less than or equal to the provided value. + * @method lessThanOrEqualTo + * @param {String} key The key to check. + * @param value The value that provides an upper bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + lessThanOrEqualTo(key, value) { + return this._addCondition(key, '$lte', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be greater than or equal to the provided value. + * @method greaterThanOrEqualTo + * @param {String} key The key to check. + * @param value The value that provides an lower bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + greaterThanOrEqualTo(key, value) { + return this._addCondition(key, '$gte', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * be contained in the provided list of values. + * @method containedIn + * @param {String} key The key to check. + * @param {Array} values The values that will match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + containedIn(key, value) { + return this._addCondition(key, '$in', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * not be contained in the provided list of values. + * @method notContainedIn + * @param {String} key The key to check. + * @param {Array} values The values that will not match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + notContainedIn(key, value) { + return this._addCondition(key, '$nin', value); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * contain each one of the provided list of values. + * @method containsAll + * @param {String} key The key to check. This key's value must be an array. + * @param {Array} values The values that will match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + containsAll(key, values) { + return this._addCondition(key, '$all', values); + } + + /** + * Adds a constraint to the query that requires a particular key's value to + * contain each one of the provided list of values starting with given strings. + * @method containsAllStartingWith + * @param {String} key The key to check. This key's value must be an array. + * @param {Array} values The string values that will match as starting string. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + containsAllStartingWith(key, values) { + var _this = this; + if (!Array.isArray(values)) { + values = [values]; + } + + values = values.map(function (value) { + return { "$regex": _this._regexStartWith(value) }; + }); + + return this.containsAll(key, values); + } + + /** + * Adds a constraint for finding objects that contain the given key. + * @method exists + * @param {String} key The key that should exist. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + exists(key) { + return this._addCondition(key, '$exists', true); + } + + /** + * Adds a constraint for finding objects that do not contain a given key. + * @method doesNotExist + * @param {String} key The key that should not exist + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + doesNotExist(key) { + return this._addCondition(key, '$exists', false); + } + + /** + * Adds a regular expression constraint for finding string values that match + * the provided regular expression. + * This may be slow for large datasets. + * @method matches + * @param {String} key The key that the string to match is stored in. + * @param {RegExp} regex The regular expression pattern to match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + matches(key, regex, modifiers) { + this._addCondition(key, '$regex', regex); + if (!modifiers) { + modifiers = ''; + } + if (regex.ignoreCase) { + modifiers += 'i'; + } + if (regex.multiline) { + modifiers += 'm'; + } + if (modifiers.length) { + this._addCondition(key, '$options', modifiers); + } + return this; + } + + /** + * Adds a constraint that requires that a key's value matches a Parse.Query + * constraint. + * @method matchesQuery + * @param {String} key The key that the contains the object to match the + * query. + * @param {Parse.Query} query The query that should match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + matchesQuery(key, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$inQuery', queryJSON); + } + + /** + * Adds a constraint that requires that a key's value not matches a + * Parse.Query constraint. + * @method doesNotMatchQuery + * @param {String} key The key that the contains the object to match the + * query. + * @param {Parse.Query} query The query that should not match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + doesNotMatchQuery(key, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$notInQuery', queryJSON); + } + + /** + * Adds a constraint that requires that a key's value matches a value in + * an object returned by a different Parse.Query. + * @method matchesKeyInQuery + * @param {String} key The key that contains the value that is being + * matched. + * @param {String} queryKey The key in the objects returned by the query to + * match against. + * @param {Parse.Query} query The query to run. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + matchesKeyInQuery(key, queryKey, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$select', { + key: queryKey, + query: queryJSON + }); + } + + /** + * Adds a constraint that requires that a key's value not match a value in + * an object returned by a different Parse.Query. + * @method doesNotMatchKeyInQuery + * @param {String} key The key that contains the value that is being + * excluded. + * @param {String} queryKey The key in the objects returned by the query to + * match against. + * @param {Parse.Query} query The query to run. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + doesNotMatchKeyInQuery(key, queryKey, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + return this._addCondition(key, '$dontSelect', { + key: queryKey, + query: queryJSON + }); + } + + /** + * Adds a constraint for finding string values that contain a provided + * string. This may be slow for large datasets. + * @method contains + * @param {String} key The key that the string to match is stored in. + * @param {String} substring The substring that the value must contain. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + contains(key, value) { + if (typeof value !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + return this._addCondition(key, '$regex', quote(value)); + } + + /** + * Adds a constraint for finding string values that start with a provided + * string. This query will use the backend index, so it will be fast even + * for large datasets. + * @method startsWith + * @param {String} key The key that the string to match is stored in. + * @param {String} prefix The substring that the value must start with. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + startsWith(key, value) { + if (typeof value !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + return this._addCondition(key, '$regex', this._regexStartWith(value)); + } + + /** + * Adds a constraint for finding string values that end with a provided + * string. This will be slow for large datasets. + * @method endsWith + * @param {String} key The key that the string to match is stored in. + * @param {String} suffix The substring that the value must end with. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + endsWith(key, value) { + if (typeof value !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + return this._addCondition(key, '$regex', quote(value) + '$'); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given. + * @method near + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + near(key, point) { + if (!(point instanceof ParseGeoPoint)) { + // Try to cast it as a GeoPoint + point = new ParseGeoPoint(point); + } + return this._addCondition(key, '$nearSphere', point); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * @method withinRadians + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in radians) of results to + * return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + withinRadians(key, point, distance) { + this.near(key, point); + return this._addCondition(key, '$maxDistance', distance); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * Radius of earth used is 3958.8 miles. + * @method withinMiles + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in miles) of results to + * return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + withinMiles(key, point, distance) { + return this.withinRadians(key, point, distance / 3958.8); + } + + /** + * Adds a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * Radius of earth used is 6371.0 kilometers. + * @method withinKilometers + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in kilometers) of results + * to return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + withinKilometers(key, point, distance) { + return this.withinRadians(key, point, distance / 6371.0); + } + + /** + * Adds a constraint to the query that requires a particular key's + * coordinates be contained within a given rectangular geographic bounding + * box. + * @method withinGeoBox + * @param {String} key The key to be constrained. + * @param {Parse.GeoPoint} southwest + * The lower-left inclusive corner of the box. + * @param {Parse.GeoPoint} northeast + * The upper-right inclusive corner of the box. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + withinGeoBox(key, southwest, northeast) { + if (!(southwest instanceof ParseGeoPoint)) { + southwest = new ParseGeoPoint(southwest); + } + if (!(northeast instanceof ParseGeoPoint)) { + northeast = new ParseGeoPoint(northeast); + } + this._addCondition(key, '$within', { '$box': [southwest, northeast] }); + return this; + } + + /** Query Orderings **/ + + /** + * Sorts the results in ascending order by the given key. + * + * @method ascending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + ascending(...keys) { + this._order = []; + return this.addAscending.apply(this, keys); + } + + /** + * Sorts the results in ascending order by the given key, + * but can also add secondary sort descriptors without overwriting _order. + * + * @method addAscending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + addAscending(...keys) { + if (!this._order) { + this._order = []; + } + keys.forEach(key => { + if (Array.isArray(key)) { + key = key.join(); + } + this._order = this._order.concat(key.replace(/\s/g, '').split(',')); + }); + + return this; + } + + /** + * Sorts the results in descending order by the given key. + * + * @method descending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + descending(...keys) { + this._order = []; + return this.addDescending.apply(this, keys); + } + + /** + * Sorts the results in descending order by the given key, + * but can also add secondary sort descriptors without overwriting _order. + * + * @method addDescending + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + addDescending(...keys) { + if (!this._order) { + this._order = []; + } + keys.forEach(key => { + if (Array.isArray(key)) { + key = key.join(); + } + this._order = this._order.concat(key.replace(/\s/g, '').split(',').map(k => { + return '-' + k; + })); + }); + + return this; + } + + /** Query Options **/ + + /** + * Sets the number of results to skip before returning any results. + * This is useful for pagination. + * Default is to skip zero results. + * @method skip + * @param {Number} n the number of results to skip. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + skip(n) { + if (typeof n !== 'number' || n < 0) { + throw new Error('You can only skip by a positive number'); + } + this._skip = n; + return this; + } + + /** + * Sets the limit of the number of results to return. The default limit is + * 100, with a maximum of 1000 results being returned at a time. + * @method limit + * @param {Number} n the number of results to limit to. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + limit(n) { + if (typeof n !== 'number') { + throw new Error('You can only set the limit to a numeric value'); + } + this._limit = n; + return this; + } + + /** + * Includes nested Parse.Objects for the provided key. You can use dot + * notation to specify which fields in the included object are also fetched. + * @method include + * @param {String} key The name of the key to include. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + include(...keys) { + keys.forEach(key => { + if (Array.isArray(key)) { + this._include = this._include.concat(key); + } else { + this._include.push(key); + } + }); + return this; + } + + /** + * Restricts the fields of the returned Parse.Objects to include only the + * provided keys. If this is called multiple times, then all of the keys + * specified in each of the calls will be included. + * @method select + * @param {Array} keys The names of the keys to include. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + select(...keys) { + if (!this._select) { + this._select = []; + } + keys.forEach(key => { + if (Array.isArray(key)) { + this._select = this._select.concat(key); + } else { + this._select.push(key); + } + }); + return this; + } + + /** + * Subscribe this query to get liveQuery updates + * @method subscribe + * @return {LiveQuerySubscription} Returns the liveQuerySubscription, it's an event emitter + * which can be used to get liveQuery updates. + */ + subscribe() { + let controller = CoreManager.getLiveQueryController(); + return controller.subscribe(this); + } + + /** + * Constructs a Parse.Query that is the OR of the passed in queries. For + * example: + *
              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 if current 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 using current. + * + *

              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: + *
                + *
              1. channels - An Array of channels to push to.
              2. + *
              3. push_time - A Date object for when to send the push.
              4. + *
              5. expiration_time - A Date object for when to expire + * the push.
              6. + *
              7. expiration_interval - The seconds from now to expire the push.
              8. + *
              9. where - A Parse.Query over Parse.Installation that is used to match + * a set of installations to push to.
              10. + *
              11. data - The data to send as part of the push
              12. + *
                  + * @param {Object} options An object that has an optional success function, + * that takes no arguments and will be called on a successful push, and + * an error function that takes a Parse.Error and will be called if the push + * failed. + * @return {Parse.Promise} A promise that is fulfilled when the push request + * completes. + */ +export function send(data, options) { + options = options || {}; + + if (data.where && data.where instanceof ParseQuery) { + data.where = data.where.toJSON().where; + } + + if (data.push_time && typeof data.push_time === 'object') { + data.push_time = data.push_time.toJSON(); + } + + if (data.expiration_time && typeof data.expiration_time === 'object') { + data.expiration_time = data.expiration_time.toJSON(); + } + + if (data.expiration_time && data.expiration_interval) { + throw new Error('expiration_time and expiration_interval cannot both be set.'); + } + + return CoreManager.getPushController().send(data, { + useMasterKey: options.useMasterKey + })._thenRunCallbacks(options); +} + +var DefaultController = { + send(data, options) { + var RESTController = CoreManager.getRESTController(); + + var request = RESTController.request('POST', 'push', data, { useMasterKey: !!options.useMasterKey }); + + return request._thenRunCallbacks(options); + } +}; + +CoreManager.setPushController(DefaultController); \ No newline at end of file diff --git a/lib/react-native/RESTController.js b/lib/react-native/RESTController.js new file mode 100644 index 000000000..89ee421a0 --- /dev/null +++ b/lib/react-native/RESTController.js @@ -0,0 +1,226 @@ +/** + * 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 ParseError from './ParseError'; +import ParsePromise from './ParsePromise'; +import Storage from './Storage'; + +var XHR = null; +if (typeof XMLHttpRequest !== 'undefined') { + XHR = XMLHttpRequest; +} + + +var useXDomainRequest = false; +if (typeof XDomainRequest !== 'undefined' && !('withCredentials' in new XMLHttpRequest())) { + useXDomainRequest = true; +} + +function ajaxIE9(method, url, data) { + var promise = new ParsePromise(); + var xdr = new XDomainRequest(); + xdr.onload = function () { + var response; + try { + response = JSON.parse(xdr.responseText); + } catch (e) { + promise.reject(e); + } + if (response) { + promise.resolve(response); + } + }; + xdr.onerror = xdr.ontimeout = function () { + // Let's fake a real error message. + var fakeResponse = { + responseText: JSON.stringify({ + code: ParseError.X_DOMAIN_REQUEST, + error: 'IE\'s XDomainRequest does not supply error info.' + }) + }; + promise.reject(fakeResponse); + }; + xdr.onprogress = function () {}; + xdr.open(method, url); + xdr.send(data); + return promise; +} + +const RESTController = { + ajax(method, url, data, headers) { + if (useXDomainRequest) { + return ajaxIE9(method, url, data, headers); + } + + var promise = new ParsePromise(); + var attempts = 0; + + var dispatch = function () { + if (XHR == null) { + throw new Error('Cannot make a request: No definition of XMLHttpRequest was found.'); + } + var handled = false; + var xhr = new XHR(); + + xhr.onreadystatechange = function () { + if (xhr.readyState !== 4 || handled) { + return; + } + handled = true; + + if (xhr.status >= 200 && xhr.status < 300) { + var response; + try { + response = JSON.parse(xhr.responseText); + } catch (e) { + promise.reject(e.toString()); + } + if (response) { + promise.resolve(response, xhr.status, xhr); + } + } else if (xhr.status >= 500 || xhr.status === 0) { + // retry on 5XX or node-xmlhttprequest error + if (++attempts < CoreManager.get('REQUEST_ATTEMPT_LIMIT')) { + // Exponentially-growing random delay + var delay = Math.round(Math.random() * 125 * Math.pow(2, attempts)); + setTimeout(dispatch, delay); + } else if (xhr.status === 0) { + promise.reject('Unable to connect to the Parse API'); + } else { + // After the retry limit is reached, fail + promise.reject(xhr); + } + } else { + promise.reject(xhr); + } + }; + + headers = headers || {}; + if (typeof headers['Content-Type'] !== 'string') { + headers['Content-Type'] = 'text/plain'; // Avoid pre-flight + } + if (CoreManager.get('IS_NODE')) { + headers['User-Agent'] = 'Parse/' + CoreManager.get('VERSION') + ' (NodeJS ' + process.versions.node + ')'; + } + + xhr.open(method, url, true); + for (var h in headers) { + xhr.setRequestHeader(h, headers[h]); + } + xhr.send(data); + }; + dispatch(); + + return promise; + }, + + request(method, path, data, options) { + options = options || {}; + var url = CoreManager.get('SERVER_URL'); + if (url[url.length - 1] !== '/') { + url += '/'; + } + url += path; + + var payload = {}; + if (data && typeof data === 'object') { + for (var k in data) { + payload[k] = data[k]; + } + } + + if (method !== 'POST') { + payload._method = method; + method = 'POST'; + } + + payload._ApplicationId = CoreManager.get('APPLICATION_ID'); + let jsKey = CoreManager.get('JAVASCRIPT_KEY'); + if (jsKey) { + payload._JavaScriptKey = jsKey; + } + payload._ClientVersion = CoreManager.get('VERSION'); + + var useMasterKey = options.useMasterKey; + if (typeof useMasterKey === 'undefined') { + useMasterKey = CoreManager.get('USE_MASTER_KEY'); + } + if (useMasterKey) { + if (CoreManager.get('MASTER_KEY')) { + delete payload._JavaScriptKey; + payload._MasterKey = CoreManager.get('MASTER_KEY'); + } else { + throw new Error('Cannot use the Master Key, it has not been provided.'); + } + } + + if (CoreManager.get('FORCE_REVOCABLE_SESSION')) { + payload._RevocableSession = '1'; + } + + var installationId = options.installationId; + var installationIdPromise; + if (installationId && typeof installationId === 'string') { + installationIdPromise = ParsePromise.as(installationId); + } else { + var installationController = CoreManager.getInstallationController(); + installationIdPromise = installationController.currentInstallationId(); + } + + return installationIdPromise.then(iid => { + payload._InstallationId = iid; + var userController = CoreManager.getUserController(); + if (options && typeof options.sessionToken === 'string') { + return ParsePromise.as(options.sessionToken); + } else if (userController) { + return userController.currentUserAsync().then(user => { + if (user) { + return ParsePromise.as(user.getSessionToken()); + } + return ParsePromise.as(null); + }); + } + return ParsePromise.as(null); + }).then(token => { + if (token) { + payload._SessionToken = token; + } + + var payloadString = JSON.stringify(payload); + + return RESTController.ajax(method, url, payloadString); + }).then(null, function (response) { + // Transform the error into an instance of ParseError by trying to parse + // the error string as JSON + var error; + if (response && response.responseText) { + try { + var errorJSON = JSON.parse(response.responseText); + error = new ParseError(errorJSON.code, errorJSON.error); + } catch (e) { + // If we fail to parse the error text, that's okay. + error = new ParseError(ParseError.INVALID_JSON, 'Received an error with invalid JSON from Parse: ' + response.responseText); + } + } else { + error = new ParseError(ParseError.CONNECTION_FAILED, 'XMLHttpRequest failed: ' + JSON.stringify(response)); + } + + return ParsePromise.error(error); + }); + }, + + _setXHR(xhr) { + XHR = xhr; + } +}; + +module.exports = RESTController; \ No newline at end of file diff --git a/lib/react-native/SingleInstanceStateController.js b/lib/react-native/SingleInstanceStateController.js new file mode 100644 index 000000000..2f57854d7 --- /dev/null +++ b/lib/react-native/SingleInstanceStateController.js @@ -0,0 +1,125 @@ +/** + * 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 * as ObjectStateMutations from './ObjectStateMutations'; + +let objectState = {}; + +export function getState(obj) { + let classData = objectState[obj.className]; + if (classData) { + return classData[obj.id] || null; + } + return null; +} + +export function initializeState(obj, initial) { + let state = getState(obj); + if (state) { + return state; + } + if (!objectState[obj.className]) { + objectState[obj.className] = {}; + } + if (!initial) { + initial = ObjectStateMutations.defaultState(); + } + state = objectState[obj.className][obj.id] = initial; + return state; +} + +export function removeState(obj) { + let state = getState(obj); + if (state === null) { + return null; + } + delete objectState[obj.className][obj.id]; + return state; +} + +export function getServerData(obj) { + let state = getState(obj); + if (state) { + return state.serverData; + } + return {}; +} + +export function setServerData(obj, attributes) { + let serverData = initializeState(obj).serverData; + ObjectStateMutations.setServerData(serverData, attributes); +} + +export function getPendingOps(obj) { + let state = getState(obj); + if (state) { + return state.pendingOps; + } + return [{}]; +} + +export function setPendingOp(obj, attr, op) { + let pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.setPendingOp(pendingOps, attr, op); +} + +export function pushPendingState(obj) { + let pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.pushPendingState(pendingOps); +} + +export function popPendingState(obj) { + let pendingOps = initializeState(obj).pendingOps; + return ObjectStateMutations.popPendingState(pendingOps); +} + +export function mergeFirstPendingState(obj) { + let pendingOps = getPendingOps(obj); + ObjectStateMutations.mergeFirstPendingState(pendingOps); +} + +export function getObjectCache(obj) { + let state = getState(obj); + if (state) { + return state.objectCache; + } + return {}; +} + +export function estimateAttribute(obj, attr) { + let serverData = getServerData(obj); + let pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttribute(serverData, pendingOps, obj.className, obj.id, attr); +} + +export function estimateAttributes(obj) { + let serverData = getServerData(obj); + let pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttributes(serverData, pendingOps, obj.className, obj.id); +} + +export function commitServerChanges(obj, changes) { + let state = initializeState(obj); + ObjectStateMutations.commitServerChanges(state.serverData, state.objectCache, changes); +} + +export function enqueueTask(obj, task) { + let state = initializeState(obj); + return state.tasks.enqueue(task); +} + +export function clearAllState() { + objectState = {}; +} + +export function duplicateState(source, dest) { + dest.id = source.id; +} \ No newline at end of file diff --git a/lib/react-native/Storage.js b/lib/react-native/Storage.js new file mode 100644 index 000000000..2bfa47412 --- /dev/null +++ b/lib/react-native/Storage.js @@ -0,0 +1,92 @@ +/** + * 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 Storage = { + async() { + var controller = CoreManager.getStorageController(); + return !!controller.async; + }, + + getItem(path) { + var controller = CoreManager.getStorageController(); + if (controller.async === 1) { + throw new Error('Synchronous storage is not supported by the current storage controller'); + } + return controller.getItem(path); + }, + + getItemAsync(path) { + var controller = CoreManager.getStorageController(); + if (controller.async === 1) { + return controller.getItemAsync(path); + } + return ParsePromise.as(controller.getItem(path)); + }, + + setItem(path, value) { + var controller = CoreManager.getStorageController(); + if (controller.async === 1) { + throw new Error('Synchronous storage is not supported by the current storage controller'); + } + return controller.setItem(path, value); + }, + + setItemAsync(path, value) { + var controller = CoreManager.getStorageController(); + if (controller.async === 1) { + return controller.setItemAsync(path, value); + } + return ParsePromise.as(controller.setItem(path, value)); + }, + + removeItem(path) { + var controller = CoreManager.getStorageController(); + if (controller.async === 1) { + throw new Error('Synchronous storage is not supported by the current storage controller'); + } + return controller.removeItem(path); + }, + + removeItemAsync(path) { + var controller = CoreManager.getStorageController(); + if (controller.async === 1) { + return controller.removeItemAsync(path); + } + return ParsePromise.as(controller.removeItem(path)); + }, + + generatePath(path) { + if (!CoreManager.get('APPLICATION_ID')) { + throw new Error('You need to call Parse.initialize before using Parse.'); + } + if (typeof path !== 'string') { + throw new Error('Tried to get a Storage path that was not a String.'); + } + if (path[0] === '/') { + path = path.substr(1); + } + return 'Parse/' + CoreManager.get('APPLICATION_ID') + '/' + path; + }, + + _clear() { + var controller = CoreManager.getStorageController(); + if (controller.hasOwnProperty('clear')) { + controller.clear(); + } + } +}; + +module.exports = Storage; + +CoreManager.setStorageController(require('./StorageController.react-native')); \ No newline at end of file diff --git a/lib/react-native/StorageController.browser.js b/lib/react-native/StorageController.browser.js new file mode 100644 index 000000000..84af3ccee --- /dev/null +++ b/lib/react-native/StorageController.browser.js @@ -0,0 +1,38 @@ +/** + * 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'; + +var StorageController = { + async: 0, + + getItem(path) { + return localStorage.getItem(path); + }, + + setItem(path, value) { + try { + localStorage.setItem(path, value); + } catch (e) { + // Quota exceeded, possibly due to Safari Private Browsing mode + } + }, + + removeItem(path) { + localStorage.removeItem(path); + }, + + clear() { + localStorage.clear(); + } +}; + +module.exports = StorageController; \ No newline at end of file diff --git a/lib/react-native/StorageController.default.js b/lib/react-native/StorageController.default.js new file mode 100644 index 000000000..ded857850 --- /dev/null +++ b/lib/react-native/StorageController.default.js @@ -0,0 +1,41 @@ +/** + * 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. + * + * + */ + +// When there is no native storage interface, we default to an in-memory map +var memMap = {}; +var StorageController = { + async: 0, + + getItem(path) { + if (memMap.hasOwnProperty(path)) { + return memMap[path]; + } + return null; + }, + + setItem(path, value) { + memMap[path] = String(value); + }, + + removeItem(path) { + delete memMap[path]; + }, + + clear() { + for (var key in memMap) { + if (memMap.hasOwnProperty(key)) { + delete memMap[key]; + } + } + } +}; + +module.exports = StorageController; \ No newline at end of file diff --git a/lib/react-native/StorageController.react-native.js b/lib/react-native/StorageController.react-native.js new file mode 100644 index 000000000..68e04d768 --- /dev/null +++ b/lib/react-native/StorageController.react-native.js @@ -0,0 +1,60 @@ +/** + * 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'; +// RN packager nonsense +import { AsyncStorage } from 'react-native/Libraries/react-native/react-native.js'; + +var StorageController = { + async: 1, + + getItemAsync(path) { + var p = new ParsePromise(); + AsyncStorage.getItem(path, function (err, value) { + if (err) { + p.reject(err); + } else { + p.resolve(value); + } + }); + return p; + }, + + setItemAsync(path, value) { + var p = new ParsePromise(); + AsyncStorage.setItem(path, value, function (err) { + if (err) { + p.reject(err); + } else { + p.resolve(value); + } + }); + return p; + }, + + removeItemAsync(path) { + var p = new ParsePromise(); + AsyncStorage.removeItem(path, function (err) { + if (err) { + p.reject(err); + } else { + p.resolve(); + } + }); + return p; + }, + + clear() { + AsyncStorage.clear(); + } +}; + +module.exports = StorageController; \ No newline at end of file diff --git a/lib/react-native/TaskQueue.js b/lib/react-native/TaskQueue.js new file mode 100644 index 000000000..584168339 --- /dev/null +++ b/lib/react-native/TaskQueue.js @@ -0,0 +1,53 @@ +/** + * 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'; + +class TaskQueue { + + constructor() { + this.queue = []; + } + + enqueue(task) { + var taskComplete = new ParsePromise(); + this.queue.push({ + task: task, + _completion: taskComplete + }); + if (this.queue.length === 1) { + task().then(() => { + this._dequeue(); + taskComplete.resolve(); + }, error => { + this._dequeue(); + taskComplete.reject(error); + }); + } + return taskComplete; + } + + _dequeue() { + this.queue.shift(); + if (this.queue.length) { + var next = this.queue[0]; + next.task().then(() => { + this._dequeue(); + next._completion.resolve(); + }, error => { + this._dequeue(); + next._completion.reject(error); + }); + } + } +} + +module.exports = TaskQueue; \ No newline at end of file diff --git a/lib/react-native/UniqueInstanceStateController.js b/lib/react-native/UniqueInstanceStateController.js new file mode 100644 index 000000000..2b112e228 --- /dev/null +++ b/lib/react-native/UniqueInstanceStateController.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 * as ObjectStateMutations from './ObjectStateMutations'; +import TaskQueue from './TaskQueue'; + +let objectState = new WeakMap(); + +export function getState(obj) { + let classData = objectState.get(obj); + return classData || null; +} + +export function initializeState(obj, initial) { + let state = getState(obj); + if (state) { + return state; + } + if (!initial) { + initial = { + serverData: {}, + pendingOps: [{}], + objectCache: {}, + tasks: new TaskQueue(), + existed: false + }; + } + state = initial; + objectState.set(obj, state); + return state; +} + +export function removeState(obj) { + let state = getState(obj); + if (state === null) { + return null; + } + objectState.delete(obj); + return state; +} + +export function getServerData(obj) { + let state = getState(obj); + if (state) { + return state.serverData; + } + return {}; +} + +export function setServerData(obj, attributes) { + let serverData = initializeState(obj).serverData; + ObjectStateMutations.setServerData(serverData, attributes); +} + +export function getPendingOps(obj) { + let state = getState(obj); + if (state) { + return state.pendingOps; + } + return [{}]; +} + +export function setPendingOp(obj, attr, op) { + let pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.setPendingOp(pendingOps, attr, op); +} + +export function pushPendingState(obj) { + let pendingOps = initializeState(obj).pendingOps; + ObjectStateMutations.pushPendingState(pendingOps); +} + +export function popPendingState(obj) { + let pendingOps = initializeState(obj).pendingOps; + return ObjectStateMutations.popPendingState(pendingOps); +} + +export function mergeFirstPendingState(obj) { + let pendingOps = getPendingOps(obj); + ObjectStateMutations.mergeFirstPendingState(pendingOps); +} + +export function getObjectCache(obj) { + let state = getState(obj); + if (state) { + return state.objectCache; + } + return {}; +} + +export function estimateAttribute(obj, attr) { + let serverData = getServerData(obj); + let pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttribute(serverData, pendingOps, obj.className, obj.id, attr); +} + +export function estimateAttributes(obj) { + let serverData = getServerData(obj); + let pendingOps = getPendingOps(obj); + return ObjectStateMutations.estimateAttributes(serverData, pendingOps, obj.className, obj.id); +} + +export function commitServerChanges(obj, changes) { + let state = initializeState(obj); + ObjectStateMutations.commitServerChanges(state.serverData, state.objectCache, changes); +} + +export function enqueueTask(obj, task) { + let state = initializeState(obj); + return state.tasks.enqueue(task); +} + +export function duplicateState(source, dest) { + let oldState = initializeState(source); + let newState = initializeState(dest); + for (let key in oldState.serverData) { + newState.serverData[key] = oldState.serverData[key]; + } + for (let index = 0; index < oldState.pendingOps.length; index++) { + for (let key in oldState.pendingOps[index]) { + newState.pendingOps[index][key] = oldState.pendingOps[index][key]; + } + } + for (let key in oldState.objectCache) { + newState.objectCache[key] = oldState.objectCache[key]; + } + newState.existed = oldState.existed; +} + +export function clearAllState() { + objectState = new WeakMap(); +} \ No newline at end of file diff --git a/lib/react-native/arrayContainsObject.js b/lib/react-native/arrayContainsObject.js new file mode 100644 index 000000000..153c7320b --- /dev/null +++ b/lib/react-native/arrayContainsObject.js @@ -0,0 +1,24 @@ +/** + * 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 function arrayContainsObject(array, object) { + if (array.indexOf(object) > -1) { + return true; + } + for (var i = 0; i < array.length; i++) { + if (array[i] instanceof ParseObject && array[i].className === object.className && array[i]._getId() === object._getId()) { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/lib/react-native/canBeSerialized.js b/lib/react-native/canBeSerialized.js new file mode 100644 index 000000000..b696d8028 --- /dev/null +++ b/lib/react-native/canBeSerialized.js @@ -0,0 +1,60 @@ +/** + * 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 ParseFile from './ParseFile'; +import ParseObject from './ParseObject'; +import ParseRelation from './ParseRelation'; + +export default function canBeSerialized(obj) { + if (!(obj instanceof ParseObject)) { + return true; + } + var attributes = obj.attributes; + for (var attr in attributes) { + var val = attributes[attr]; + if (!canBeSerializedHelper(val)) { + return false; + } + } + return true; +} + +function canBeSerializedHelper(value) { + if (typeof value !== 'object') { + return true; + } + if (value instanceof ParseRelation) { + return true; + } + if (value instanceof ParseObject) { + return !!value.id; + } + if (value instanceof ParseFile) { + if (value.url()) { + return true; + } + return false; + } + if (Array.isArray(value)) { + for (var i = 0; i < value.length; i++) { + if (!canBeSerializedHelper(value[i])) { + return false; + } + } + return true; + } + for (var k in value) { + if (!canBeSerializedHelper(value[k])) { + return false; + } + } + return true; +} \ No newline at end of file diff --git a/lib/react-native/decode.js b/lib/react-native/decode.js new file mode 100644 index 000000000..9863da760 --- /dev/null +++ b/lib/react-native/decode.js @@ -0,0 +1,62 @@ +/** + * 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 ParseFile from './ParseFile'; +import ParseGeoPoint from './ParseGeoPoint'; +import ParseObject from './ParseObject'; +import { opFromJSON } from './ParseOp'; +import ParseRelation from './ParseRelation'; + +export default function decode(value) { + if (value === null || typeof value !== 'object') { + return value; + } + if (Array.isArray(value)) { + var dup = []; + value.forEach((v, i) => { + dup[i] = decode(v); + }); + return dup; + } + if (typeof value.__op === 'string') { + return opFromJSON(value); + } + if (value.__type === 'Pointer' && value.className) { + return ParseObject.fromJSON(value); + } + if (value.__type === 'Object' && value.className) { + return ParseObject.fromJSON(value); + } + if (value.__type === 'Relation') { + // The parent and key fields will be populated by the parent + var relation = new ParseRelation(null, null); + relation.targetClassName = value.className; + return relation; + } + if (value.__type === 'Date') { + return new Date(value.iso); + } + if (value.__type === 'File') { + return ParseFile.fromJSON(value); + } + if (value.__type === 'GeoPoint') { + return new ParseGeoPoint({ + latitude: value.latitude, + longitude: value.longitude + }); + } + var copy = {}; + for (var k in value) { + copy[k] = decode(value[k]); + } + return copy; +} \ No newline at end of file diff --git a/lib/react-native/encode.js b/lib/react-native/encode.js new file mode 100644 index 000000000..74a925352 --- /dev/null +++ b/lib/react-native/encode.js @@ -0,0 +1,71 @@ +/** + * 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 ParseFile from './ParseFile'; +import ParseGeoPoint from './ParseGeoPoint'; +import ParseObject from './ParseObject'; +import { Op } from './ParseOp'; +import ParseRelation from './ParseRelation'; + +var toString = Object.prototype.toString; + +function encode(value, disallowObjects, forcePointers, seen) { + if (value instanceof ParseObject) { + if (disallowObjects) { + throw new Error('Parse Objects not allowed here'); + } + var seenEntry = value.id ? value.className + ':' + value.id : value; + if (forcePointers || !seen || seen.indexOf(seenEntry) > -1 || value.dirty() || Object.keys(value._getServerData()).length < 1) { + return value.toPointer(); + } + seen = seen.concat(seenEntry); + return value._toFullJSON(seen); + } + if (value instanceof Op || value instanceof ParseACL || value instanceof ParseGeoPoint || value instanceof ParseRelation) { + return value.toJSON(); + } + if (value instanceof ParseFile) { + if (!value.url()) { + throw new Error('Tried to encode an unsaved file.'); + } + return value.toJSON(); + } + if (toString.call(value) === '[object Date]') { + if (isNaN(value)) { + throw new Error('Tried to encode an invalid date.'); + } + return { __type: 'Date', iso: value.toJSON() }; + } + if (toString.call(value) === '[object RegExp]' && typeof value.source === 'string') { + return value.source; + } + + if (Array.isArray(value)) { + return value.map(v => { + return encode(v, disallowObjects, forcePointers, seen); + }); + } + + if (value && typeof value === 'object') { + var output = {}; + for (var k in value) { + output[k] = encode(value[k], disallowObjects, forcePointers, seen); + } + return output; + } + + return value; +} + +export default function (value, disallowObjects, forcePointers, seen) { + return encode(value, !!disallowObjects, !!forcePointers, seen || []); +} \ No newline at end of file diff --git a/lib/react-native/equals.js b/lib/react-native/equals.js new file mode 100644 index 000000000..06ff31f3c --- /dev/null +++ b/lib/react-native/equals.js @@ -0,0 +1,53 @@ +/** + * 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 ParseFile from './ParseFile'; +import ParseGeoPoint from './ParseGeoPoint'; +import ParseObject from './ParseObject'; + +export default function equals(a, b) { + if (typeof a !== typeof b) { + return false; + } + + if (!a || typeof a !== 'object') { + // a is a primitive + return a === b; + } + + if (Array.isArray(a) || Array.isArray(b)) { + if (!Array.isArray(a) || !Array.isArray(b)) { + return false; + } + if (a.length !== b.length) { + return false; + } + for (var i = a.length; i--;) { + if (!equals(a[i], b[i])) { + return false; + } + } + return true; + } + + if (a instanceof ParseACL || a instanceof ParseFile || a instanceof ParseGeoPoint || a instanceof ParseObject) { + return a.equals(b); + } + + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + for (var k in a) { + if (!equals(a[k], b[k])) { + return false; + } + } + return true; +} \ No newline at end of file diff --git a/lib/react-native/escape.js b/lib/react-native/escape.js new file mode 100644 index 000000000..e094f0747 --- /dev/null +++ b/lib/react-native/escape.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. + * + * + */ + +var encoded = { + '&': '&', + '<': '<', + '>': '>', + '/': '/', + '\'': ''', + '"': '"' +}; + +export default function escape(str) { + return str.replace(/[&<>\/'"]/g, function (char) { + return encoded[char]; + }); +} \ No newline at end of file diff --git a/lib/react-native/isRevocableSession.js b/lib/react-native/isRevocableSession.js new file mode 100644 index 000000000..967341a57 --- /dev/null +++ b/lib/react-native/isRevocableSession.js @@ -0,0 +1,14 @@ +/** + * 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. + * + * + */ + +export default function isRevocableSession(token) { + return token.indexOf('r:') > -1; +} \ No newline at end of file diff --git a/lib/react-native/parseDate.js b/lib/react-native/parseDate.js new file mode 100644 index 000000000..fe7538e41 --- /dev/null +++ b/lib/react-native/parseDate.js @@ -0,0 +1,28 @@ +/** + * 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. + * + * + */ + +export default function parseDate(iso8601) { + var regexp = new RegExp('^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})' + 'T' + '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})' + '(.([0-9]+))?' + 'Z$'); + var match = regexp.exec(iso8601); + if (!match) { + return null; + } + + var year = match[1] || 0; + var month = (match[2] || 1) - 1; + var day = match[3] || 0; + var hour = match[4] || 0; + var minute = match[5] || 0; + var second = match[6] || 0; + var milli = match[8] || 0; + + return new Date(Date.UTC(year, month, day, hour, minute, second, milli)); +} \ No newline at end of file diff --git a/lib/react-native/unique.js b/lib/react-native/unique.js new file mode 100644 index 000000000..c3d1e6364 --- /dev/null +++ b/lib/react-native/unique.js @@ -0,0 +1,29 @@ +/** + * 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 ParseObject from './ParseObject'; + +export default function unique(arr) { + var uniques = []; + arr.forEach(value => { + if (value instanceof ParseObject) { + if (!arrayContainsObject(uniques, value)) { + uniques.push(value); + } + } else { + if (uniques.indexOf(value) < 0) { + uniques.push(value); + } + } + }); + return uniques; +} \ No newline at end of file diff --git a/lib/react-native/unsavedChildren.js b/lib/react-native/unsavedChildren.js new file mode 100644 index 000000000..5b71deea0 --- /dev/null +++ b/lib/react-native/unsavedChildren.js @@ -0,0 +1,80 @@ +/** + * 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 ParseFile from './ParseFile'; +import ParseObject from './ParseObject'; +import ParseRelation from './ParseRelation'; + +/** + * Return an array of unsaved children, which are either Parse Objects or Files. + * If it encounters any dirty Objects without Ids, it will throw an exception. + */ +export default function unsavedChildren(obj, allowDeepUnsaved) { + var encountered = { + objects: {}, + files: [] + }; + var identifier = obj.className + ':' + obj._getId(); + encountered.objects[identifier] = obj.dirty() ? obj : true; + var attributes = obj.attributes; + for (var attr in attributes) { + if (typeof attributes[attr] === 'object') { + traverse(attributes[attr], encountered, false, !!allowDeepUnsaved); + } + } + var unsaved = []; + for (var id in encountered.objects) { + if (id !== identifier && encountered.objects[id] !== true) { + unsaved.push(encountered.objects[id]); + } + } + return unsaved.concat(encountered.files); +} + +function traverse(obj, encountered, shouldThrow, allowDeepUnsaved) { + if (obj instanceof ParseObject) { + if (!obj.id && shouldThrow) { + throw new Error('Cannot create a pointer to an unsaved Object.'); + } + var identifier = obj.className + ':' + obj._getId(); + if (!encountered.objects[identifier]) { + encountered.objects[identifier] = obj.dirty() ? obj : true; + var attributes = obj.attributes; + for (var attr in attributes) { + if (typeof attributes[attr] === 'object') { + traverse(attributes[attr], encountered, !allowDeepUnsaved, allowDeepUnsaved); + } + } + } + return; + } + if (obj instanceof ParseFile) { + if (!obj.url() && encountered.files.indexOf(obj) < 0) { + encountered.files.push(obj); + } + return; + } + if (obj instanceof ParseRelation) { + return; + } + if (Array.isArray(obj)) { + obj.forEach(el => { + if (typeof el === 'object') { + traverse(el, encountered, shouldThrow, allowDeepUnsaved); + } + }); + } + for (var k in obj) { + if (typeof obj[k] === 'object') { + traverse(obj[k], encountered, shouldThrow, allowDeepUnsaved); + } + } +} \ No newline at end of file diff --git a/src/ParseQuery.js b/src/ParseQuery.js index 78c148853..89c9c142a 100644 --- a/src/ParseQuery.js +++ b/src/ParseQuery.js @@ -229,6 +229,13 @@ export default class ParseQuery { return this; } + /** + * Converts string for regular expression at the beginning + */ + _regexStartWith(string: string): String { + return '^' + quote(string); + } + /** * Returns a JSON representation of this query. * @method toJSON @@ -682,6 +689,27 @@ export default class ParseQuery { return this._addCondition(key, '$all', values); } + /** + * Adds a constraint to the query that requires a particular key's value to + * contain each one of the provided list of values starting with given strings. + * @method containsAllStartingWith + * @param {String} key The key to check. This key's value must be an array. + * @param {Array} values The string values that will match as starting string. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + containsAllStartingWith(key: string, values: Array): ParseQuery { + var _this = this; + if (!Array.isArray(values)) { + values = [values]; + } + + values = values.map(function (value) { + return {"$regex": _this._regexStartWith(value)}; + }); + + return this.containsAll(key, values); + } + /** * Adds a constraint for finding objects that contain the given key. * @method exists @@ -826,7 +854,7 @@ export default class ParseQuery { if (typeof value !== 'string') { throw new Error('The value being searched for must be a string.'); } - return this._addCondition(key, '$regex', '^' + quote(value)); + return this._addCondition(key, '$regex', this._regexStartWith(value)); } /** diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js index c33458f1e..56cf0a3ff 100644 --- a/src/__tests__/ParseQuery-test.js +++ b/src/__tests__/ParseQuery-test.js @@ -264,6 +264,33 @@ describe('ParseQuery', () => { }); }); + it('can generate contains-all-starting-with queries', () => { + var q = new ParseQuery('Item'); + q.containsAllStartingWith('tags', ['ho', 'out']); + expect(q.toJSON()).toEqual({ + where: { + tags: { + $all: [ + {$regex: '^\\Qho\\E'}, + {$regex: '^\\Qout\\E'} + ] + } + } + }); + + q.containsAllStartingWith('tags', ['sal', 'ne']); + expect(q.toJSON()).toEqual({ + where: { + tags: { + $all: [ + {$regex: '^\\Qsal\\E'}, + {$regex: '^\\Qne\\E'} + ] + } + } + }); + }); + it('can generate exists queries', () => { var q = new ParseQuery('Item'); q.exists('name');