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';