From 7c239a71d110009c8d1b4597af46b3c83bb5f41c Mon Sep 17 00:00:00 2001 From: Lee Yeh Date: Mon, 13 Mar 2017 13:08:34 +0800 Subject: [PATCH 01/47] fix(init): remove disableCurrentUser assignment (#457) --- src/init.js | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/init.js b/src/init.js index 2673a246f..b3b02b241 100644 --- a/src/init.js +++ b/src/init.js @@ -29,32 +29,24 @@ const masterKeyWarn = () => { */ AV.init = (...args) => { - switch (args.length) { - case 1: - const options = args[0]; - if (typeof options === 'object') { - if (process.env.CLIENT_PLATFORM && options.masterKey) { - masterKeyWarn(); - } - initialize(options.appId, options.appKey, options.masterKey, options.hookKey); - request.setServerUrlByRegion(options.region); - AV._config.disableCurrentUser = options.disableCurrentUser; - } else { - throw new Error('AV.init(): Parameter is not correct.'); - } - break; - // 兼容旧版本的初始化方法 - case 2: - case 3: - console.warn('Please use AV.init() to replace AV.initialize(), ' + - 'AV.init() need an Object param, like { appId: \'YOUR_APP_ID\', appKey: \'YOUR_APP_KEY\' } . ' + - 'Docs: https://leancloud.cn/docs/sdk_setup-js.html'); - if (process.env.CLIENT_PLATFORM && args.length === 3) { + if (args.length === 1) { + const options = args[0]; + if (typeof options === 'object') { + if (process.env.CLIENT_PLATFORM && options.masterKey) { masterKeyWarn(); } - initialize(...args); - request.setServerUrlByRegion('cn'); - break; + initialize(options.appId, options.appKey, options.masterKey, options.hookKey); + request.setServerUrlByRegion(options.region); + } else { + throw new Error('AV.init(): Parameter is not correct.'); + } + } else { + // 兼容旧版本的初始化方法 + if (process.env.CLIENT_PLATFORM && args[3]) { + masterKeyWarn(); + } + initialize(...args); + request.setServerUrlByRegion('cn'); } }; From 56dcb698b21213ec6c8d026de986516af16468b6 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Mon, 13 Mar 2017 14:51:10 +0800 Subject: [PATCH 02/47] fix(Query): polish #destroyAll AuthOptions support --- src/query.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query.js b/src/query.js index 312a630ea..b00290342 100644 --- a/src/query.js +++ b/src/query.js @@ -380,7 +380,7 @@ module.exports = function(AV) { destroyAll: function(options){ var self = this; return self.find(options).then(function(objects){ - return AV.Object.destroyAll(objects); + return AV.Object.destroyAll(objects, options); }); }, From 2125d3047e89b8f677fcf367b3861b3256f5813c Mon Sep 17 00:00:00 2001 From: leeyeh Date: Mon, 13 Mar 2017 15:33:30 +0800 Subject: [PATCH 03/47] fix(User): refreshSessionToken also refresh cache --- src/user.js | 2 +- test/user.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/user.js b/src/user.js index 50fd5cd1b..41896beb6 100644 --- a/src/user.js +++ b/src/user.js @@ -593,7 +593,7 @@ module.exports = function(AV) { return AVRequest(`users/${this.id}/refreshSessionToken`, null, null, 'PUT', null, options) .then(response => { this._finishFetch(response); - return this; + return this._handleSaveResult(true).then(() => this); }); }, diff --git a/test/user.js b/test/user.js index 662704f85..ca4e77f5e 100644 --- a/test/user.js +++ b/test/user.js @@ -240,6 +240,10 @@ describe("User", function() { return user.refreshSessionToken().then(user => { user.getSessionToken().should.be.a.String(); user.getSessionToken().should.not.be.eql(prevSessionToken); + // cache refreshed + delete AV.User._currentUser; + AV.User._currentUserMatchesDisk = false; + user.getSessionToken().should.be.eql(AV.User.current().getSessionToken()); }) }); }) From 5dd99fc41c2d82ef6708aa829032da06a7cd3963 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Mon, 13 Mar 2017 16:47:19 +0800 Subject: [PATCH 04/47] chore(release): v2.1.3 --- bower.json | 2 +- changelog.md | 5 +++++ package.json | 2 +- src/version.js | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 9a625bd7e..e07854cc7 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.1.2", + "version": "2.1.3", "homepage": "https://github.com/leancloud/javascript-sdk", "authors": [ "LeanCloud " diff --git a/changelog.md b/changelog.md index 0d10f0dbf..d12c33804 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,8 @@ +## 2.1.3 (2017-3-13) +* 修复了调用 `User#refreshSessionToken` 刷新用户的 sessionToken 后本地存储中的用户没有更新的问题 +* 修复了初始化可能会造成 disableCurrentUser 配置失效的问题 +* 修复了 `Query#destroyAll` 方法 `options` 参数无效的问题 + ## 2.1.2 (2017-02-17) ### Bug Fixes * 修复了文件上传时,如果 `fileName` 没有指定扩展名会导致上传文件 `mime-type` 不符合预期的问题 diff --git a/package.json b/package.json index bf332f2d8..bc2012d09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.1.2", + "version": "2.1.3", "main": "./dist/node/index.js", "description": "LeanCloud JavaScript SDK.", "repository": { diff --git a/src/version.js b/src/version.js index ab7812ade..69bc5b508 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -module.exports = '2.1.2'; +module.exports = '2.1.3'; From d09e16131651028abb388460101b764bf15cd6eb Mon Sep 17 00:00:00 2001 From: Lee Yeh Date: Tue, 21 Mar 2017 10:32:33 +0800 Subject: [PATCH 05/47] fix(File): parse file data after fileToken (#460) --- src/file.js | 140 +++++++++++++++++++++++++++------------------------- 1 file changed, 73 insertions(+), 67 deletions(-) diff --git a/src/file.js b/src/file.js index 6dd03950c..26a5c2e6b 100644 --- a/src/file.js +++ b/src/file.js @@ -7,6 +7,7 @@ const AVRequest = require('./request').request; const Promise = require('./promise'); const { tap } = require('./utils'); const debug = require('debug')('leancloud:file'); +const parseBase64 = require('./utils/parse-base64'); module.exports = function(AV) { @@ -101,7 +102,16 @@ module.exports = function(AV) { base64: '', }; + if (_.isString(data)) { + throw new TypeError("Creating an AV.File from a String is not yet supported."); + } + if (_.isArray(data)) { + this.attributes.metaData.size = data.length; + data = { base64: encodeBase64(data) }; + } + this._extName = ''; + this._data = data; let owner; if (data && data.owner) { @@ -117,46 +127,10 @@ module.exports = function(AV) { } } } - - this.attributes.metaData = { - owner: (owner ? owner.id : 'unknown') - }; + + this.attributes.metaData.owner = owner ? owner.id : 'unknown'; this.set('mime_type', mimeType); - - if (_.isArray(data)) { - this.attributes.metaData.size = data.length; - data = { base64: encodeBase64(data) }; - } - if (data && data.base64) { - var parseBase64 = require('./utils/parse-base64'); - var dataBase64 = parseBase64(data.base64, mimeType); - this._source = Promise.resolve({ data: dataBase64, type: mimeType }); - } else if (data && data.blob) { - if (!data.blob.type && mimeType) { - data.blob.type = mimeType; - } - if (!data.blob.name) { - data.blob.name = name; - } - if (process.env.CLIENT_PLATFORM === 'ReactNative' || process.env.CLIENT_PLATFORM === 'Weapp') { - this._extName = extname(data.blob.uri); - } - this._source = Promise.resolve({ data: data.blob, type: mimeType }); - } else if (typeof File !== "undefined" && data instanceof File) { - if (data.size) { - this.attributes.metaData.size = data.size; - } - if (data.name) { - this._extName = extname(data.name); - } - this._source = Promise.resolve({ data, type: mimeType }); - } else if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) { - this.attributes.metaData.size = data.length; - this._source = Promise.resolve({ data, type: mimeType }); - } else if (_.isString(data)) { - throw new Error("Creating a AV.File from a String is not yet supported."); - } }; /** @@ -450,37 +424,68 @@ module.exports = function(AV) { throw new Error('File already saved. If you want to manipulate a file, use AV.Query to get it.'); } if (!this._previousSave) { - if (this._source) { - this._previousSave = this._source.then(({ data, type }) => - this._fileToken(type) - .then(uploadInfo => { - if (uploadInfo.mime_type) { - this.set('mime_type', uploadInfo.mime_type); + if (this._data) { + let mimeType = this.get('mime_type'); + this._previousSave = this._fileToken(mimeType).then(uploadInfo => { + if (uploadInfo.mime_type) { + mimeType = uploadInfo.mime_type; + this.set('mime_type', mimeType); + } + this._token = uploadInfo.token; + return Promise.resolve().then(() => { + const data = this._data; + if (data && data.base64) { + return parseBase64(data.base64, mimeType); + } + if (data && data.blob) { + if (!data.blob.type && mimeType) { + data.blob.type = mimeType; + } + if (!data.blob.name) { + data.blob.name = this.get('name'); + } + if (process.env.CLIENT_PLATFORM === 'ReactNative' || process.env.CLIENT_PLATFORM === 'Weapp') { + this._extName = extname(data.blob.uri); } - this._token = uploadInfo.token; - - let uploadPromise; - switch (uploadInfo.provider) { - case 's3': - uploadPromise = s3(uploadInfo, data, this, options); - break; - case 'qcloud': - uploadPromise = cos(uploadInfo, data, this, options); - break; - case 'qiniu': - default: - uploadPromise = qiniu(uploadInfo, data, this, options); - break; + return data.blob; + } + if (typeof File !== "undefined" && data instanceof File) { + if (data.size) { + this.attributes.metaData.size = data.size; } - return uploadPromise.then( - tap(() => this._callback(true)), - (error) => { - this._callback(false); - throw error; - } - ); - }) - ); + if (data.name) { + this._extName = extname(data.name); + } + return data; + } + if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) { + this.attributes.metaData.size = data.length; + return data; + } + throw new TypeError('malformed file data'); + }).then(data => { + let uploadPromise; + switch (uploadInfo.provider) { + case 's3': + uploadPromise = s3(uploadInfo, data, this, options); + break; + case 'qcloud': + uploadPromise = cos(uploadInfo, data, this, options); + break; + case 'qiniu': + default: + uploadPromise = qiniu(uploadInfo, data, this, options); + break; + } + return uploadPromise.then( + tap(() => this._callback(true)), + (error) => { + this._callback(false); + throw error; + } + ); + }); + }); } else if (this.attributes.url && this.attributes.metaData.__source === 'external') { // external link file. const data = { @@ -510,6 +515,7 @@ module.exports = function(AV) { result: success, }).catch(debug); delete this._token; + delete this._data; }, /** From da383282016c2da5a1f3f295cd4e76a0594646eb Mon Sep 17 00:00:00 2001 From: Lee Yeh Date: Tue, 21 Mar 2017 11:18:56 +0800 Subject: [PATCH 06/47] fix(Role): use noDefaultACL flag while creating Role interally (#461) --- src/av.js | 4 ++-- src/object.js | 6 +++--- src/role.js | 13 ++++++++----- test/role.js | 10 ++++++++++ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/av.js b/src/av.js index ab3e7c06f..43719ef79 100644 --- a/src/av.js +++ b/src/av.js @@ -291,7 +291,7 @@ AV._decode = function(value, key) { var className; if (value.__type === "Pointer") { className = value.className; - var pointer = AV.Object._create(className); + var pointer = AV.Object._create(className, undefined, undefined, /* noDefaultACL*/ true); if(Object.keys(value).length > 3) { const v = _.clone(value); delete v.__type; @@ -308,7 +308,7 @@ AV._decode = function(value, key) { const v = _.clone(value); delete v.__type; delete v.className; - var object = AV.Object._create(className); + var object = AV.Object._create(className, undefined, undefined, /* noDefaultACL*/ true); object._finishFetch(v, true); return object; } diff --git a/src/object.js b/src/object.js index 39ac6e89d..a0a63491b 100644 --- a/src/object.js +++ b/src/object.js @@ -1239,7 +1239,7 @@ module.exports = function(AV) { * @return {AV.Object} A new subclass instance of AV.Object. */ AV.Object.createWithoutData = function(className, id, hasData){ - var result = new AV.Object(className); + var result = AV.Object._create(className, undefined, undefined, /* noDefaultACL*/ true); result.id = id; result._hasData = hasData; return result; @@ -1293,9 +1293,9 @@ module.exports = function(AV) { * Creates an instance of a subclass of AV.Object for the given classname. * @private */ - AV.Object._create = function(className, attributes, options) { + AV.Object._create = function(className, attributes, options, noDefaultACL) { var ObjectClass = AV.Object._getSubclass(className); - return new ObjectClass(attributes, options); + return new ObjectClass(attributes, options, noDefaultACL); }; // Set up a map of className to class so that we can create new instances of diff --git a/src/role.js b/src/role.js index b0131a6dc..1a994aa6a 100644 --- a/src/role.js +++ b/src/role.js @@ -21,7 +21,7 @@ module.exports = function(AV) { * @param {AV.ACL} [acl] The ACL for this role. if absent, the default ACL * `{'*': { read: true }}` will be used. */ - constructor: function(name, acl) { + constructor: function(name, acl, noDefaultACL) { if (_.isString(name)) { AV.Object.prototype.constructor.call(this, null, null); this.setName(name); @@ -29,10 +29,13 @@ module.exports = function(AV) { AV.Object.prototype.constructor.call(this, name, acl); } if (acl === undefined) { - var defaultAcl = new AV.ACL(); - defaultAcl.setPublicReadAccess(true); - if(!this.getACL()) { - this.setACL(defaultAcl); + if (!noDefaultACL) { + if(!this.getACL()) { + console.warn('DEPRECATED: To create a Role without ACL(a default ACL will be used) is deprecated. Please specify an ACL.'); + var defaultAcl = new AV.ACL(); + defaultAcl.setPublicReadAccess(true); + this.setACL(defaultAcl); + } } } else if (!(acl instanceof AV.ACL)) { throw new TypeError('acl must be an instance of AV.ACL'); diff --git a/test/role.js b/test/role.js index f017badab..58e7b6116 100644 --- a/test/role.js +++ b/test/role.js @@ -17,6 +17,16 @@ describe("Role", function() { } }); }); + it('no default ACL', () => { + expect(AV.Object.createWithoutData('_Role').getACL()).to.eql(undefined); + expect(AV._decode({ + __type: 'Pointer', + className: '_Role', + name: 'Admin', + objectId: '577e50c3165abd005549f210', + }).getACL()).to.eql(undefined); + expect((new AV.Object('_Role')).getACL()).not.to.eql(undefined); + }); it("type check", function() { expect(function() { new AV.Role('foo', {}); From 1a79b7e84332d7fb51b47be0389a3cade8fe64a6 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Thu, 23 Mar 2017 19:16:39 +0800 Subject: [PATCH 07/47] chore(build): upgrade config for webpack 2.3 --- webpack/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack/common.js b/webpack/common.js index bbea588e2..f9ab9e73c 100644 --- a/webpack/common.js +++ b/webpack/common.js @@ -8,7 +8,7 @@ module.exports = function() { filename: 'av.js', libraryTarget: "umd2", library: "AV", - path: './dist' + path: path.resolve(__dirname, '../dist') }, resolve: {}, devtool: 'source-map', From 502f23d798592fcf6691e41911f111f4103a09d5 Mon Sep 17 00:00:00 2001 From: Lee Yeh Date: Fri, 24 Mar 2017 14:03:41 +0800 Subject: [PATCH 08/47] fix(Insight): add missing param in be73b2856e2992d5e76b0a3315f30e3d555cbfdc (#464) --- src/request.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/request.js b/src/request.js index 935452d71..921019d48 100644 --- a/src/request.js +++ b/src/request.js @@ -59,7 +59,7 @@ const ajax = (method, resourceUrl, data, headers = {}, onprogress) => { }); }; -const setAppId = (headers, signKey) => { +const setAppKey = (headers, signKey) => { if (signKey) { headers['X-LC-Sign'] = sign(AV.applicationKey); } else { @@ -87,10 +87,10 @@ const setHeaders = (authOptions = {}, signKey) => { } } else { console.warn('masterKey is not set, fall back to use appKey'); - setAppId(headers, signKey); + setAppKey(headers, signKey); } } else { - setAppId(headers, signKey); + setAppKey(headers, signKey); } if (AV.hookKey) { headers['X-LC-Hook-Key'] = AV.hookKey; @@ -278,7 +278,7 @@ const AVRequest = (route, className, objectId, method, dataObject = {}, authOpti } return getServerURLPromise.then(() => { const apiURL = createApiUrl(route, className, objectId, method, dataObject); - return setHeaders(authOptions).then( + return setHeaders(authOptions, route !== 'bigquery').then( headers => ajax(method, apiURL, dataObject, headers) .then( null, From fcc42aa7263b63fde932c22e8c247a026f803f03 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Mon, 27 Mar 2017 16:39:26 +0800 Subject: [PATCH 09/47] chore(release): v2.1.4 --- bower.json | 2 +- changelog.md | 8 +++++++- package.json | 2 +- src/version.js | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index e07854cc7..7cd25c42e 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.1.3", + "version": "2.1.4", "homepage": "https://github.com/leancloud/javascript-sdk", "authors": [ "LeanCloud " diff --git a/changelog.md b/changelog.md index d12c33804..8de15d97b 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,10 @@ -## 2.1.3 (2017-3-13) +## 2.1.4 (2017-03-27) +### Bug Fixes +* 如果在创建 `Role` 时不指定 `acl` 参数,SDK 会自动为其设置一个「默认 acl」,这导致了通过 Query 得到或使用 `Object.createWithoutData` 方法得到 `Role` 也会被意外的设置 acl。这个版本修复了这个问题。 +* 修复了在 React Native for Android 中使用 blob 方式上传文件失败的问题 + +## 2.1.3 (2017-03-13) +### Bug Fixes * 修复了调用 `User#refreshSessionToken` 刷新用户的 sessionToken 后本地存储中的用户没有更新的问题 * 修复了初始化可能会造成 disableCurrentUser 配置失效的问题 * 修复了 `Query#destroyAll` 方法 `options` 参数无效的问题 diff --git a/package.json b/package.json index bc2012d09..f1d2facb8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.1.3", + "version": "2.1.4", "main": "./dist/node/index.js", "description": "LeanCloud JavaScript SDK.", "repository": { diff --git a/src/version.js b/src/version.js index 69bc5b508..ec40e7562 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -module.exports = '2.1.3'; +module.exports = '2.1.4'; From 160baed30c574aad12decfdd648714f218ce3b82 Mon Sep 17 00:00:00 2001 From: Ang Long Date: Sat, 1 Apr 2017 17:29:24 +0800 Subject: [PATCH 10/47] doc: update AV.Push.send API doc (#467) --- src/push.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/push.js b/src/push.js index deee12e26..5d1f5ee7e 100644 --- a/src/push.js +++ b/src/push.js @@ -20,7 +20,8 @@ module.exports = function(AV) { * a set of installations to push to. * @param {String} [data.cql] A CQL statement over AV.Installation that is used to match * a set of installations to push to. - * @param {Date} data.data The data to send as part of the push + * @param {Object} data.data The data to send as part of the push. + More details: https://url.leanapp.cn/pushData * @param {AuthOptions} [options] * @return {Promise} */ From 6590963264a973fdc1a83225738bc640af530492 Mon Sep 17 00:00:00 2001 From: Lee Yeh Date: Wed, 19 Apr 2017 15:36:40 +0800 Subject: [PATCH 11/47] fix(localStorage): use memory storage as fallback in safari private mode (#468) --- src/utils/localstorage-browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/localstorage-browser.js b/src/utils/localstorage-browser.js index deb8b5718..d6ece9754 100644 --- a/src/utils/localstorage-browser.js +++ b/src/utils/localstorage-browser.js @@ -36,7 +36,7 @@ try { // in browser, `localStorage.async = false` will excute `localStorage.setItem('async', false)` _(apiNames).each(function(apiName) { Storage[apiName] = function() { - return global.localStorage[apiName].apply(global.localStorage, arguments); + return localStorage[apiName].apply(localStorage, arguments); }; }); Storage.async = false; From 81d9305b7451c4601d0edff919926cb19d147845 Mon Sep 17 00:00:00 2001 From: Lee Yeh Date: Mon, 24 Apr 2017 14:37:16 +0800 Subject: [PATCH 12/47] feat(ACL): add includeACL flag for Query and fetching (#470) --- src/file.js | 12 +++++------ src/object.js | 14 ++++--------- src/query.js | 14 +++++++++++++ src/utils/index.js | 15 +++++++++++++ test/acl.js | 52 ++++++++++++++++++++++++++++++---------------- test/query.js | 10 +++++++++ 6 files changed, 83 insertions(+), 34 deletions(-) diff --git a/src/file.js b/src/file.js index 26a5c2e6b..1b8495757 100644 --- a/src/file.js +++ b/src/file.js @@ -5,7 +5,7 @@ const s3 = require('./uploader/s3'); const AVError = require('./error'); const AVRequest = require('./request').request; const Promise = require('./promise'); -const { tap } = require('./utils'); +const { tap, transformFetchOptions } = require('./utils'); const debug = require('debug')('leancloud:file'); const parseBase64 = require('./utils/parse-base64'); @@ -521,14 +521,14 @@ module.exports = function(AV) { /** * fetch the file from server. If the server's representation of the * model differs from its current attributes, they will be overriden, - * @param {AuthOptions} options AuthOptions plus 'keys' and 'include' option. + * @param {Object} fetchOptions Optional options to set 'keys', + * 'include' and 'includeACL' option. + * @param {AuthOptions} options * @return {Promise} A promise that is fulfilled when the fetch * completes. */ - fetch: function(options) { - var options = null; - - var request = AVRequest('files', null, this.id, 'GET', options); + fetch: function(fetchOptions, options) { + var request = AVRequest('files', null, this.id, 'GET', transformFetchOptions(fetchOptions), options); return request.then(this._finishFetch.bind(this)); }, _finishFetch: function(response) { diff --git a/src/object.js b/src/object.js index a0a63491b..12122ab5f 100644 --- a/src/object.js +++ b/src/object.js @@ -808,23 +808,17 @@ module.exports = function(AV) { * Fetch the model from the server. If the server's representation of the * model differs from its current attributes, they will be overriden, * triggering a "change" event. - * @param {Object} fetchOptions Optional options to set 'keys' and - * 'include' option. + * @param {Object} fetchOptions Optional options to set 'keys', + * 'include' and 'includeACL' option. * @param {AuthOptions} options * @return {Promise} A promise that is fulfilled when the fetch * completes. */ - fetch: function(fetchOptions = {}, options) { - if (_.isArray(fetchOptions.keys)) { - fetchOptions.keys = fetchOptions.keys.join(','); - } - if (_.isArray(fetchOptions.include)) { - fetchOptions.include = fetchOptions.include.join(','); - } + fetch: function(fetchOptions, options) { var self = this; var request = AVRequest('classes', this.className, this.id, 'GET', - fetchOptions, options); + utils.transformFetchOptions(fetchOptions), options); return request.then(function(response) { self._finishFetch(self.parse(response), true); return self; diff --git a/src/query.js b/src/query.js index b00290342..1b96d7c54 100644 --- a/src/query.js +++ b/src/query.js @@ -197,6 +197,7 @@ module.exports = function(AV) { if (queryJSON.keys) fetchOptions.keys = queryJSON.keys; if (queryJSON.include) fetchOptions.include = queryJSON.include; + if (queryJSON.includeACL) fetchOptions.includeACL = queryJSON.includeACL; return obj.fetch(fetchOptions, options); }, @@ -216,6 +217,9 @@ module.exports = function(AV) { if (this._select.length > 0) { params.keys = this._select.join(","); } + if (this._includeACL !== undefined) { + params.returnACL = this._includeACL; + } if (this._limit >= 0) { params.limit = this._limit; } @@ -929,6 +933,16 @@ module.exports = function(AV) { return this; }, + /** + * Include the ACL. + * @param {Boolean} [value=true] Whether to include the ACL + * @return {AV.Query} Returns the query, so you can chain this call. + */ + includeACL: function(value = true) { + this._includeACL = value; + return this; + }, + /** * Restrict the fields of the returned AV.Objects to include only the * provided keys. If this is called multiple times, then all of the keys diff --git a/src/utils/index.js b/src/utils/index.js index bfb3cb1d6..21bff2621 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -13,6 +13,20 @@ const ensureArray = target => { return [target]; }; +const transformFetchOptions = ({ keys, include, includeACL } = {}) => { + const fetchOptions = {}; + if (_.isArray(keys)) { + fetchOptions.keys = keys.join(','); + } + if (_.isArray(include)) { + fetchOptions.include = include.join(','); + } + if (includeACL) { + fetchOptions.returnACL = includeACL; + } + return fetchOptions; +}; + const getSessionToken = (authOptions) => { if (authOptions.sessionToken) { return authOptions.sessionToken; @@ -29,6 +43,7 @@ const tap = interceptor => value => ((interceptor(value), value)); module.exports = { isNullOrUndefined, ensureArray, + transformFetchOptions, getSessionToken, tap, }; diff --git a/test/acl.js b/test/acl.js index 0097ebf75..f7e185b33 100644 --- a/test/acl.js +++ b/test/acl.js @@ -2,26 +2,42 @@ var GameScore = AV.Object.extend("GameScore"); describe("ObjectACL", function () { - describe("*", function () { - it("set * acl", function () { - var gameScore = new GameScore(); - gameScore.set("score", 2); - gameScore.set("playerName", "sdf"); - gameScore.set("cheatMode", false); + it("set and fetch acl", function () { + var gameScore = new GameScore(); + gameScore.set("score", 2); + gameScore.set("playerName", "sdf"); + gameScore.set("cheatMode", false); - var postACL = new AV.ACL(); - postACL.setPublicReadAccess(true); - postACL.setPublicWriteAccess(true); + var postACL = new AV.ACL(); + postACL.setPublicReadAccess(true); + postACL.setPublicWriteAccess(true); - postACL.setReadAccess("546", true); - postACL.setReadAccess("56238", true); - postACL.setWriteAccess("5a061", true); - postACL.setRoleWriteAccess("r6", true); - gameScore.setACL(postACL); - return gameScore.save().then(result => { - result.id.should.be.ok(); - return gameScore.destroy(); + postACL.setReadAccess("read-only", true); + postACL.setWriteAccess("write-only", true); + postACL.setRoleWriteAccess("write-only-role", true); + gameScore.setACL(postACL); + return gameScore.save().then(result => { + result.id.should.be.ok(); + return AV.Object.createWithoutData('GameScore', result.id).fetch({ + includeACL: true, }); - }); + }).then(fetchedGameScore => { + const acl = fetchedGameScore.getACL(); + acl.should.be.instanceOf(AV.ACL); + acl.getPublicReadAccess().should.eql(true); + acl.getPublicWriteAccess().should.eql(true); + acl.getReadAccess('read-only').should.eql(true); + acl.getWriteAccess('read-only').should.eql(false); + acl.getReadAccess('write-only').should.eql(false); + acl.getWriteAccess('write-only').should.eql(true); + acl.getRoleReadAccess('write-only-role').should.eql(false); + acl.getRoleWriteAccess('write-only-role').should.eql(true); + }).then( + () => gameScore.destroy(), + error => { + gameScore.destroy(); + throw error; + } + ); }); }); diff --git a/test/query.js b/test/query.js index 864791655..5f98e6f19 100644 --- a/test/query.js +++ b/test/query.js @@ -208,6 +208,16 @@ describe('Queries', function () { }); }); + it('includeACL', function () { + return new AV.Query(GameScore) + .includeACL() + .equalTo('objectId', this.gameScore.id) + .find() + .then(([gameScore]) => { + gameScore.getACL().should.be.instanceOf(AV.ACL); + }); + }); + it('containsAll with an large array should not cause URI too long', () => { return new AV.Query(GameScore) .containsAll('arr', new Array(200).fill('contains-all-test')) From 8aa34e860cdbe6981eec9a4f206604e56e647d89 Mon Sep 17 00:00:00 2001 From: Lee Yeh Date: Mon, 24 Apr 2017 14:52:33 +0800 Subject: [PATCH 13/47] feat: captcha (#469) --- .gitignore | 1 - README.md | 2 -- demo/index.html | 5 +++- demo/test-es6.js | 66 -------------------------------------------- demo/test.js | 40 +++++++++++++++++++++++++++ src/cloudfunction.js | 55 +++++++++++++++++++++++++++++++----- src/user.js | 45 ++++++++++++++++++++++-------- storage.d.ts | 18 ++++++++---- 8 files changed, 137 insertions(+), 95 deletions(-) delete mode 100644 demo/test-es6.js create mode 100644 demo/test.js diff --git a/.gitignore b/.gitignore index 1964a1f92..469b916fe 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ coverage *.swp dist/js-sdk-api-docs npm-debug.log -demo/test-es5.js .nyc_output dist docs diff --git a/README.md b/README.md index 24d9b53ac..3b6cca9bb 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,6 @@ bower install leancloud-storage --save * `fork` 这个项目 * `npm install` 安装相关依赖 * 开发和调试 - * 浏览器环境执行 `gulp dev`,会自动启动 `demo` 目录,可在 `test-es6.js` 中修改和测试,`test-es5.js` 为自动生成的代码 - * Nodejs 环境同样在 `demo` 目录中,通过执行 `node test-es6.js` 开发与调试。推荐安装 `node inspector` 来调试,安装后执行 `node-debug test-es6.js`。每次修改代码后,如果开发代码引用的是 dist 目录中的代码,需要执行 `gulp release` * 确保测试全部通过 `npm run test`,浏览器环境打开 `test/test.html` * 提交并发起 `Pull Request` diff --git a/demo/index.html b/demo/index.html index 8d3f9fb4a..0a3cf7def 100644 --- a/demo/index.html +++ b/demo/index.html @@ -10,8 +10,11 @@

LeanCloud

为开发加速

欢迎调试 JavaScript SDK,请打开浏览器控制台

+ + +
- + diff --git a/demo/test-es6.js b/demo/test-es6.js deleted file mode 100644 index c26f74518..000000000 --- a/demo/test-es6.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - 请参考 README.md 中的开发方式, - 执行 gulp dev 该文件会被编译为 test-es5.js 并自动运行此文件 -*/ - -/* eslint no-console: ["error", { allow: ["log"] }] */ -/* eslint no-undef: ["error", { "AV": true }] */ - -'use strict'; - -let av; - -// 检测是否在 Nodejs 环境下运行 -if (typeof(process) !== 'undefined' && process.versions && process.versions.node) { - av = require('../dist/node/av'); -} else { - av = window.AV; -} - -// 初始化 -const appId = 'a5CDnmOX94uSth8foK9mjHfq-gzGzoHsz'; -const appKey = 'Ue3h6la9zH0IxkUJmyhLjk9h'; -const region = 'cn'; - -// const appId = 'QvNM6AG2khJtBQo6WRMWqfLV-gzGzoHsz'; -// const appKey = 'be2YmUduiuEnCB2VR9bLRnnV'; -// const region = 'us'; - -av.init({ appId, appKey, region }); - -// 基本存储 -const TestClass = av.Object.extend('TestClass'); -const testObj = new TestClass(); -testObj.set({ - name: 'hjiang', - phone: '123123123', -}); - -testObj.save().then(() => { - console.log('success'); -}).catch((err) => { - console.log('failed'); - console.log(err); -}); - -// 存储文件 -const base64 = 'd29ya2luZyBhdCBhdm9zY2xvdWQgaXMgZ3JlYXQh'; -const file = new av.File('myfile.txt', { base64 }); -file.metaData('format', 'txt file'); -file.save().then(() => { - console.log(file.get('url')); -}).catch((error) => { - console.log(error); -}); - -// 查找文件 -const query = new av.Query(TestClass); -query.equalTo('name', 'hjiang'); -query.find().then((list) => { - console.log(list); -}); - -// 用户登录 -AV.User.login('ttt', '123456') -.then((res) => console.log(res)) -.catch(err => console.log(err)); diff --git a/demo/test.js b/demo/test.js new file mode 100644 index 000000000..074ebf930 --- /dev/null +++ b/demo/test.js @@ -0,0 +1,40 @@ +var av = void 0; + +// 检测是否在 Nodejs 环境下运行 +if (typeof process !== 'undefined' && process.versions && process.versions.node) { + av = require('../dist/node/av'); +} else { + av = window.AV; +} + +// 初始化 +var appId = 'a5CDnmOX94uSth8foK9mjHfq-gzGzoHsz'; +var appKey = 'Ue3h6la9zH0IxkUJmyhLjk9h'; +var region = 'cn'; + +// const appId = 'QvNM6AG2khJtBQo6WRMWqfLV-gzGzoHsz'; +// const appKey = 'be2YmUduiuEnCB2VR9bLRnnV'; +// const region = 'us'; + +av.init({ appId: appId, appKey: appKey, region: region }); + +let captchaToken; +const captchaImage = document.getElementById('captcha'); +const captchaInput = document.getElementById('code'); + +function refreshCaptcha(){ + AV.Cloud.requestCaptcha({ + size: 6, + ttl: 30, + }).then(function(data) { + captchaToken = data.captchaToken; + captchaImage.src = data.url; + }).catch(console.error); +} +refreshCaptcha(); + +function verify() { + AV.Cloud.verifyCaptcha(captchaInput.value, captchaToken).then(function(validateCode) { + console.log('validateCode: ' + validateCode); + }, console.error); +} diff --git a/src/cloudfunction.js b/src/cloudfunction.js index 9700f7926..8ed198bb6 100644 --- a/src/cloudfunction.js +++ b/src/cloudfunction.js @@ -64,20 +64,29 @@ module.exports = function(AV) { /** * Makes a call to request a sms code for operation verification. - * @param {Object} data The mobile phone number string or a JSON - * object that contains mobilePhoneNumber,template,op,ttl,name etc. - * @return {Promise} A promise that will be resolved with the result - * of the function. + * @param {String|Object} data The mobile phone number string or a JSON + * object that contains mobilePhoneNumber,template,sign,op,ttl,name etc. + * @param {String} data.mobilePhoneNumber + * @param {String} [data.template] sms template name + * @param {String} [data.sign] sms signature name + * @param {AuthOptions} [options] AuthOptions plus: + * @param {String} [options.validateToken] a validate token returned by {@link AV.Cloud.verifyCaptcha} + * @return {Promise} A promise that will be resolved if the request succeed */ - requestSmsCode: function(data){ + requestSmsCode: function(data, options = {}) { if(_.isString(data)) { data = { mobilePhoneNumber: data }; } if(!data.mobilePhoneNumber) { throw new Error('Missing mobilePhoneNumber.'); } + if (options.validateToken) { + data = _.extend({}, data, { + validate_token: options.validateToken, + }); + } var request = AVRequest("requestSmsCode", null, null, 'POST', - data); + data, options); return request; }, @@ -99,6 +108,38 @@ module.exports = function(AV) { var request = AVRequest("verifySmsCode", code, null, 'POST', params); return request; - } + }, + + /** + * request a captcha + * @param {Object} [options] + * @param {Number} [options.size=4] length of the captcha, ranged 3-6 + * @param {Number} [options.width] width(px) of the captcha, ranged 60-200 + * @param {Number} [options.height] height(px) of the captcha, ranged 30-100 + * @param {Number} [options.ttl=60] time to live(s), ranged 10-180 + * @return {Promise} { captchaToken, url } + */ + requestCaptcha(options) { + return AVRequest('requestCaptcha', null, null, 'GET', options).then(({ + captcha_url: url, + captcha_token: captchaToken, + }) => ({ + captchaToken, + url, + })); + }, + + /** + * verify captcha code + * @param {String} code the code from user input + * @param {String} captchaToken captchaToken returned by {@link AV.Cloud.requestCaptcha} + * @return {Promise.} validateToken if the code is valid + */ + verifyCaptcha(code, captchaToken) { + return AVRequest('verifyCaptcha', null, null, 'POST', { + captcha_code: code, + captcha_token: captchaToken, + }).then(({ validate_token: validateToken }) => validateToken); + }, }); }; diff --git a/src/user.js b/src/user.js index 41896beb6..088c118d3 100644 --- a/src/user.js +++ b/src/user.js @@ -899,14 +899,21 @@ module.exports = function(AV) { * number associated with the user account. This sms code allows the user to * verify their mobile phone number by calling AV.User.verifyMobilePhone * - * @param {String} mobilePhone The mobile phone number associated with the + * @param {String} mobilePhoneNumber The mobile phone number associated with the * user that doesn't verify their mobile phone number. + * @param {AuthOptions} [options] AuthOptions plus: + * @param {String} [options.validateToken] a validate token returned by {@link AV.Cloud.verifyCaptcha} * @return {Promise} */ - requestMobilePhoneVerify: function(mobilePhone){ - var json = { mobilePhoneNumber: mobilePhone }; + requestMobilePhoneVerify: function(mobilePhoneNumber, options = {}){ + const data = { + mobilePhoneNumber, + } + if (options.validataToken) { + data.validate_token = options.validataToken + } var request = AVRequest("requestMobilePhoneVerify", null, null, "POST", - json); + data, options); return request; }, @@ -916,14 +923,21 @@ module.exports = function(AV) { * number associated with the user account. This sms code allows the user to * reset their account's password by calling AV.User.resetPasswordBySmsCode * - * @param {String} mobilePhone The mobile phone number associated with the + * @param {String} mobilePhoneNumber The mobile phone number associated with the * user that doesn't verify their mobile phone number. + * @param {AuthOptions} [options] AuthOptions plus: + * @param {String} [options.validateToken] a validate token returned by {@link AV.Cloud.verifyCaptcha} * @return {Promise} */ - requestPasswordResetBySmsCode: function(mobilePhone){ - var json = { mobilePhoneNumber: mobilePhone }; + requestPasswordResetBySmsCode: function(mobilePhoneNumber, options = {}){ + const data = { + mobilePhoneNumber, + } + if (options.validataToken) { + data.validate_token = options.validataToken + } var request = AVRequest("requestPasswordResetBySmsCode", null, null, "POST", - json); + data, options); return request; }, @@ -960,14 +974,21 @@ module.exports = function(AV) { * number associated with the user account. This sms code allows the user to * login by AV.User.logInWithMobilePhoneSmsCode function. * - * @param {String} mobilePhone The mobile phone number associated with the + * @param {String} mobilePhoneNumber The mobile phone number associated with the * user that want to login by AV.User.logInWithMobilePhoneSmsCode + * @param {AuthOptions} [options] AuthOptions plus: + * @param {String} [options.validateToken] a validate token returned by {@link AV.Cloud.verifyCaptcha} * @return {Promise} */ - requestLoginSmsCode: function(mobilePhone){ - var json = { mobilePhoneNumber: mobilePhone }; + requestLoginSmsCode: function(mobilePhoneNumber, options = {}){ + const data = { + mobilePhoneNumber, + } + if (options.validataToken) { + data.validate_token = options.validataToken + } var request = AVRequest("requestLoginSmsCode", null, null, "POST", - json); + data, options); return request; }, diff --git a/storage.d.ts b/storage.d.ts index 9c3291552..2dd4b001f 100644 --- a/storage.d.ts +++ b/storage.d.ts @@ -17,6 +17,10 @@ declare namespace AV { user?: User; } + interface SMSAuthOptions extends AuthOptions { + validateToken?: string; + } + export interface WaitOption { /** * Set to true to wait for the server to confirm success @@ -508,10 +512,10 @@ declare namespace AV { static signUpOrlogInWithAuthData(data: any, platform: string, options?: AuthOptions): Promise; static signUpOrlogInWithMobilePhone(mobilePhoneNumber: string, smsCode: string, attributes?: any, options?: AuthOptions): Promise; static requestEmailVerify(email: string, options?: AuthOptions): Promise; - static requestLoginSmsCode(mobilePhone: string, options?: AuthOptions): Promise; - static requestMobilePhoneVerify(mobilePhone: string, options?: AuthOptions): Promise; + static requestLoginSmsCode(mobilePhoneNumber: string, options?: SMSAuthOptions): Promise; + static requestMobilePhoneVerify(mobilePhoneNumber: string, options?: SMSAuthOptions): Promise; static requestPasswordReset(email: string, options?: AuthOptions): Promise; - static requestPasswordResetBySmsCode(mobilePhone: string, options?: AuthOptions): Promise; + static requestPasswordResetBySmsCode(mobilePhoneNumber: string, options?: SMSAuthOptions): Promise; static resetPasswordBySmsCode(code: string, password: string, options?: AuthOptions): Promise; static verifyMobilePhone(code: string, options?: AuthOptions): Promise; signUp(attrs?: any, options?: AuthOptions): Promise; @@ -673,9 +677,11 @@ declare namespace AV { } export namespace Cloud { - function run(name: string, data?: any, options?: AuthOptions): Promise; - function requestSmsCode(data: any, options?: AuthOptions): Promise; - function verifySmsCode(code: string, phone: string, options?: AuthOptions): Promise; + function run(name: string, data?: any, options?: AuthOptions): Promise; + function requestSmsCode(data: string|{ mobilePhoneNumber: string, template?: string, sign?: string }, options?: SMSAuthOptions): Promise; + function verifySmsCode(code: string, phone: string): Promise; + function requestCaptcha(options?: { size?: number, width?: number, height?: number, ttl?: number}): Promise<{ captchaToken: string, dataURI: string }>; + function verifyCaptcha(code: string, captchaToken: string): Promise; } /** From fa4de5876a202a555b7aed1f9da93557b00ca990 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Tue, 25 Apr 2017 14:46:01 +0800 Subject: [PATCH 14/47] chore(release): v2.2.0 --- bower.json | 2 +- changelog.md | 14 +++++++++++++- package.json | 2 +- src/utils/index.js | 8 ++++---- src/version.js | 2 +- storage.d.ts | 17 +++++++++++------ 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/bower.json b/bower.json index 7cd25c42e..644295864 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.1.4", + "version": "2.2.0", "homepage": "https://github.com/leancloud/javascript-sdk", "authors": [ "LeanCloud " diff --git a/changelog.md b/changelog.md index 8de15d97b..7652cf949 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,15 @@ +# 2.2.0 (2017-04-25) +### Bug Fixes +* 修复了 Safari 隐身模式下用户无法登录的问题 + +### Features +* 短信支持图形验证码(需要在控制台应用选项「启用短信图形验证码」) + * 新增 `Cloud.requestCaptcha` 与 `Cloud.verifyCaptcha` 方法请求、校验图形验证码。 + * `Cloud.requestSmsCode`,`User.requestLoginSmsCode`,`User.requestMobilePhoneVerify` 与 `User.requestPasswordResetBySmsCode` 方法增加了 `authOptions.validateToken` 参数。没有提供有效的 validateToken 的请求会被拒绝。 +* 支持客户端查询 ACL(需要在控制台应用选项启用「查询时返回值包括 ACL」) + * 增加 `Query#includeACL` 方法。 + * `Object#fetch` 与 `File#fetch` 方法增加了 `fetchOptions.includeACL` 参数。 + ## 2.1.4 (2017-03-27) ### Bug Fixes * 如果在创建 `Role` 时不指定 `acl` 参数,SDK 会自动为其设置一个「默认 acl」,这导致了通过 Query 得到或使用 `Object.createWithoutData` 方法得到 `Role` 也会被意外的设置 acl。这个版本修复了这个问题。 @@ -18,7 +30,7 @@ ### Bug Fixes * 修复了使用 masterKey 获取一个 object 后再次 save 可能会报 ACL 格式不正确的问题。 -## 2.1.0 (2017-01-20) +# 2.1.0 (2017-01-20) ### Bug Fixes * 修复了 `File#toJSON` 序列化结果中缺失 objectId 等字段的问题 * 修复了使用 `Query#containsAll`、`Query#containedIn` 或 `Query#notContainedIn` 方法传入大数组时查询结果可能为空的问题 diff --git a/package.json b/package.json index f1d2facb8..19daf969e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.1.4", + "version": "2.2.0", "main": "./dist/node/index.js", "description": "LeanCloud JavaScript SDK.", "repository": { diff --git a/src/utils/index.js b/src/utils/index.js index 21bff2621..515f0478f 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -15,11 +15,11 @@ const ensureArray = target => { const transformFetchOptions = ({ keys, include, includeACL } = {}) => { const fetchOptions = {}; - if (_.isArray(keys)) { - fetchOptions.keys = keys.join(','); + if (keys) { + fetchOptions.keys = ensureArray(keys).join(','); } - if (_.isArray(include)) { - fetchOptions.include = include.join(','); + if (include) { + fetchOptions.include = ensureArray(include).join(','); } if (includeACL) { fetchOptions.returnACL = includeACL; diff --git a/src/version.js b/src/version.js index ec40e7562..8c3c3948c 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -module.exports = '2.1.4'; +module.exports = '2.2.0'; diff --git a/storage.d.ts b/storage.d.ts index 2dd4b001f..79e9d7fa5 100644 --- a/storage.d.ts +++ b/storage.d.ts @@ -8,6 +8,12 @@ declare namespace AV { export var applicationKey: string; export var masterKey: string; + interface FetchOptions { + keys?: string | string[]; + include?: string | string[]; + includeACL?: boolean; + } + export interface AuthOptions { /** * In Cloud Code and Node only, causes the Master Key to be used for this request. @@ -126,15 +132,15 @@ declare namespace AV { static withURL(name: string, url: string): File; static createWithoutData(objectId: string): File; - destroy(): Promise; - fetch(options?: AuthOptions): Promise; + destroy(): Promise; + fetch(fetchOptions?: FetchOptions, options?: AuthOptions): Promise; metaData(): any; metaData(metaKey: string): any; metaData(metaKey: string, metaValue: any): any; name(): string; ownerId(): string; url(): string; - save(options?: AuthOptions): Promise; + save(options?: AuthOptions): Promise; setACL(acl?: ACL): any; size(): any; thumbnailURL(width: number, height: number): string; @@ -254,7 +260,7 @@ declare namespace AV { destroy(options?: Object.DestroyOptions): Promise; dirty(attr: String): boolean; escape(attr: string): string; - fetch(fetchOptions?: any, options?: Object.FetchOptions): Promise; + fetch(fetchOptions?: FetchOptions, options?: AuthOptions): Promise; fetchWhenSave(enable: boolean): any; get(attr: string): any; getACL(): ACL; @@ -280,8 +286,6 @@ declare namespace AV { interface DestroyAllOptions extends AuthOptions { } - interface FetchOptions extends AuthOptions { } - interface SaveOptions extends AuthOptions, SilentOption, WaitOption { } interface SaveAllOptions extends AuthOptions { } @@ -440,6 +444,7 @@ declare namespace AV { greaterThanOrEqualTo(key: string, value: any): Query; include(key: string): Query; include(keys: string[]): Query; + includeACL(value?: boolean): Query; lessThan(key: string, value: any): Query; lessThanOrEqualTo(key: string, value: any): Query; limit(n: number): Query; From 54462a3c25bc5a7432462906e388ccde96deed6b Mon Sep 17 00:00:00 2001 From: leeyeh Date: Wed, 26 Apr 2017 15:31:30 +0800 Subject: [PATCH 15/47] refactor(test): run tests in us region within ci --- .travis.yml | 10 +++++++++- test/file.js | 2 +- test/object.js | 13 ++++++------- test/query.js | 2 +- test/status.js | 10 ++++------ test/test.js | 11 +++++++---- test/user.js | 33 ++------------------------------- 7 files changed, 30 insertions(+), 51 deletions(-) diff --git a/.travis.yml b/.travis.yml index 28aea5303..fcda6a710 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,15 @@ node_js: - "4" sudo: false - +env: + global: + - REGION=us + - APPID=QvNM6AG2khJtBQo6WRMWqfLV-gzGzoHsz + - APPKEY=be2YmUduiuEnCB2VR9bLRnnV + - MASTERKEY=1AqFJWElESSui6JKqHiKnLTY + - HOOKKEY=Y7RVPi20qOKQg4Lp8CyY35Lq + - STATUS_TARGET_USER_ID=57d7b3c28a51a2004eb9b31d + - FILE_ID=577258d732070000567dea7e before_install: - if [[ `npm -v` != 3* ]]; then npm i -g npm; fi install: diff --git a/test/file.js b/test/file.js index 994034d59..5f78a0c91 100644 --- a/test/file.js +++ b/test/file.js @@ -124,7 +124,7 @@ describe('File', function() { }); describe('#fetch', function() { - var fileId = '52f9dd5ae4b019816c865985'; + const fileId = process.env.FILE_ID || '52f9dd5ae4b019816c865985'; it('createWithoutData() should return a File', function() { var file = AV.File.createWithoutData(fileId); expect(file).to.be.a(AV.File); diff --git a/test/object.js b/test/object.js index 5a6c285a5..ea1406f57 100644 --- a/test/object.js +++ b/test/object.js @@ -310,7 +310,6 @@ describe('Objects', function(){ var Person=AV.Object.extend("Person"); var p; - var posts=[]; it("should create a Person",function(){ var Person = AV.Object.extend("Person"); @@ -320,13 +319,12 @@ describe('Objects', function(){ }); it("should create many to many relations",function(){ - var query = new AV.Query(Person); - return query.first().then(function(result){ - var p=result; + return Promise.all([ + new AV.Query(Post).first(), + new AV.Query(Person).first(), + ]).then(function([post, p]){ var relation = p.relation("likes"); - for(var i=0;i { - const fileId = '52f9dd5ae4b019816c865985'; + const fileId = process.env.FILE_ID || '52f9dd5ae4b019816c865985'; query = new AV.Query(AV.File); query.equalTo('objectId', fileId); return query.find().then(([file]) => { diff --git a/test/status.js b/test/status.js index 957b0646b..cb342e84a 100644 --- a/test/status.js +++ b/test/status.js @@ -1,6 +1,7 @@ 'use strict'; describe("AV.Status",function(){ + var targetUser = process.env.STATUS_TARGET_USER_ID || '5627906060b22ef9c464cc99'; before(function() { var userName = this.userName = 'StatusTest' + Date.now(); return AV.User.signUp(userName, userName).then(user => { @@ -18,7 +19,7 @@ describe("AV.Status",function(){ it("should send private status to an user.",function(){ var status = new AV.Status('image url', 'message'); - return AV.Status.sendPrivateStatus(status, '5627906060b22ef9c464cc99'); + return AV.Status.sendPrivateStatus(status, targetUser); }); it("should send status to a female user.",function(){ @@ -30,7 +31,7 @@ describe("AV.Status",function(){ }); describe("Query statuses.", function(){ - const user = AV.Object.createWithoutData('_User', '5627906060b22ef9c464cc99'); + const user = AV.Object.createWithoutData('_User', targetUser); it("should return unread count.", function(){ return AV.Status.countUnreadStatuses().then(function(response){ expect(response.total).to.be.a('number'); @@ -62,9 +63,6 @@ describe("AV.Status",function(){ }); describe("Status guide test.", function(){ - //follow 5627906060b22ef9c464cc99 - //unfolow 5627906060b22ef9c464cc99 - var targetUser = '5627906060b22ef9c464cc99'; it("should follow/unfollow successfully.", function(){ return AV.User.current().follow(targetUser).then(function(){ var query = AV.User.current().followeeQuery(); @@ -73,7 +71,7 @@ describe("AV.Status",function(){ }).then(function(followees){ debug(followees); expect(followees.length).to.be(1); - expect(followees[0].id).to.be('5627906060b22ef9c464cc99'); + expect(followees[0].id).to.be(targetUser); expect(followees[0].get('username')).to.be('leeyeh'); return AV.User.current().unfollow(targetUser); }).then(function(){ diff --git a/test/test.js b/test/test.js index da6810c19..1070700d6 100644 --- a/test/test.js +++ b/test/test.js @@ -1,5 +1,7 @@ 'use strict'; +if (!process) process = { env: {}}; + if (typeof require !== 'undefined') { global.debug = require('debug')('test'); global.expect = require('expect.js'); @@ -13,9 +15,10 @@ if (typeof require !== 'undefined') { // masterKey: 'l0n9wu3kwnrtf2cg1b6w2l87nphzpypgff6240d0lxui2mm4' // }); AV.init({ - appId: '95TNUaOSUd8IpKNW0RSqSEOm-9Nh9j0Va', - appKey: 'gNAE1iHowdQvV7cqpfCMGaGN', - masterKey: 'ue9M9nqwD4MQNXD3oiN5rAOv', - hookKey: '2iCbUZDgEF0siKxmCn2kVQXV' + appId: process.env.APPID || '95TNUaOSUd8IpKNW0RSqSEOm-9Nh9j0Va', + appKey: process.env.APPKEY || 'gNAE1iHowdQvV7cqpfCMGaGN', + masterKey: process.env.MASTERKEY || 'ue9M9nqwD4MQNXD3oiN5rAOv', + hookKey: process.env.HOOKKEY || '2iCbUZDgEF0siKxmCn2kVQXV', + region: process.env.REGION || 'cn', }); AV.setProduction(true); diff --git a/test/user.js b/test/user.js index ca4e77f5e..c907742bf 100644 --- a/test/user.js +++ b/test/user.js @@ -122,7 +122,7 @@ describe("User", function() { it("should return conditoinal users", function() { var query = new AV.Query(AV.User); query.equalTo("gender", "female"); // find all the women - return query.find(); + return query.find({useMasterKey: true}); }); }); @@ -168,35 +168,6 @@ describe("User", function() { }); }); - describe("Follow/unfollow users", function() { - it("should follow/unfollow", function() { - var user = AV.User.current(); - return user.follow('53fb0fd6e4b074a0f883f08a').then(function() { - var query = user.followeeQuery(); - return query.find(); - }).then(function(results) { - expect(results.length).to.be(1); - debug(results); - expect(results[0].id).to.be('53fb0fd6e4b074a0f883f08a'); - var followerQuery = AV.User.followerQuery('53fb0fd6e4b074a0f883f08a'); - return followerQuery.find(); - }).then(function(results) { - expect(results.filter(function(result) { - return result.id === user.id; - })).not.to.be(0); - debug(results); - //unfollow - return user.unfollow('53fb0fd6e4b074a0f883f08a'); - }).then(function() { - //query should be emtpy - var query = user.followeeQuery(); - return query.find(); - }).then(function(results) { - expect(results.length).to.be(0); - }); - }); - }); - describe("User logInAnonymously", function() { it("should create anonymous user, and login with AV.User.signUpOrlogInWithAuthData()", function() { var getFixedId = function () { @@ -225,7 +196,7 @@ describe("User", function() { return AV.User.logIn(username, password); }).then(function (loginedUser) { return AV.User.associateWithAuthData(loginedUser, 'weixin', { - openid: 'aaabbbccc123123', + openid: 'aaabbbccc123123'+username, access_token: 'a123123aaabbbbcccc', expires_in: 1382686496, }); From 8c4b2c50e7161c89acc9cfa972ec2905438162c8 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Wed, 26 Apr 2017 16:46:17 +0800 Subject: [PATCH 16/47] fix(User): correct typos --- src/user.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/user.js b/src/user.js index 088c118d3..5267199e2 100644 --- a/src/user.js +++ b/src/user.js @@ -909,8 +909,8 @@ module.exports = function(AV) { const data = { mobilePhoneNumber, } - if (options.validataToken) { - data.validate_token = options.validataToken + if (options.validateToken) { + data.validate_token = options.validateToken } var request = AVRequest("requestMobilePhoneVerify", null, null, "POST", data, options); @@ -933,8 +933,8 @@ module.exports = function(AV) { const data = { mobilePhoneNumber, } - if (options.validataToken) { - data.validate_token = options.validataToken + if (options.validateToken) { + data.validate_token = options.validateToken } var request = AVRequest("requestPasswordResetBySmsCode", null, null, "POST", data, options); @@ -984,8 +984,8 @@ module.exports = function(AV) { const data = { mobilePhoneNumber, } - if (options.validataToken) { - data.validate_token = options.validataToken + if (options.validateToken) { + data.validate_token = options.validateToken } var request = AVRequest("requestLoginSmsCode", null, null, "POST", data, options); From 083cd17173863d0a344e2cb35b4a13599950db3c Mon Sep 17 00:00:00 2001 From: leeyeh Date: Wed, 26 Apr 2017 16:50:44 +0800 Subject: [PATCH 17/47] chore(release): v2.2.1 --- bower.json | 2 +- changelog.md | 4 ++++ package.json | 2 +- src/version.js | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 644295864..d4e805019 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.2.0", + "version": "2.2.1", "homepage": "https://github.com/leancloud/javascript-sdk", "authors": [ "LeanCloud " diff --git a/changelog.md b/changelog.md index 7652cf949..09b5384e5 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,7 @@ +## 2.2.1 (2017-04-26) +### Bug Fixes +* 修复了 `User.requestLoginSmsCode`,`User.requestMobilePhoneVerify` 与 `User.requestPasswordResetBySmsCode` 方法 `authOptions.validateToken` 参数的拼写错误。 + # 2.2.0 (2017-04-25) ### Bug Fixes * 修复了 Safari 隐身模式下用户无法登录的问题 diff --git a/package.json b/package.json index 19daf969e..fac027f81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.2.0", + "version": "2.2.1", "main": "./dist/node/index.js", "description": "LeanCloud JavaScript SDK.", "repository": { diff --git a/src/version.js b/src/version.js index 8c3c3948c..d810b9459 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -module.exports = '2.2.0'; +module.exports = '2.2.1'; From 31cf60f4f4eea5761ca4754b63f93583ef3804b7 Mon Sep 17 00:00:00 2001 From: Ang Long Date: Tue, 9 May 2017 17:51:42 +0800 Subject: [PATCH 18/47] feat: add AV.Conversation class --- src/conversation.js | 96 ++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 2 + test/conversation.js | 18 +++++++++ test/index.js | 1 + 4 files changed, 117 insertions(+) create mode 100644 src/conversation.js create mode 100644 test/conversation.js diff --git a/src/conversation.js b/src/conversation.js new file mode 100644 index 000000000..d2845f390 --- /dev/null +++ b/src/conversation.js @@ -0,0 +1,96 @@ +'use strict'; + +const AV = require('./av'); + +/** + * @class + * + *

An AV.Conversation is a local representation of a LeanCloud realtime's + * conversation. Tshi class is a subclass of an AV.Object, and retains the + * same functionality of an AV.Object, but also extends it with various + * conversation specific methods, like get members, creators of this conversation. + *

+ */ +module.exports = AV.Object.extend('_Conversation', { + constructor: function(name, isSystem, isTransient) { + AV.Object.prototype.constructor.call(this, null, null); + this.set('name', name); + this.set('sys', isSystem ? true : false); + this.set('tr', isTransient ? true : false); + }, + /** + * Get current conversation's creator. + * + * @return {String} + */ + getCreator: function() { + return this.get('c'); + }, + + /** + * Get the last message's time. + * + * @return {Date} + */ + getLastMessageAt: function() { + return this.get('lm'); + }, + + /** + * Get this conversation's members + * + * @return {Array} + */ + getMembers: function() { + return this.get('m'); + }, + + /** + * Add a member to this conversation + * + * @param {String} member + */ + addMember: function(member) { + this.add('m', member); + }, + + /** + * Get this conversation's members who set this conversation as muted. + * + * @return {Boolean} + */ + getMutedMembers: function() { + return this.get('mu'); + }, + + /** + * Get this conversation's name field. + * + * @return String + */ + getName: function() { + return this.get('name'); + }, + + /** + * Returns true if this conversation is transient conversation. + * + * @return {Boolean} + */ + isTransient: function() { + return this.get('tr'); + }, + + /** + * Returns true if this conversation is system conversation. + * + * @return {Boolean} + */ + isSystem: function() { + return this.get('sys'); + }, + + send: function() { + + }, +}); diff --git a/src/index.js b/src/index.js index 6de1aeeae..d26187e57 100644 --- a/src/index.js +++ b/src/index.js @@ -32,6 +32,8 @@ require('./status')(AV); require('./search')(AV); require('./insight')(AV); +AV.Conversation = require('./conversation'); + module.exports = AV; /** diff --git a/test/conversation.js b/test/conversation.js new file mode 100644 index 000000000..cba70462e --- /dev/null +++ b/test/conversation.js @@ -0,0 +1,18 @@ +'use strict'; + +describe('Conversation', () => { + describe('.constructor', () => { + const conv = new AV.Conversation('test', true, false); + expect(conv.isTransient()).to.be(false); + expect(conv.isSystem()).to.be(true); + expect(conv.getName()).to.be('test'); + }); + describe('#save', () => { + it('should create a realtime conversation', () => { + const conv = new AV.Conversation('test'); + conv.addMember('test1'); + conv.addMember('test2'); + return conv.save(); + }); + }); +}); diff --git a/test/index.js b/test/index.js index 6422cd05c..05c944d64 100644 --- a/test/index.js +++ b/test/index.js @@ -13,3 +13,4 @@ require('./status.js'); require('./sms.js'); require('./search.js'); require('./hooks.js'); +require('./conversation.js'); From 33c3907c4bf25c37f36f1d629fe0005e32299772 Mon Sep 17 00:00:00 2001 From: Ang Long Date: Wed, 10 May 2017 11:59:53 +0800 Subject: [PATCH 19/47] feat: add AV.Conversation#send function --- src/conversation.js | 73 +++++++++++++++++++++++++++++++++----------- test/conversation.js | 12 +++++++- 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/conversation.js b/src/conversation.js index d2845f390..19d31099c 100644 --- a/src/conversation.js +++ b/src/conversation.js @@ -1,22 +1,31 @@ 'use strict'; +const request = require('./request').request; const AV = require('./av'); -/** - * @class - * - *

An AV.Conversation is a local representation of a LeanCloud realtime's - * conversation. Tshi class is a subclass of an AV.Object, and retains the - * same functionality of an AV.Object, but also extends it with various - * conversation specific methods, like get members, creators of this conversation. - *

- */ module.exports = AV.Object.extend('_Conversation', { - constructor: function(name, isSystem, isTransient) { + + /** + * @class AV.Conversation + *

An AV.Conversation is a local representation of a LeanCloud realtime's + * conversation. This class is a subclass of AV.Object, and retains the + * same functionality of an AV.Object, but also extends it with various + * conversation specific methods, like get members, creators of this conversation. + *

+ * + * @param {String} name The name of the Role to create. + * @param {Boolean} [options.isSystem] Set this conversation as system conversation. + * @param {Boolean} [options.isTransient] Set this conversation as transient conversation. + */ + constructor: function(name, options = {}) { AV.Object.prototype.constructor.call(this, null, null); this.set('name', name); - this.set('sys', isSystem ? true : false); - this.set('tr', isTransient ? true : false); + if (options.isSystem !== undefined) { + this.set('sys', options.isSystem ? true: false); + } + if (options.isTransient !== undefined) { + this.set('tr', options.isTransient ? true : false); + } }, /** * Get current conversation's creator. @@ -39,7 +48,7 @@ module.exports = AV.Object.extend('_Conversation', { /** * Get this conversation's members * - * @return {Array} + * @return {String[]} */ getMembers: function() { return this.get('m'); @@ -51,13 +60,13 @@ module.exports = AV.Object.extend('_Conversation', { * @param {String} member */ addMember: function(member) { - this.add('m', member); + return this.add('m', member); }, /** * Get this conversation's members who set this conversation as muted. * - * @return {Boolean} + * @return {String[]} */ getMutedMembers: function() { return this.get('mu'); @@ -90,7 +99,37 @@ module.exports = AV.Object.extend('_Conversation', { return this.get('sys'); }, - send: function() { - + /** + * Send realtime message to this conversation, using HTTP request. + * + * @param {String} clientId Sender's client id. + * @param {(String|Object)} message The message which will send to conversation. + * It could be a raw string, or an object with a `toJSON` method, like a + * realtime SDK's Message object. See more: {@link https://leancloud.cn/docs/realtime_guide-js.html#消息} + * @param {Boolean} [options.transient] Whether send this message as transient message or not. + * @param {Object} [options.pushData] Push data to this message. See more: {@link https://url.leanapp.cn/pushData 推送消息内容} + * @param {AuthOptions} [authOptions] + * @return {Promise} + */ + send: function(clientId, message, options, authOptions) { + if (typeof message.toJSON === 'function') { + message = message.toJSON(); + } + if (typeof message !== 'string') { + message = JSON.stringify(message); + } + const data = { + from_peer: clientId, + conv_id: this.id, + transient: false, + message: message, + }; + if (options.transient !== undefined) { + data.transient = options.transient ? true : false; + } + if (options.pushData !== undefined) { + data.push_data = options.pushData; + } + return request('rtm', 'messages', null, 'POST', data, authOptions); }, }); diff --git a/test/conversation.js b/test/conversation.js index cba70462e..301ca2b44 100644 --- a/test/conversation.js +++ b/test/conversation.js @@ -2,7 +2,7 @@ describe('Conversation', () => { describe('.constructor', () => { - const conv = new AV.Conversation('test', true, false); + const conv = new AV.Conversation('test', { isSystem: true, isTransient: false }); expect(conv.isTransient()).to.be(false); expect(conv.isSystem()).to.be(true); expect(conv.getName()).to.be('test'); @@ -15,4 +15,14 @@ describe('Conversation', () => { return conv.save(); }); }); + describe('#send', () => { + it('should send a realtime message to the conversation', () => { + const conv = new AV.Conversation('test'); + conv.addMember('test1'); + conv.addMember('test2'); + return conv.save().then(() => { + return conv.send('admin', 'test test test!', {}, { useMasterKey: true }); + }); + }); + }) }); From 39bf2cdbedf949d5ab490fb8c1c83c3a9d7e55db Mon Sep 17 00:00:00 2001 From: Ang Long Date: Thu, 11 May 2017 12:01:34 +0800 Subject: [PATCH 20/47] feat: add AV.Conversation's type define file --- storage.d.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/storage.d.ts b/storage.d.ts index 79e9d7fa5..6932715a5 100644 --- a/storage.d.ts +++ b/storage.d.ts @@ -548,6 +548,31 @@ declare namespace AV { getRoles(options?: AuthOptions): Promise; } + /** + * @class AV.Conversation + *

An AV.Conversation is a local representation of a LeanCloud realtime's + * conversation. This class is a subclass of AV.Object, and retains the + * same functionality of an AV.Object, but also extends it with various + * conversation specific methods, like get members, creators of this conversation. + *

+ * + * @param {String} name The name of the Role to create. + * @param {Boolean} [options.isSystem] Set this conversation as system conversation. + * @param {Boolean} [options.isTransient] Set this conversation as transient conversation. + */ + export class Conversation extends Object { + constructor(name: string, options?: { isSytem?: boolean, isTransient?: boolean }); + getCreator(): string; + getLastMessageAt(): Date; + getMembers(): string[]; + addMember(member: string): Conversation; + getMutedMembers(): string[]; + getName(): string; + isTransient(): boolean; + isSystem(): boolean; + send(clintId: string, message: string|object, options?: { transient?: boolean, pushData?: object }, authOptions?: AuthOptions): Promise; + } + export class Error { code: ErrorCode; From 4abdd8c5c631e9bdc7f0804a5301f5fc2f83b1cd Mon Sep 17 00:00:00 2001 From: Ang Long Date: Thu, 11 May 2017 15:55:02 +0800 Subject: [PATCH 21/47] feat: support toClientIds option in AV.Conversation#send --- src/conversation.js | 10 +++++++--- storage.d.ts | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/conversation.js b/src/conversation.js index 19d31099c..dbb0fcf5f 100644 --- a/src/conversation.js +++ b/src/conversation.js @@ -102,16 +102,17 @@ module.exports = AV.Object.extend('_Conversation', { /** * Send realtime message to this conversation, using HTTP request. * - * @param {String} clientId Sender's client id. + * @param {String} fromClient Sender's client id. * @param {(String|Object)} message The message which will send to conversation. * It could be a raw string, or an object with a `toJSON` method, like a * realtime SDK's Message object. See more: {@link https://leancloud.cn/docs/realtime_guide-js.html#消息} * @param {Boolean} [options.transient] Whether send this message as transient message or not. + * @param {String[]} [options.toClients] Ids of clients to send to. This option can be used only in system conversation. * @param {Object} [options.pushData] Push data to this message. See more: {@link https://url.leanapp.cn/pushData 推送消息内容} * @param {AuthOptions} [authOptions] * @return {Promise} */ - send: function(clientId, message, options, authOptions) { + send: function(fromClient, message, options={}, authOptions={}) { if (typeof message.toJSON === 'function') { message = message.toJSON(); } @@ -119,11 +120,14 @@ module.exports = AV.Object.extend('_Conversation', { message = JSON.stringify(message); } const data = { - from_peer: clientId, + from_peer: fromClient, conv_id: this.id, transient: false, message: message, }; + if (options.toClientIds !== undefined) { + data.to_peers = toClients; + } if (options.transient !== undefined) { data.transient = options.transient ? true : false; } diff --git a/storage.d.ts b/storage.d.ts index 6932715a5..a87a86254 100644 --- a/storage.d.ts +++ b/storage.d.ts @@ -570,7 +570,7 @@ declare namespace AV { getName(): string; isTransient(): boolean; isSystem(): boolean; - send(clintId: string, message: string|object, options?: { transient?: boolean, pushData?: object }, authOptions?: AuthOptions): Promise; + send(fromClient: string, message: string|object, options?: { transient?: boolean, pushData?: object, toClients?: string[] }, authOptions?: AuthOptions): Promise; } export class Error { From 53a7c8cc458ad48b02b639733e2d236b0881f56f Mon Sep 17 00:00:00 2001 From: Ang Long Date: Thu, 11 May 2017 16:43:26 +0800 Subject: [PATCH 22/47] test: add system conversation send test --- src/conversation.js | 4 ++-- test/conversation.js | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/conversation.js b/src/conversation.js index dbb0fcf5f..e4134c7bd 100644 --- a/src/conversation.js +++ b/src/conversation.js @@ -125,8 +125,8 @@ module.exports = AV.Object.extend('_Conversation', { transient: false, message: message, }; - if (options.toClientIds !== undefined) { - data.to_peers = toClients; + if (options.toClients !== undefined) { + data.to_peers = options.toClients; } if (options.transient !== undefined) { data.transient = options.transient ? true : false; diff --git a/test/conversation.js b/test/conversation.js index 301ca2b44..951633e56 100644 --- a/test/conversation.js +++ b/test/conversation.js @@ -24,5 +24,16 @@ describe('Conversation', () => { return conv.send('admin', 'test test test!', {}, { useMasterKey: true }); }); }); + + it('should send a realtime message to the system conversation', () => { + const conv = new AV.Conversation('system', { isSystem: true }); + return conv.save().then(() => { + return conv.send('admin', 'test system conversation !', { + toClients: ['user1', 'user2'] + }, { + useMasterKey: true, + }); + }); + }); }) }); From 76297f86b938264c6fb71ebc755eeb20d17fe77f Mon Sep 17 00:00:00 2001 From: Lee Yeh Date: Thu, 11 May 2017 18:02:37 +0800 Subject: [PATCH 23/47] feat(Captcha): add AV.Captcha (#472) * feat(Captcha): add AV.Captcha * feat(Captcha): ttl and size requires masterKey * fix(Captcha): Cloud.requestCaptcha returns AV.Captcha * fix(Captcha): assign AV.Captcha before AV.Cloud --- demo/index.html | 4 +- demo/test.js | 30 +++------ src/captcha.js | 142 +++++++++++++++++++++++++++++++++++++++++++ src/cloudfunction.js | 22 +++---- src/index.js | 1 + storage.d.ts | 30 ++++++++- test/captcha.js | 21 +++++++ test/index.js | 1 + test/test.html | 1 + 9 files changed, 217 insertions(+), 35 deletions(-) create mode 100644 src/captcha.js create mode 100644 test/captcha.js diff --git a/demo/index.html b/demo/index.html index 0a3cf7def..7f447813f 100644 --- a/demo/index.html +++ b/demo/index.html @@ -10,9 +10,9 @@

LeanCloud

为开发加速

欢迎调试 JavaScript SDK,请打开浏览器控制台

- + - +
diff --git a/demo/test.js b/demo/test.js index 074ebf930..349be37c5 100644 --- a/demo/test.js +++ b/demo/test.js @@ -18,23 +18,13 @@ var region = 'cn'; av.init({ appId: appId, appKey: appKey, region: region }); -let captchaToken; -const captchaImage = document.getElementById('captcha'); -const captchaInput = document.getElementById('code'); - -function refreshCaptcha(){ - AV.Cloud.requestCaptcha({ - size: 6, - ttl: 30, - }).then(function(data) { - captchaToken = data.captchaToken; - captchaImage.src = data.url; - }).catch(console.error); -} -refreshCaptcha(); - -function verify() { - AV.Cloud.verifyCaptcha(captchaInput.value, captchaToken).then(function(validateCode) { - console.log('validateCode: ' + validateCode); - }, console.error); -} +av.Captcha.request().then(captcha => { + captcha.bind({ + textInput: 'code', + image: 'captcha', + verifyButton: 'verify', + }, { + success: validateCode => console.log('validateCode: ' + validateCode), + error: console.error, + }); +}); diff --git a/src/captcha.js b/src/captcha.js new file mode 100644 index 000000000..fc8643733 --- /dev/null +++ b/src/captcha.js @@ -0,0 +1,142 @@ +const { tap } = require('./utils'); + +module.exports = (AV) => { + /** + * @class + * @example + * AV.Captcha.request().then(captcha => { + * captcha.bind({ + * textInput: 'code', // the id for textInput + * image: 'captcha', + * verifyButton: 'verify', + * }, { + * success: (validateCode) => {}, // next step + * error: (error) => {}, // present error.message to user + * }); + * }); + */ + AV.Captcha = function Captcha(options, authOptions) { + this._options = options; + this._authOptions = authOptions; + /** + * The image url of the captcha + * @type string + */ + this.url = undefined; + /** + * The captchaToken of the captcha. + * @type string + */ + this.captchaToken = undefined; + /** + * The validateToken of the captcha. + * @type string + */ + this.validateToken = undefined; + }; + + /** + * Refresh the captcha + * @return {Promise.} a new capcha url + */ + AV.Captcha.prototype.refresh = function refresh() { + return AV.Cloud.requestCaptcha(this._options, this._authOptions).then(({ + captchaToken, url, + }) => { + Object.assign(this, { captchaToken, url }); + return url; + }); + }; + + /** + * Verify the captcha + * @param {String} code The code from user input + * @return {Promise.} validateToken if the code is valid + */ + AV.Captcha.prototype.verify = function verify(code) { + return AV.Cloud.verifyCaptcha(code, this.captchaToken) + .then(tap(validateToken => (this.validateToken = validateToken))); + }; + + if (process.env.CLIENT_PLATFORM === 'Browser') { + /** + * Bind the captcha to HTMLElements. ONLY AVAILABLE in browsers. + * @param [elements] + * @param {String|HTMLInputElement} [elements.textInput] An input element typed text, or the id for the element. + * @param {String|HTMLImageElement} [elements.image] An image element, or the id for the element. + * @param {String|HTMLElement} [elements.verifyButton] A button element, or the id for the element. + * @param [callbacks] + * @param {Function} [callbacks.success] Success callback will be called if the code is verified. The param `validateCode` can be used for further SMS request. + * @param {Function} [callbacks.error] Error callback will be called if something goes wrong, detailed in param `error.message`. + */ + AV.Captcha.prototype.bind = function bind({ + textInput, + image, + verifyButton, + }, { + success, + error, + }) { + if (typeof textInput === 'string') { + textInput = document.getElementById(textInput); + if (!textInput) throw new Error(`textInput with id ${textInput} not found`); + } + if (typeof image === 'string') { + image = document.getElementById(image); + if (!image) throw new Error(`image with id ${image} not found`); + } + if (typeof verifyButton === 'string') { + verifyButton = document.getElementById(verifyButton); + if (!verifyButton) throw new Error(`verifyButton with id ${verifyButton} not found`); + } + + this.__refresh = () => this.refresh().then(url => { + image.src = url; + if (textInput) { + textInput.value = ''; + textInput.focus(); + } + }).catch(err => console.warn(`refresh captcha fail: ${err.message}`)); + if (image) { + this.__image = image; + image.src = this.url; + image.addEventListener('click', this.__refresh); + } + + this.__verify = () => { + const code = textInput.value; + this.verify(code).catch(err => { + this.__refresh(); + throw err; + }).then(success, error).catch(err => console.warn(`verify captcha fail: ${err.message}`)); + }; + if (textInput && verifyButton) { + this.__verifyButton = verifyButton; + verifyButton.addEventListener('click', this.__verify); + } + }; + + /** + * unbind the captcha from HTMLElements. ONLY AVAILABLE in browsers. + */ + AV.Captcha.prototype.unbind = function unbind() { + if (this.__image) this.__image.removeEventListener('click', this.__refresh); + if (this.__verifyButton) this.__verifyButton.removeEventListener('click', this.__verify); + }; + } + + + /** + * Request a captcha + * @param [options] + * @param {Number} [options.width] width(px) of the captcha, ranged 60-200 + * @param {Number} [options.height] height(px) of the captcha, ranged 30-100 + * @param {Number} [options.size=4] length of the captcha, ranged 3-6. MasterKey required. + * @param {Number} [options.ttl=60] time to live(s), ranged 10-180. MasterKey required. + * @return {Promise.} + */ + AV.Captcha.request = (options, authOptions) => { + const captcha = new AV.Captcha(options, authOptions); + return captcha.refresh().then(() => captcha); + }; +}; diff --git a/src/cloudfunction.js b/src/cloudfunction.js index 8ed198bb6..f9c679cf0 100644 --- a/src/cloudfunction.js +++ b/src/cloudfunction.js @@ -9,6 +9,7 @@ module.exports = function(AV) { *

* * @namespace + * @borrows AV.Captcha.request as requestCaptcha */ AV.Cloud = AV.Cloud || {}; @@ -110,17 +111,8 @@ module.exports = function(AV) { return request; }, - /** - * request a captcha - * @param {Object} [options] - * @param {Number} [options.size=4] length of the captcha, ranged 3-6 - * @param {Number} [options.width] width(px) of the captcha, ranged 60-200 - * @param {Number} [options.height] height(px) of the captcha, ranged 30-100 - * @param {Number} [options.ttl=60] time to live(s), ranged 10-180 - * @return {Promise} { captchaToken, url } - */ - requestCaptcha(options) { - return AVRequest('requestCaptcha', null, null, 'GET', options).then(({ + _requestCaptcha(options, authOptions) { + return AVRequest('requestCaptcha', null, null, 'GET', options, authOptions).then(({ captcha_url: url, captcha_token: captchaToken, }) => ({ @@ -130,7 +122,13 @@ module.exports = function(AV) { }, /** - * verify captcha code + * Request a captcha. + */ + requestCaptcha: AV.Captcha.request, + + /** + * Verify captcha code. This is the low-level API for captcha. + * Checkout {@link AV.Captcha} for high abstract APIs. * @param {String} code the code from user input * @param {String} captchaToken captchaToken returned by {@link AV.Cloud.requestCaptcha} * @return {Promise.} validateToken if the code is valid diff --git a/src/index.js b/src/index.js index d26187e57..7bc4f6edf 100644 --- a/src/index.js +++ b/src/index.js @@ -26,6 +26,7 @@ require('./object')(AV); require('./role')(AV); require('./user')(AV); require('./query')(AV); +require('./captcha')(AV); require('./cloudfunction')(AV); require('./push')(AV); require('./status')(AV); diff --git a/storage.d.ts b/storage.d.ts index a87a86254..96e8bf22b 100644 --- a/storage.d.ts +++ b/storage.d.ts @@ -27,6 +27,13 @@ declare namespace AV { validateToken?: string; } + interface CaptchaOptions { + size?: number; + width?: number; + height?: number; + ttl?: number; + } + export interface WaitOption { /** * Set to true to wait for the server to confirm success @@ -546,6 +553,27 @@ declare namespace AV { refreshSessionToken(options?: AuthOptions): Promise; getRoles(options?: AuthOptions): Promise; + + } + + export class Captcha { + url: string; + captchaToken: string; + validateToken: string; + + static request(options?: CaptchaOptions, authOptions?: AuthOptions): Promise; + + refresh(): Promise; + verify(code: string): Promise; + bind(elements?: { + textInput?: string|HTMLInputElement, + image?: string|HTMLImageElement, + verifyButton?: string|HTMLElement, + }, callbacks?: { + success?: (validateToken: string) => any, + error?: (error: Error) => any, + }): void; + unbind(): void; } /** @@ -710,7 +738,7 @@ declare namespace AV { function run(name: string, data?: any, options?: AuthOptions): Promise; function requestSmsCode(data: string|{ mobilePhoneNumber: string, template?: string, sign?: string }, options?: SMSAuthOptions): Promise; function verifySmsCode(code: string, phone: string): Promise; - function requestCaptcha(options?: { size?: number, width?: number, height?: number, ttl?: number}): Promise<{ captchaToken: string, dataURI: string }>; + function requestCaptcha(options?: CaptchaOptions, authOptions?: AuthOptions): Promise; function verifyCaptcha(code: string, captchaToken: string): Promise; } diff --git a/test/captcha.js b/test/captcha.js new file mode 100644 index 000000000..752ad3bf3 --- /dev/null +++ b/test/captcha.js @@ -0,0 +1,21 @@ +describe('Captcha', () => { + before(function () { + return AV.Captcha.request().then(captcha => { + this.captcha = captcha; + }); + }); + it('.request', function () { + this.captcha.should.be.instanceof(AV.Captcha); + this.captcha.url.should.be.a.String(); + this.captcha.captchaToken.should.be.a.String(); + }); + it('.refresh', function () { + const currentUrl = this.captcha.url; + return this.captcha.refresh().then(() => { + this.captcha.url.should.not.equalTo(currentUrl); + }); + }); + it('.refresh', function () { + return this.captcha.verify('fakecode').should.be.rejected(); + }); +}); diff --git a/test/index.js b/test/index.js index 05c944d64..c529b272e 100644 --- a/test/index.js +++ b/test/index.js @@ -3,6 +3,7 @@ require('./test.js'); require('./av.js'); require('./file.js'); require('./error.js'); +// require('./captcha.js'); require('./object.js'); require('./user.js'); require('./query.js'); diff --git a/test/test.html b/test/test.html index 304164ff6..a58bcf185 100644 --- a/test/test.html +++ b/test/test.html @@ -27,6 +27,7 @@ + From 7b7fb8473711449761738599a9bbeab5b03a2567 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Thu, 11 May 2017 18:04:12 +0800 Subject: [PATCH 24/47] chore(release): v2.3.0 --- bower.json | 2 +- changelog.md | 5 +++++ package.json | 2 +- src/version.js | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index d4e805019..0a8bee303 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.2.1", + "version": "2.3.0", "homepage": "https://github.com/leancloud/javascript-sdk", "authors": [ "LeanCloud " diff --git a/changelog.md b/changelog.md index 09b5384e5..e9a20a116 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,8 @@ +# 2.3.0 (2017-05-11) +### Features +* 增加了 `AV.Conversation` 类。现在可以直接使用 SDK 来创建、管理会话,发送消息。 +* 改进了验证码 API。增加了 `AV.Captcha`,可以通过 `AV.Captcha.request` 方法获取一个 Captcha 实例。特别的,在浏览器中,可以直接使用 `Captcha#bind` 方法将 Captcha 与 DOM 元素进行绑定。 + ## 2.2.1 (2017-04-26) ### Bug Fixes * 修复了 `User.requestLoginSmsCode`,`User.requestMobilePhoneVerify` 与 `User.requestPasswordResetBySmsCode` 方法 `authOptions.validateToken` 参数的拼写错误。 diff --git a/package.json b/package.json index fac027f81..05d853be8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.2.1", + "version": "2.3.0", "main": "./dist/node/index.js", "description": "LeanCloud JavaScript SDK.", "repository": { diff --git a/src/version.js b/src/version.js index d810b9459..e6f9ea17c 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -module.exports = '2.2.1'; +module.exports = '2.3.0'; From c2f7ddfafca78c67949e7f308d41e7ff60e798ef Mon Sep 17 00:00:00 2001 From: Ang Long Date: Fri, 12 May 2017 12:25:00 +0800 Subject: [PATCH 25/47] docs: fix AV.Conversation's api doc generation --- src/conversation.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/conversation.js b/src/conversation.js index e4134c7bd..f56851cda 100644 --- a/src/conversation.js +++ b/src/conversation.js @@ -3,20 +3,19 @@ const request = require('./request').request; const AV = require('./av'); -module.exports = AV.Object.extend('_Conversation', { - - /** - * @class AV.Conversation - *

An AV.Conversation is a local representation of a LeanCloud realtime's - * conversation. This class is a subclass of AV.Object, and retains the - * same functionality of an AV.Object, but also extends it with various - * conversation specific methods, like get members, creators of this conversation. - *

- * - * @param {String} name The name of the Role to create. - * @param {Boolean} [options.isSystem] Set this conversation as system conversation. - * @param {Boolean} [options.isTransient] Set this conversation as transient conversation. - */ +/** + * @class AV.Conversation + *

An AV.Conversation is a local representation of a LeanCloud realtime's + * conversation. This class is a subclass of AV.Object, and retains the + * same functionality of an AV.Object, but also extends it with various + * conversation specific methods, like get members, creators of this conversation. + *

+ * + * @param {String} name The name of the Role to create. + * @param {Boolean} [options.isSystem] Set this conversation as system conversation. + * @param {Boolean} [options.isTransient] Set this conversation as transient conversation. + */ +module.exports = AV.Object.extend('_Conversation', /** @lends AV.Conversation.prototype */ { constructor: function(name, options = {}) { AV.Object.prototype.constructor.call(this, null, null); this.set('name', name); From 1afcb0efa00cbe5bf7b5747ce91c845c79539c63 Mon Sep 17 00:00:00 2001 From: Ang Long Date: Fri, 12 May 2017 13:24:31 +0800 Subject: [PATCH 26/47] docs: fix AV.Conversation's api doc index generation --- src/conversation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversation.js b/src/conversation.js index f56851cda..53498c400 100644 --- a/src/conversation.js +++ b/src/conversation.js @@ -4,13 +4,13 @@ const request = require('./request').request; const AV = require('./av'); /** - * @class AV.Conversation *

An AV.Conversation is a local representation of a LeanCloud realtime's * conversation. This class is a subclass of AV.Object, and retains the * same functionality of an AV.Object, but also extends it with various * conversation specific methods, like get members, creators of this conversation. *

* + * @class AV.Conversation * @param {String} name The name of the Role to create. * @param {Boolean} [options.isSystem] Set this conversation as system conversation. * @param {Boolean} [options.isTransient] Set this conversation as transient conversation. From 8a9fb087a1f941657b4494c1c73a680923f90223 Mon Sep 17 00:00:00 2001 From: Lee Yeh Date: Fri, 12 May 2017 15:44:11 +0800 Subject: [PATCH 27/47] fix(Captcha): fix a stackoverflow (#478) --- src/captcha.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/captcha.js b/src/captcha.js index fc8643733..3c66903a6 100644 --- a/src/captcha.js +++ b/src/captcha.js @@ -40,7 +40,7 @@ module.exports = (AV) => { * @return {Promise.} a new capcha url */ AV.Captcha.prototype.refresh = function refresh() { - return AV.Cloud.requestCaptcha(this._options, this._authOptions).then(({ + return AV.Cloud._requestCaptcha(this._options, this._authOptions).then(({ captchaToken, url, }) => { Object.assign(this, { captchaToken, url }); From bfe528ae191637547cfdd282f04005a4077c934d Mon Sep 17 00:00:00 2001 From: leeyeh Date: Fri, 12 May 2017 15:47:12 +0800 Subject: [PATCH 28/47] chore(release): v2.3.1 --- bower.json | 2 +- changelog.md | 4 ++++ package.json | 2 +- src/version.js | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 0a8bee303..d63526c4d 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.3.0", + "version": "2.3.1", "homepage": "https://github.com/leancloud/javascript-sdk", "authors": [ "LeanCloud " diff --git a/changelog.md b/changelog.md index e9a20a116..ecd65eb17 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,7 @@ +## 2.3.1 (2017-05-12) +### Bug Fixes +* 修复了获取图形验证码会导致栈溢出的问题。 + # 2.3.0 (2017-05-11) ### Features * 增加了 `AV.Conversation` 类。现在可以直接使用 SDK 来创建、管理会话,发送消息。 diff --git a/package.json b/package.json index 05d853be8..7928c1c8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.3.0", + "version": "2.3.1", "main": "./dist/node/index.js", "description": "LeanCloud JavaScript SDK.", "repository": { diff --git a/src/version.js b/src/version.js index e6f9ea17c..695f2e5dd 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -module.exports = '2.3.0'; +module.exports = '2.3.1'; From 148f4bbf10bf6943d764c131a56cd1220c6b9ad6 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Fri, 12 May 2017 16:12:46 +0800 Subject: [PATCH 29/47] chore(release): v2.3.2 v2.3.1 was accidentally published without dist --- bower.json | 2 +- changelog.md | 2 +- package.json | 2 +- src/version.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index d63526c4d..75cb6caa9 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.3.1", + "version": "2.3.2", "homepage": "https://github.com/leancloud/javascript-sdk", "authors": [ "LeanCloud " diff --git a/changelog.md b/changelog.md index ecd65eb17..012859f00 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -## 2.3.1 (2017-05-12) +## 2.3.2 (2017-05-12) ### Bug Fixes * 修复了获取图形验证码会导致栈溢出的问题。 diff --git a/package.json b/package.json index 7928c1c8a..e4c90b47f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.3.1", + "version": "2.3.2", "main": "./dist/node/index.js", "description": "LeanCloud JavaScript SDK.", "repository": { diff --git a/src/version.js b/src/version.js index 695f2e5dd..1ee9e0d21 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -module.exports = '2.3.1'; +module.exports = '2.3.2'; From f28ef5a2ecd4deb6fdcf940f4fe63facb38b54b1 Mon Sep 17 00:00:00 2001 From: Ang Long Date: Sat, 13 May 2017 17:38:23 +0800 Subject: [PATCH 30/47] feat: add AV.Conversation#broadcast function --- src/conversation.js | 38 ++++++++++++++++++++++++++++++++++++++ storage.d.ts | 1 + test/conversation.js | 16 +++++++++++++++- 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/conversation.js b/src/conversation.js index 53498c400..5db13a227 100644 --- a/src/conversation.js +++ b/src/conversation.js @@ -1,5 +1,6 @@ 'use strict'; +const _ = require('underscore'); const request = require('./request').request; const AV = require('./av'); @@ -135,4 +136,41 @@ module.exports = AV.Object.extend('_Conversation', /** @lends AV.Conversation.pr } return request('rtm', 'messages', null, 'POST', data, authOptions); }, + + /** + * Send realtime broadcast message to all clients, with this conversation, using HTTP request. + * + * @param {String} fromClient Sender's client id. + * @param {(String|Object)} message The message which will send to conversation. + * It could be a raw string, or an object with a `toJSON` method, like a + * realtime SDK's Message object. See more: {@link https://leancloud.cn/docs/realtime_guide-js.html#消息}. + * @param {Object} [options.pushData] Push data to this message. See more: {@link https://url.leanapp.cn/pushData 推送消息内容}. + * @param {Object} [options.validTill] The message will valid till this time. + * @param {AuthOptions} [authOptions] + * @return {Promise} + */ + broadcast: function(fromClient, message, options={}, authOptions={}) { + if (typeof message.toJSON === 'function') { + message = message.toJSON(); + } + if (typeof message !== 'string') { + message = JSON.stringify(message); + } + const data = { + from_peer: fromClient, + conv_id: this.id, + message: message, + }; + if (options.pushData !== undefined) { + data.push = options.pushData; + } + if (options.validTill !== undefined) { + let ts = options.validTill; + if (_.isDate(ts)) { + ts = ts.getTime(); + } + options.valid_till = ts; + } + return request('rtm', 'broadcast', null, 'POST', data, authOptions); + } }); diff --git a/storage.d.ts b/storage.d.ts index 96e8bf22b..121073c8b 100644 --- a/storage.d.ts +++ b/storage.d.ts @@ -599,6 +599,7 @@ declare namespace AV { isTransient(): boolean; isSystem(): boolean; send(fromClient: string, message: string|object, options?: { transient?: boolean, pushData?: object, toClients?: string[] }, authOptions?: AuthOptions): Promise; + broadcast(fromClient: string, message: string|object, options?: { pushData?: object, validTill?: number|Date }, authOptions?: AuthOptions): Promise; } export class Error { diff --git a/test/conversation.js b/test/conversation.js index 951633e56..35603b2e4 100644 --- a/test/conversation.js +++ b/test/conversation.js @@ -35,5 +35,19 @@ describe('Conversation', () => { }); }); }); - }) + }); + describe('#broadcast', () => { + it('should broadcast a message to all clients with current conversation', () => { + const conv = new AV.Conversation('test', { isSystem: true }); + return conv.save().then(() => { + const options = { + validTill: new Date().getTime() / 1000 + 1000, + }; + const authOptions = { + useMasterKey: true, + }; + return conv.broadcast('admin', 'test broadcast!', options, authOptions); + }); + }); + }); }); From 5143dbedc8ee9fbd100ebb1e717165991b4d64b9 Mon Sep 17 00:00:00 2001 From: Lee Yeh Date: Mon, 15 May 2017 13:52:00 +0800 Subject: [PATCH 31/47] chore: ensure dist files before publishing (#479) * chore: ensure dist files before publishing * fix(ci): use prepublishOnly hook prepublish hook is unexpectedly triggerred when running `npm install` --- package.json | 3 ++- script/check-version.js | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100755 script/check-version.js diff --git a/package.json b/package.json index e4c90b47f..6cae3d5b9 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "build:weapp": "CLIENT_PLATFORM=Weapp webpack --config webpack/weapp.js", "uglify:browser": "cd dist; uglifyjs av.js -m -c -o av-min.js --in-source-map av.js.map --source-map av-min.js.map; cd ..;", "uglify:weapp": "cd dist; uglifyjs av-weapp.js -m -c -o av-weapp-min.js --in-source-map av-weapp.js.map --source-map av-weapp-min.js.map; cd ..;", - "build": "gulp build" + "build": "gulp build", + "prepublishOnly": "./script/check-version.js" }, "dependencies": { "debug": "^2.2.0", diff --git a/script/check-version.js b/script/check-version.js new file mode 100755 index 000000000..d5f5b171c --- /dev/null +++ b/script/check-version.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node +const assert = require('assert'); +assert(require('../').version === require('../package.json').version); +assert(require('../bower.json').version === require('../package.json').version); From 924dfc0fa0a6819e704814f74bdb9dfd3c007731 Mon Sep 17 00:00:00 2001 From: Lee Yeh Date: Mon, 15 May 2017 13:52:00 +0800 Subject: [PATCH 32/47] fix(Query): throw NOT_FOUND error when getting a non-existing object --- src/query.js | 15 +++++++++------ test/query.js | 9 ++++++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/query.js b/src/query.js index 1b96d7c54..fc2e20528 100644 --- a/src/query.js +++ b/src/query.js @@ -3,7 +3,7 @@ const debug = require('debug')('leancloud:query'); const Promise = require('./promise'); const AVError = require('./error'); const AVRequest = require('./request').request; -const { ensureArray } = require('./utils'); +const { ensureArray, transformFetchOptions } = require('./utils'); const requires = (value, message) => { if (value === undefined) { @@ -187,19 +187,22 @@ module.exports = function(AV) { throw errorObject; } - var self = this; - - var obj = self._newObject(); + var obj = this._newObject(); obj.id = objectId; - var queryJSON = self.toJSON(); + var queryJSON = this.toJSON(); var fetchOptions = {}; if (queryJSON.keys) fetchOptions.keys = queryJSON.keys; if (queryJSON.include) fetchOptions.include = queryJSON.include; if (queryJSON.includeACL) fetchOptions.includeACL = queryJSON.includeACL; - return obj.fetch(fetchOptions, options); + return AVRequest('classes', this.className, objectId, 'GET', transformFetchOptions(fetchOptions), options) + .then((response) => { + if (_.isEmpty(response)) throw new AVError(AVError.OBJECT_NOT_FOUND, 'Object not found.'); + obj._finishFetch(obj.parse(response), true); + return obj; + }); }, /** diff --git a/test/query.js b/test/query.js index 1df890ad8..12705b7b7 100644 --- a/test/query.js +++ b/test/query.js @@ -41,6 +41,10 @@ describe('Queries', function () { }).to.throwError(); }); + it('should throw when object not exists', function () { + query = new AV.Query(GameScore); + return query.get('123').should.be.rejectedWith(/Object not found/); + }); }); @@ -287,12 +291,11 @@ describe('Queries', function () { var userQ = new AV.Query('Person'); - return userQ.get('52f9bea1e4b035debf88b730').then(function (p) { - p.relation('likes').query(); + return userQ.first().then(function (p) { + return p.relation('likes').query().count(); // p.relation('likes').query().count().then(function(c){ // debug(c) // }) - debug(p); }); // userQ.first().then(function(p){ From 66cabe8c4f0a2e7a90e826bd07b7a09959f4ebb7 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Fri, 19 May 2017 10:59:37 +0800 Subject: [PATCH 33/47] chore(release): v2.4.0 --- bower.json | 2 +- changelog.md | 7 +++++++ package.json | 2 +- src/version.js | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 75cb6caa9..75cf43129 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.3.2", + "version": "2.4.0", "homepage": "https://github.com/leancloud/javascript-sdk", "authors": [ "LeanCloud " diff --git a/changelog.md b/changelog.md index 012859f00..ede22000e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,10 @@ +# 2.4.0 (2017-05-11) +### Bug Fixes +* **可能导致不兼容** 修复了 `Query#get` 方法在目标对象不存在的情况下会返回一个没有数据的 `AV.Object` 实例的问题,现在该方法会正确地抛出 `Object not found` 异常。这个问题是在 2.0.0 版本中引入的。 + +### Features +* 增加了 `Conversation#broadcast` 方法用于广播系统消息 + ## 2.3.2 (2017-05-12) ### Bug Fixes * 修复了获取图形验证码会导致栈溢出的问题。 diff --git a/package.json b/package.json index 6cae3d5b9..a5d8000eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.3.2", + "version": "2.4.0", "main": "./dist/node/index.js", "description": "LeanCloud JavaScript SDK.", "repository": { diff --git a/src/version.js b/src/version.js index 1ee9e0d21..dcb2317e3 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -module.exports = '2.3.2'; +module.exports = '2.4.0'; From 54baa5c3b99b91c90e6433c4e52da9a62131801c Mon Sep 17 00:00:00 2001 From: Lee Yeh Date: Wed, 31 May 2017 16:41:56 +0800 Subject: [PATCH 34/47] fix(Role): stop complaint about defaultACL when query Roles (#484) --- src/query.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/query.js b/src/query.js index fc2e20528..cbbc9514e 100644 --- a/src/query.js +++ b/src/query.js @@ -241,13 +241,13 @@ module.exports = function(AV) { }, _newObject: function(response){ - var obj; if (response && response.className) { - obj = new AV.Object(response.className); - } else { - obj = new this.objectClass(); + return new AV.Object(response.className); } - return obj; + if (this.objectClass === AV.Role) { + return new AV.Role(undefined, undefined, /* noDefaultACL */ true); + } + return new this.objectClass(); }, _createRequest(params = this.toJSON(), options) { if (JSON.stringify(params).length > 2000) { From 38efdfb84cf69405a7f6794ccb3fcdc16bb18180 Mon Sep 17 00:00:00 2001 From: Lee Yeh Date: Thu, 1 Jun 2017 11:38:10 +0800 Subject: [PATCH 35/47] feat(Status): Follower/Followee support attributes (#485) --- src/user.js | 40 ++++++++++++++++++++++++-------------- storage.d.ts | 52 +++++++++++++++++++++++++++++++------------------- test/status.js | 14 +++++++++++++- 3 files changed, 71 insertions(+), 35 deletions(-) diff --git a/src/user.js b/src/user.js index 5267199e2..dad789d65 100644 --- a/src/user.js +++ b/src/user.js @@ -376,44 +376,56 @@ module.exports = function(AV) { /** * Follow a user * @since 0.3.0 - * @param {AV.User | String} target The target user or user's objectId to follow. - * @param {AuthOptions} options + * @param {Object | AV.User | String} options if an AV.User or string is given, it will be used as the target user. + * @param {AV.User | String} options.user The target user or user's objectId to follow. + * @param {Object} [options.attributes] key-value attributes dictionary to be used as + * conditions of followerQuery/followeeQuery. + * @param {AuthOptions} [authOptions] */ - follow: function(target, options){ + follow: function(options, authOptions){ if(!this.id){ throw new Error('Please signin.'); } - if(!target){ - throw new Error('Invalid target user.'); + let user; + let attributes; + if (options.user) { + user = options.user; + attributes = options.attributes; + } else { + user = options; } - var userObjectId = _.isString(target) ? target: target.id; + var userObjectId = _.isString(user) ? user: user.id; if(!userObjectId){ throw new Error('Invalid target user.'); } var route = 'users/' + this.id + '/friendship/' + userObjectId; - var request = AVRequest(route, null, null, 'POST', null, options); + var request = AVRequest(route, null, null, 'POST', AV._encode(attributes), authOptions); return request; }, /** * Unfollow a user. * @since 0.3.0 - * @param {AV.User | String} target The target user or user's objectId to unfollow. - * @param {AuthOptions} options + * @param {Object | AV.User | String} options if an AV.User or string is given, it will be used as the target user. + * @param {AV.User | String} options.user The target user or user's objectId to unfollow. + * @param {AuthOptions} [authOptions] */ - unfollow: function(target, options){ + unfollow: function(options, authOptions){ if(!this.id){ throw new Error('Please signin.'); } - if(!target){ - throw new Error('Invalid target user.'); + let user; + if (options.user) { + user = options.user; + } else { + user = options; } - var userObjectId = _.isString(target) ? target: target.id; + var userObjectId = _.isString(user) ? user : user.id; if(!userObjectId){ throw new Error('Invalid target user.'); } var route = 'users/' + this.id + '/friendship/' + userObjectId; - var request = AVRequest(route, null, null, 'DELETE', null, options); + var request = AVRequest(route, null, null, 'DELETE', null, authOptions); return request; }, diff --git a/storage.d.ts b/storage.d.ts index 121073c8b..dc9eba981 100644 --- a/storage.d.ts +++ b/storage.d.ts @@ -478,6 +478,8 @@ declare namespace AV { interface GetOptions extends AuthOptions { } } + class FriendShipQuery extends Query {} + /** * Represents a Role on the AV server. Roles represent groupings of * Users for the purposes of granting permissions (e.g. specifying an ACL @@ -513,28 +515,32 @@ declare namespace AV { export class User extends Object { static current(): User; - static signUp(username: string, password: string, attrs: any, options?: AuthOptions): Promise; - static logIn(username: string, password: string, options?: AuthOptions): Promise; - static logOut(): Promise; - static become(sessionToken: string, options?: AuthOptions): Promise; - - static loginWithWeapp(): Promise; - static logInWithMobilePhone(mobilePhone: string, password: string, options?: AuthOptions): Promise; - static logInWithMobilePhoneSmsCode(mobilePhone: string, smsCode: string, options?: AuthOptions): Promise; - static signUpOrlogInWithAuthData(data: any, platform: string, options?: AuthOptions): Promise; - static signUpOrlogInWithMobilePhone(mobilePhoneNumber: string, smsCode: string, attributes?: any, options?: AuthOptions): Promise; - static requestEmailVerify(email: string, options?: AuthOptions): Promise; + static signUp(username: string, password: string, attrs: any, options?: AuthOptions): Promise; + static logIn(username: string, password: string, options?: AuthOptions): Promise; + static logOut(): Promise; + static become(sessionToken: string, options?: AuthOptions): Promise; + + static loginWithWeapp(): Promise; + static logInWithMobilePhone(mobilePhone: string, password: string, options?: AuthOptions): Promise; + static logInWithMobilePhoneSmsCode(mobilePhone: string, smsCode: string, options?: AuthOptions): Promise; + static signUpOrlogInWithAuthData(data: any, platform: string, options?: AuthOptions): Promise; + static signUpOrlogInWithMobilePhone(mobilePhoneNumber: string, smsCode: string, attributes?: any, options?: AuthOptions): Promise; + static requestEmailVerify(email: string, options?: AuthOptions): Promise; static requestLoginSmsCode(mobilePhoneNumber: string, options?: SMSAuthOptions): Promise; static requestMobilePhoneVerify(mobilePhoneNumber: string, options?: SMSAuthOptions): Promise; - static requestPasswordReset(email: string, options?: AuthOptions): Promise; + static requestPasswordReset(email: string, options?: AuthOptions): Promise; static requestPasswordResetBySmsCode(mobilePhoneNumber: string, options?: SMSAuthOptions): Promise; - static resetPasswordBySmsCode(code: string, password: string, options?: AuthOptions): Promise; - static verifyMobilePhone(code: string, options?: AuthOptions): Promise; - signUp(attrs?: any, options?: AuthOptions): Promise; - logIn(options?: AuthOptions): Promise; - linkWithWeapp(): Promise; - fetch(options?: AuthOptions): Promise; - save(arg1?: any, arg2?: any, arg3?: any): Promise; + static resetPasswordBySmsCode(code: string, password: string, options?: AuthOptions): Promise; + static verifyMobilePhone(code: string, options?: AuthOptions): Promise; + + static followerQuery(userObjectId: string): FriendShipQuery; + static followeeQuery(userObjectId: string): FriendShipQuery; + + signUp(attrs?: any, options?: AuthOptions): Promise; + logIn(options?: AuthOptions): Promise; + linkWithWeapp(): Promise; + fetch(options?: AuthOptions): Promise; + save(arg1?: any, arg2?: any, arg3?: any): Promise; isAuthenticated(): Promise; isCurrent(): boolean; @@ -553,7 +559,13 @@ declare namespace AV { refreshSessionToken(options?: AuthOptions): Promise; getRoles(options?: AuthOptions): Promise; - + + follow(user: User|string, authOptions?: AuthOptions): Promise; + follow(options: { user: User|string, attributes?: Object}, authOptions?: AuthOptions): Promise; + unfollow(user: User|string, authOptions?: AuthOptions): Promise; + unfollow(options: { user: User|string }, authOptions?: AuthOptions): Promise; + followerQuery(): FriendShipQuery; + followeeQuery(): FriendShipQuery; } export class Captcha { diff --git a/test/status.js b/test/status.js index cb342e84a..f0f806ea3 100644 --- a/test/status.js +++ b/test/status.js @@ -64,8 +64,15 @@ describe("AV.Status",function(){ describe("Status guide test.", function(){ it("should follow/unfollow successfully.", function(){ - return AV.User.current().follow(targetUser).then(function(){ + return AV.User.current().follow({ + user: targetUser, + attributes: { + group: 1, + position: new AV.GeoPoint(0,0), + }, + }).then(function(){ var query = AV.User.current().followeeQuery(); + query.equalTo('group', 1); query.include('followee'); return query.find(); }).then(function(followees){ @@ -73,6 +80,11 @@ describe("AV.Status",function(){ expect(followees.length).to.be(1); expect(followees[0].id).to.be(targetUser); expect(followees[0].get('username')).to.be('leeyeh'); + var query = AV.User.current().followeeQuery(); + query.equalTo('group', 0); + return query.find(); + }).then(function(followees){ + expect(followees.length).to.be(0); return AV.User.current().unfollow(targetUser); }).then(function(){ var query = AV.User.current().followeeQuery(); From 6a4377867f9c978c41d7cf0d88f08e9a136c8fcb Mon Sep 17 00:00:00 2001 From: leeyeh Date: Thu, 1 Jun 2017 11:54:13 +0800 Subject: [PATCH 36/47] chore(release): v2.5.0 --- bower.json | 2 +- changelog.md | 9 ++++++++- package.json | 2 +- src/version.js | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index 75cf43129..6c0aeac54 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.4.0", + "version": "2.5.0", "homepage": "https://github.com/leancloud/javascript-sdk", "authors": [ "LeanCloud " diff --git a/changelog.md b/changelog.md index ede22000e..9a5a2b015 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,11 @@ -# 2.4.0 (2017-05-11) +# 2.5.0 (2017-06-01) +### Bug Fixes +* 修复了查询 `Role` 时错误的打印了 deprecation 警告的问题 + +### Features +* `User#follow` 增加了一种重载,现在可以通过 `options.attributes` 参数为创建的 `Follower` 与 `Followee` 增加自定义属性,方便之后通过 `User#followerQuery` 与 `User#followerQuery` 进行查询。 + +# 2.4.0 (2017-05-19) ### Bug Fixes * **可能导致不兼容** 修复了 `Query#get` 方法在目标对象不存在的情况下会返回一个没有数据的 `AV.Object` 实例的问题,现在该方法会正确地抛出 `Object not found` 异常。这个问题是在 2.0.0 版本中引入的。 diff --git a/package.json b/package.json index a5d8000eb..9522a0899 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.4.0", + "version": "2.5.0", "main": "./dist/node/index.js", "description": "LeanCloud JavaScript SDK.", "repository": { diff --git a/src/version.js b/src/version.js index dcb2317e3..de4c49e3b 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -module.exports = '2.4.0'; +module.exports = '2.5.0'; From c6bb69f4e7b1f8539394e8c13ca8b9a25a8757cd Mon Sep 17 00:00:00 2001 From: leeyeh Date: Wed, 28 Jun 2017 15:19:33 +0800 Subject: [PATCH 37/47] fix(Status): prioritize AuthOptions over currentUser prevent currentUser warning when used with AuthOptions in node. --- src/status.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/status.js b/src/status.js index a23d58b2e..d5f6e21f6 100644 --- a/src/status.js +++ b/src/status.js @@ -1,9 +1,15 @@ const _ = require('underscore'); const AVRequest = require('./request').request; +const { getSessionToken } = require('./utils'); module.exports = function(AV) { - const getUser = (options = {}) => AV.User.currentAsync() - .then(currUser => currUser || AV.User._fetchUserBySessionToken(options.sessionToken)); + const getUser = (options = {}) => { + const sessionToken = getSessionToken(options); + if (sessionToken) { + return AV.User._fetchUserBySessionToken(getSessionToken(options)); + } + return AV.User.currentAsync(); + }; const getUserPointer = options => getUser(options) .then(currUser => AV.Object.createWithoutData('_User', currUser.id)._toPointer()); @@ -90,7 +96,7 @@ module.exports = function(AV) { * }); */ send: function(options = {}){ - if(!options.sessionToken && !AV.User.current()) { + if(!getSessionToken(options) && !AV.User.current()) { throw new Error('Please signin an user.'); } if(!this.query){ @@ -146,7 +152,7 @@ module.exports = function(AV) { * }); */ AV.Status.sendStatusToFollowers = function(status, options = {}) { - if(!options.sessionToken && !AV.User.current()){ + if(!getSessionToken(options) && !AV.User.current()){ throw new Error('Please signin an user.'); } return getUserPointer(options).then(currUser => { @@ -189,7 +195,7 @@ module.exports = function(AV) { * }); */ AV.Status.sendPrivateStatus = function(status, target, options = {}) { - if(!options.sessionToken && !AV.User.current()){ + if(!getSessionToken(options) && !AV.User.current()){ throw new Error('Please signin an user.'); } if(!target){ @@ -236,7 +242,7 @@ module.exports = function(AV) { */ AV.Status.countUnreadStatuses = function(owner, inboxType = 'default', options = {}){ if (!_.isString(inboxType)) options = inboxType; - if(!options.sessionToken && owner == null && !AV.User.current()) { + if(!getSessionToken(options) && owner == null && !AV.User.current()) { throw new Error('Please signin an user or pass the owner objectId.'); } return getUser(options).then(owner => { @@ -263,7 +269,7 @@ module.exports = function(AV) { */ AV.Status.resetUnreadCount = function(owner, inboxType = 'default', options = {}){ if (!_.isString(inboxType)) options = inboxType; - if(!options.sessionToken && owner == null && !AV.User.current()) { + if(!getSessionToken(options) && owner == null && !AV.User.current()) { throw new Error('Please signin an user or pass the owner objectId.'); } return getUser(options).then(owner => { From 4b2a29de8fe4023a840d0a834b3692edbfbea364 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Wed, 28 Jun 2017 11:16:38 +0800 Subject: [PATCH 38/47] chore(release): v2.5.1 --- .travis.yml | 3 +-- bower.json | 2 +- changelog.md | 5 +++++ package.json | 2 +- script/release.sh | 2 +- src/version.js | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index fcda6a710..142b05d4b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,6 @@ script: - npm test && codecov - npm run build after_success: - - if [[ "$TRAVIS_BRANCH" == "master" ]] && [[ "${TRAVIS_PULL_REQUEST}" = "false" ]]; then + - if [[ "$TRAVIS_BRANCH" == "v2" ]] && [[ "${TRAVIS_PULL_REQUEST}" = "false" ]]; then ./script/release.sh; - ./script/deploy.sh; fi diff --git a/bower.json b/bower.json index 6c0aeac54..47d088307 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.5.0", + "version": "2.5.1", "homepage": "https://github.com/leancloud/javascript-sdk", "authors": [ "LeanCloud " diff --git a/changelog.md b/changelog.md index 9a5a2b015..4b1562d9a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,8 @@ +## 2.5.1 (2017-06-28) +### Bug Fixes +* 修复了应用内社交模块对 AuthOptions 支持不完整的问题 +* 修复了应用内社交模块在云引擎中使用时错误的打印了 `AV.User.currentAsync` 方法不可用警告的问题 + # 2.5.0 (2017-06-01) ### Bug Fixes * 修复了查询 `Role` 时错误的打印了 deprecation 警告的问题 diff --git a/package.json b/package.json index 9522a0899..014745c7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.5.0", + "version": "2.5.1", "main": "./dist/node/index.js", "description": "LeanCloud JavaScript SDK.", "repository": { diff --git a/script/release.sh b/script/release.sh index 5163a3343..935b8c504 100755 --- a/script/release.sh +++ b/script/release.sh @@ -8,6 +8,6 @@ test "$(git config user.name)" = '' && ( ) git add dist -f; git commit -m "chore(build): build ${REV} [skip ci]"; -git push -qf https://${TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git ${BRANCH}:dist > /dev/null 2>&1; +git push -qf https://${TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git ${BRANCH}:v2-dist > /dev/null 2>&1; git reset HEAD~1; echo "done."; diff --git a/src/version.js b/src/version.js index de4c49e3b..c6070b4e9 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -module.exports = '2.5.0'; +module.exports = '2.5.1'; From 7a48c698c00370c6983997052e18c8f761521ba7 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Mon, 3 Jul 2017 21:35:18 +0800 Subject: [PATCH 39/47] fix(User): ensure _mergeMagicFields returns data --- src/object.js | 2 +- src/user.js | 2 +- test/object.js | 13 +++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/object.js b/src/object.js index 12122ab5f..4666137c2 100644 --- a/src/object.js +++ b/src/object.js @@ -1005,7 +1005,7 @@ module.exports = function(AV) { output[key] = AV._parseDate(output[key]); } }); - if (!output.updatedAt) { + if (output.createdAt && !output.updatedAt) { output.updatedAt = output.createdAt; } return output; diff --git a/src/user.js b/src/user.js index dad789d65..34afa513f 100644 --- a/src/user.js +++ b/src/user.js @@ -46,7 +46,7 @@ module.exports = function(AV) { this._sessionToken = attrs.sessionToken; delete attrs.sessionToken; } - AV.User.__super__._mergeMagicFields.call(this, attrs); + return AV.User.__super__._mergeMagicFields.call(this, attrs); }, /** diff --git a/test/object.js b/test/object.js index ea1406f57..613b6b863 100644 --- a/test/object.js +++ b/test/object.js @@ -107,6 +107,19 @@ describe('Objects', function(){ parsedGameScore.get('score').should.eql(gameScore.get('score')); }); + it('toJSON and parse (User)', () => { + const user = new AV.Object.createWithoutData('_User', 'objectId'); + user.set('id', 'id'); + user.set('score', 20); + const json = user.toJSON(); + json.objectId.should.eql(user.id); + json.score.should.eql(user.get('score')); + const parsedUser = new AV.User(json, { parse: true }); + parsedUser.id.should.eql(user.id); + parsedUser.get('id').should.eql(user.get('id')); + parsedUser.get('score').should.eql(user.get('score')); + }); + it('should create a User',function(){ var User = AV.Object.extend("User"); var u = new User(); From 6bb7200db159d30f9fe30796b575c1a878b3ff45 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Mon, 3 Jul 2017 21:40:41 +0800 Subject: [PATCH 40/47] chore(release): v2.5.2 --- bower.json | 2 +- changelog.md | 4 ++++ package.json | 2 +- src/version.js | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 47d088307..4a11d75ac 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.5.1", + "version": "2.5.2", "homepage": "https://github.com/leancloud/javascript-sdk", "authors": [ "LeanCloud " diff --git a/changelog.md b/changelog.md index 4b1562d9a..ccc00fe1c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,7 @@ +## 2.5.2 (2017-07-03) +### Bug Fixes +* 修复了使用 `new AV.User(data, { parse: true })` 方式构造的 User 没有数据的问题。 + ## 2.5.1 (2017-06-28) ### Bug Fixes * 修复了应用内社交模块对 AuthOptions 支持不完整的问题 diff --git a/package.json b/package.json index 014745c7d..49f2cb213 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.5.1", + "version": "2.5.2", "main": "./dist/node/index.js", "description": "LeanCloud JavaScript SDK.", "repository": { diff --git a/src/version.js b/src/version.js index c6070b4e9..7f9736032 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -module.exports = '2.5.1'; +module.exports = '2.5.2'; From 0280f6dbb7a9af154e50ddb8d3ecb52b41d76161 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Tue, 1 Aug 2017 16:45:13 +0800 Subject: [PATCH 41/47] fix: use polyfilled Promise --- src/cloudfunction.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cloudfunction.js b/src/cloudfunction.js index f9c679cf0..4b7e6bf48 100644 --- a/src/cloudfunction.js +++ b/src/cloudfunction.js @@ -1,5 +1,6 @@ const _ = require('underscore'); const AVRequest = require('./request').request; +const Promise = require('./promise'); module.exports = function(AV) { /** From eef9235eeb8b145d95c76fad13a08c381125582b Mon Sep 17 00:00:00 2001 From: Eric Zeng Date: Thu, 13 Jul 2017 12:03:50 +0800 Subject: [PATCH 42/47] fix(ts): correct and add a overwrite for Object#save --- storage.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/storage.d.ts b/storage.d.ts index dc9eba981..af9ce7965 100644 --- a/storage.d.ts +++ b/storage.d.ts @@ -280,7 +280,8 @@ declare namespace AV { previousAttributes(): any; relation(attr: string): Relation; remove(attr: string, item: any): any; - save(options?: Object.SaveOptions, arg2?: any, arg3?: any): Promise; + save(attrs?: object | null, options?: Object.SaveOptions): Promise; + save(key: string, value: any, options?: Object.SaveOptions): Promise; set(key: string, value: any, options?: Object.SetOptions): boolean; setACL(acl: ACL, options?: Object.SetOptions): boolean; unset(attr: string, options?: Object.SetOptions): any; @@ -540,7 +541,6 @@ declare namespace AV { logIn(options?: AuthOptions): Promise; linkWithWeapp(): Promise; fetch(options?: AuthOptions): Promise; - save(arg1?: any, arg2?: any, arg3?: any): Promise; isAuthenticated(): Promise; isCurrent(): boolean; @@ -559,7 +559,7 @@ declare namespace AV { refreshSessionToken(options?: AuthOptions): Promise; getRoles(options?: AuthOptions): Promise; - + follow(user: User|string, authOptions?: AuthOptions): Promise; follow(options: { user: User|string, attributes?: Object}, authOptions?: AuthOptions): Promise; unfollow(user: User|string, authOptions?: AuthOptions): Promise; @@ -751,7 +751,7 @@ declare namespace AV { function run(name: string, data?: any, options?: AuthOptions): Promise; function requestSmsCode(data: string|{ mobilePhoneNumber: string, template?: string, sign?: string }, options?: SMSAuthOptions): Promise; function verifySmsCode(code: string, phone: string): Promise; - function requestCaptcha(options?: CaptchaOptions, authOptions?: AuthOptions): Promise; + function requestCaptcha(options?: CaptchaOptions, authOptions?: AuthOptions): Promise; function verifyCaptcha(code: string, captchaToken: string): Promise; } From 5f66abe3fec3404d6aeed38010ef3539654d5dd3 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Tue, 1 Aug 2017 16:47:19 +0800 Subject: [PATCH 43/47] fix(ts): validate d.ts beforerunning test fixed #497 --- package.json | 5 ++++- storage.d.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 49f2cb213..dfe6ee2f0 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "url": "https://github.com/leancloud/javascript-sdk" }, "scripts": { - "test": "NODE_ENV=test nyc --reporter lcov --reporter text mocha --timeout 300000 test/index.js", + "lint": "tsc storage.d.ts", + "test": "npm run lint && npm run test:node", + "test:node": "NODE_ENV=test nyc --reporter lcov --reporter text mocha --timeout 300000 test/index.js", "docs": "jsdoc src README.md package.json -d docs -c .jsdocrc.json", "build:node": "gulp babel-node", "build:browser": "CLIENT_PLATFORM=Browser webpack --config webpack/browser.js", @@ -50,6 +52,7 @@ "nyc": "^8.1.0", "should": "^11.1.0", "uglify-js": "git+https://github.com/Swaagie/UglifyJS2.git#fcb4f2f21584dc5b21af4c10e17733e1686135e4", + "typescript": "^2.4.1", "weapp-polyfill": "^1.1.0", "webpack": "^2.2.0-rc.3" }, diff --git a/storage.d.ts b/storage.d.ts index af9ce7965..9f8ba4f78 100644 --- a/storage.d.ts +++ b/storage.d.ts @@ -1,3 +1,7 @@ +interface IteratorResult { + done: boolean; + value: T; +} interface AsyncIterator { next(): Promise> } @@ -540,7 +544,6 @@ declare namespace AV { signUp(attrs?: any, options?: AuthOptions): Promise; logIn(options?: AuthOptions): Promise; linkWithWeapp(): Promise; - fetch(options?: AuthOptions): Promise; isAuthenticated(): Promise; isCurrent(): boolean; From 7bd60349fca9a079c8674307e6d486f784a39281 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Fri, 28 Jul 2017 18:48:09 +0800 Subject: [PATCH 44/47] fix(ts): detail SaveOptions and FileSaveOptions --- storage.d.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/storage.d.ts b/storage.d.ts index 9f8ba4f78..1faecc719 100644 --- a/storage.d.ts +++ b/storage.d.ts @@ -38,6 +38,14 @@ declare namespace AV { ttl?: number; } + interface FileSaveOptions extends AuthOptions { + onprogress?: (event: { + loaded: number, + total: number, + percent: number, + }) => void; + } + export interface WaitOption { /** * Set to true to wait for the server to confirm success @@ -151,7 +159,7 @@ declare namespace AV { name(): string; ownerId(): string; url(): string; - save(options?: AuthOptions): Promise; + save(options?: FileSaveOptions): Promise; setACL(acl?: ACL): any; size(): any; thumbnailURL(width: number, height: number): string; @@ -298,7 +306,10 @@ declare namespace AV { interface DestroyAllOptions extends AuthOptions { } - interface SaveOptions extends AuthOptions, SilentOption, WaitOption { } + interface SaveOptions extends AuthOptions, SilentOption, WaitOption { + fetchWhenSave?: boolean, + where?: Query, + } interface SaveAllOptions extends AuthOptions { } From a3994342760e277b88fbacc7f7153efad30ccd5e Mon Sep 17 00:00:00 2001 From: leeyeh Date: Tue, 1 Aug 2017 18:56:17 +0800 Subject: [PATCH 45/47] chore(release): v2.5.3 --- bower.json | 2 +- changelog.md | 4 ++++ package.json | 2 +- src/version.js | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 4a11d75ac..eb8cb4c8c 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.5.2", + "version": "2.5.3", "homepage": "https://github.com/leancloud/javascript-sdk", "authors": [ "LeanCloud " diff --git a/changelog.md b/changelog.md index ccc00fe1c..9645a0116 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,7 @@ +## 2.5.3 (2017-08-01) +### Bug Fixes +* 修复了一些 TypeScript 定义文件的问题。 + ## 2.5.2 (2017-07-03) ### Bug Fixes * 修复了使用 `new AV.User(data, { parse: true })` 方式构造的 User 没有数据的问题。 diff --git a/package.json b/package.json index dfe6ee2f0..2431e055c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.5.2", + "version": "2.5.3", "main": "./dist/node/index.js", "description": "LeanCloud JavaScript SDK.", "repository": { diff --git a/src/version.js b/src/version.js index 7f9736032..b6acb2616 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -module.exports = '2.5.2'; +module.exports = '2.5.3'; From c5aa78ba5bdf998bab1b4dbfd999db72546cabe9 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Mon, 25 Sep 2017 17:42:21 +0800 Subject: [PATCH 46/47] fix(inboxQuery): port _createRequest from Query to prevent URI too long --- src/query.js | 2 +- src/status.js | 24 +++++++++++++++++++++--- test/status.js | 5 +++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/query.js b/src/query.js index cbbc9514e..2556a889b 100644 --- a/src/query.js +++ b/src/query.js @@ -250,7 +250,7 @@ module.exports = function(AV) { return new this.objectClass(); }, _createRequest(params = this.toJSON(), options) { - if (JSON.stringify(params).length > 2000) { + if (encodeURIComponent(JSON.stringify(params)).length > 2000) { const body = { requests: [{ method: 'GET', diff --git a/src/status.js b/src/status.js index d5f6e21f6..cd50a7426 100644 --- a/src/status.js +++ b/src/status.js @@ -313,9 +313,27 @@ module.exports = function(AV) { _newObject: function(){ return new AV.Status(); }, - _createRequest: function(params, options){ - return AVRequest('subscribe/statuses', null, null, 'GET', - params || this.toJSON(), options); + _createRequest: function (params = this.toJSON(), options) { + if (encodeURIComponent(JSON.stringify(params)).length > 2000) { + const body = { + requests: [{ + method: 'GET', + path: '/1.1/subscribe/statuses', + params, + }], + }; + return AVRequest('batch', null, null, 'POST', body, options) + .then(response => { + const result = response[0]; + if (result.success) { + return result.success; + } + const error = new Error(result.error.error || 'Unknown batch error'); + error.code = result.error.code; + throw error; + }); + } + return AVRequest('subscribe/statuses', null, null, 'GET', params, options); }, diff --git a/test/status.js b/test/status.js index f0f806ea3..919412155 100644 --- a/test/status.js +++ b/test/status.js @@ -60,6 +60,11 @@ describe("AV.Status",function(){ expect(response.unread).to.be.eql(0); }); }); + it('should not cause URI too long', () => { + return AV.Status.inboxQuery(user) + .containsAll('arr', new Array(50).fill(AV.Object.createWithoutData('Todo', '5850f138128fe1006978f766'))) + .find(); + }); }); describe("Status guide test.", function(){ From 718f05f9566542c55603e0db6d22d4c6d00aed36 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Mon, 25 Sep 2017 17:46:03 +0800 Subject: [PATCH 47/47] chore(release): v2.5.4 --- bower.json | 2 +- changelog.md | 4 ++++ package.json | 2 +- src/version.js | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index eb8cb4c8c..aeea71ebf 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.5.3", + "version": "2.5.4", "homepage": "https://github.com/leancloud/javascript-sdk", "authors": [ "LeanCloud " diff --git a/changelog.md b/changelog.md index 9645a0116..1f69fc47f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,7 @@ +## 2.5.4 (2017-09-25) +### Bug Fixes +* 修复了使用应用内社交模块 `inboxQuery` 查询时可能出现 `URI too long` 异常的问题。 + ## 2.5.3 (2017-08-01) ### Bug Fixes * 修复了一些 TypeScript 定义文件的问题。 diff --git a/package.json b/package.json index 2431e055c..f8763c5df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leancloud-storage", - "version": "2.5.3", + "version": "2.5.4", "main": "./dist/node/index.js", "description": "LeanCloud JavaScript SDK.", "repository": { diff --git a/src/version.js b/src/version.js index b6acb2616..deef1b8be 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -module.exports = '2.5.3'; +module.exports = '2.5.4'; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy