From 2fd9e2a98f6a3b102d53092cad8d4c884ad5f076 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Sep 2016 14:47:56 +0100 Subject: [PATCH 1/3] Pull out multi-inviting from MultiInviteDialog MultiInviteDialog would otherwise use this, but is about to go away, so it has been left. --- src/Invite.js | 15 ++++ src/utils/MultiInviter.js | 144 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 src/utils/MultiInviter.js diff --git a/src/Invite.js b/src/Invite.js index 3b52d6a1f4..de1f9bd1bb 100644 --- a/src/Invite.js +++ b/src/Invite.js @@ -15,6 +15,7 @@ limitations under the License. */ import MatrixClientPeg from './MatrixClientPeg'; +import MultiInviter from '../../../utils/MultiInviter'; const emailRegex = /^\S+@\S+\.\S+$/; @@ -43,3 +44,17 @@ export function inviteToRoom(roomId, addr) { throw new Error('Unsupported address'); } } + +/** + * Invites multiple addresses to a room + * Simpler interface to utils/MultiInviter but with + * no option to cancel. + * + * @param {roomId} The ID of the room to invite to + * @param {array} Array of strings of addresses to invite. May be matrix IDs or 3pids. + * @returns Promise + */ +export function inviteMultipleToRoom(roomId, addrs) { + this.inviter = new MultiInviter(roomId); + return inviter.invite(addrs); +} diff --git a/src/utils/MultiInviter.js b/src/utils/MultiInviter.js new file mode 100644 index 0000000000..68a0800ed7 --- /dev/null +++ b/src/utils/MultiInviter.js @@ -0,0 +1,144 @@ +/* +Copyright 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {getAddressType, inviteToRoom} from '../Invite'; +import q from 'q'; + +/** + * Invites multiple addresses to a room, handling rate limiting from the server + */ +export default class MultiInviter { + constructor(roomId) { + this.roomId = roomId; + + this.canceled = false; + this.addrs = []; + this.busy = false; + this.completionStates = {}; // State of each address (invited or error) + this.errorTexts = {}; // Textual error per address + this.deferred = null; + } + + /** + * Invite users to this room. This may only be called once per + * instance of the class. + * + * The promise is given progress when each address completes, with an + * object argument with each completed address with value either + * 'invited' or 'error'. + * + * @param {array} addresses Array of addresses to invite + * @returns {Promise} Resolved when all invitations in the queue are complete + */ + invite(addrs) { + if (this.addrs.length > 0) { + throw new Error("Already inviting/invited"); + } + this.addrs.push(...addrs); + + for (const addr of this.addrs) { + if (getAddressType(addr) === null) { + this.completionStates[addr] = 'error'; + this.errorTexts[addr] = 'Unrecognised address'; + } + } + this.deferred = q.defer(); + this._inviteMore(0); + + return this.deferred.promise; + } + + /** + * Stops inviting. Causes promises returned by invite() to be rejected. + */ + cancel() { + if (!this.busy) return; + + this._canceled = true; + this.deferred.reject(new Error('canceled')); + } + + getCompletionState(addr) { + return this.completionStates[addr]; + } + + getErrorText(addr) { + return this.errorTexts[addr]; + } + + _inviteMore(nextIndex) { + if (this._canceled) { + return; + } + + if (nextIndex == this.addrs.length) { + this.busy = false; + this.deferred.resolve(this.completionStates); + return; + } + + const addr = this.addrs[nextIndex]; + + // don't try to invite it if it's an invalid address + // (it will already be marked as an error though, + // so no need to do so again) + if (getAddressType(addr) === null) { + this._inviteMore(nextIndex + 1); + return; + } + + // don't re-invite (there's no way in the UI to do this, but + // for sanity's sake) + if (this.completionStates[addr] == 'invited') { + this._inviteMore(nextIndex + 1); + return; + } + + inviteToRoom(this.roomId, addr).then(() => { + if (this._canceled) { return; } + + this.completionStates[addr] = 'invited'; + this.deferred.notify(this.completionStates); + + this._inviteMore(nextIndex + 1); + }, (err) => { + if (this._canceled) { return; } + + let errorText; + let fatal = false; + if (err.errcode == 'M_FORBIDDEN') { + fatal = true; + errorText = 'You do not have permission to invite people to this room.'; + } else if (err.errcode == 'M_LIMIT_EXCEEDED') { + // we're being throttled so wait a bit & try again + setTimeout(() => { + this._inviteMore(nextIndex); + }, 5000); + return; + } else { + errorText = 'Unknown server error'; + } + this.completionStates[addr] = 'error'; + this.errorTexts[addr] = errorText; + this.busy = !fatal; + + if (!fatal) { + this.deferred.notify(this.completionStates); + this._inviteMore(nextIndex + 1); + } + }); + } +} From a53e00919854e87795ca4ab62d76f6f8078d11f7 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Sep 2016 14:55:16 +0100 Subject: [PATCH 2/3] Missed a brace --- src/Invite.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Invite.js b/src/Invite.js index 494d5f5662..d1debc4e8c 100644 --- a/src/Invite.js +++ b/src/Invite.js @@ -57,6 +57,7 @@ export function inviteToRoom(roomId, addr) { export function inviteMultipleToRoom(roomId, addrs) { this.inviter = new MultiInviter(roomId); return inviter.invite(addrs); +} export function isValidAddress(addr) { // Check if the addr is a valid type From 406771532d9a72b70b2f531c479fb7614bf9bf8a Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Sep 2016 14:56:54 +0100 Subject: [PATCH 3/3] Correct path for MultiInviter --- src/Invite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Invite.js b/src/Invite.js index d1debc4e8c..59e1dc41ea 100644 --- a/src/Invite.js +++ b/src/Invite.js @@ -15,7 +15,7 @@ limitations under the License. */ import MatrixClientPeg from './MatrixClientPeg'; -import MultiInviter from '../../../utils/MultiInviter'; +import MultiInviter from './utils/MultiInviter'; const emailRegex = /^\S+@\S+\.\S+$/;