diff --git a/src/stores/GroupStore.js b/src/stores/GroupStore.js index 9dce15fb53..c3465b684c 100644 --- a/src/stores/GroupStore.js +++ b/src/stores/GroupStore.js @@ -19,6 +19,14 @@ import { groupMemberFromApiObject, groupRoomFromApiObject } from '../groups'; import FlairStore from './FlairStore'; import MatrixClientPeg from '../MatrixClientPeg'; +function parseMembersResponse(response) { + return response.chunk.map((apiMember) => groupMemberFromApiObject(apiMember)); +} + +function parseRoomsResponse(response) { + return response.chunk.map((apiRoom) => groupRoomFromApiObject(apiRoom)); +} + /** * Stores the group summary for a room and provides an API to change it and * other useful group APIs that may have an effect on the group summary. @@ -38,65 +46,68 @@ export default class GroupStore extends EventEmitter { throw new Error('GroupStore needs a valid groupId to be created'); } this.groupId = groupId; - this._summary = {}; - this._rooms = []; - this._members = []; - this._invitedMembers = []; + this._state = {}; + this._state[GroupStore.STATE_KEY.Summary] = {}; + this._state[GroupStore.STATE_KEY.GroupRooms] = []; + this._state[GroupStore.STATE_KEY.GroupMembers] = []; + this._state[GroupStore.STATE_KEY.GroupInvitedMembers] = []; this._ready = {}; + this._fetchResourcePromise = {}; + this._resourceFetcher = { + [GroupStore.STATE_KEY.Summary]: () => { + return MatrixClientPeg.get() + .getGroupSummary(this.groupId); + }, + [GroupStore.STATE_KEY.GroupRooms]: () => { + return MatrixClientPeg.get() + .getGroupRooms(this.groupId) + .then(parseRoomsResponse); + }, + [GroupStore.STATE_KEY.GroupMembers]: () => { + return MatrixClientPeg.get() + .getGroupUsers(this.groupId) + .then(parseMembersResponse); + }, + [GroupStore.STATE_KEY.GroupInvitedMembers]: () => { + return MatrixClientPeg.get() + .getGroupInvitedUsers(this.groupId) + .then(parseMembersResponse); + }, + }; + this.on('error', (err) => { console.error(`GroupStore for ${this.groupId} encountered error`, err); }); } - _fetchMembers() { - MatrixClientPeg.get().getGroupUsers(this.groupId).then((result) => { - this._members = result.chunk.map((apiMember) => { - return groupMemberFromApiObject(apiMember); - }); - this._ready[GroupStore.STATE_KEY.GroupMembers] = true; - this._notifyListeners(); - }).catch((err) => { - console.error("Failed to get group member list: " + err); - this.emit('error', err); - }); + _fetchResource(stateKey) { + // Ongoing request, ignore + if (this._fetchResourcePromise[stateKey]) return; - MatrixClientPeg.get().getGroupInvitedUsers(this.groupId).then((result) => { - this._invitedMembers = result.chunk.map((apiMember) => { - return groupMemberFromApiObject(apiMember); - }); - this._ready[GroupStore.STATE_KEY.GroupInvitedMembers] = true; + const clientPromise = this._resourceFetcher[stateKey](); + + // Indicate ongoing request + this._fetchResourcePromise[stateKey] = clientPromise; + + clientPromise.then((result) => { + this._state[stateKey] = result; + this._ready[stateKey] = true; this._notifyListeners(); }).catch((err) => { // Invited users not visible to non-members - if (err.httpStatus === 403) { + if (stateKey === GroupStore.STATE_KEY.GroupInvitedMembers && err.httpStatus === 403) { return; } - console.error("Failed to get group invited member list: " + err); - this.emit('error', err); - }); - } - _fetchSummary() { - MatrixClientPeg.get().getGroupSummary(this.groupId).then((resp) => { - this._summary = resp; - this._ready[GroupStore.STATE_KEY.Summary] = true; - this._notifyListeners(); - }).catch((err) => { + console.error("Failed to get resource " + stateKey + ":" + err); this.emit('error', err); + }).finally(() => { + // Indicate finished request, allow for future fetches + delete this._fetchResourcePromise[stateKey]; }); - } - _fetchRooms() { - MatrixClientPeg.get().getGroupRooms(this.groupId).then((resp) => { - this._rooms = resp.chunk.map((apiRoom) => { - return groupRoomFromApiObject(apiRoom); - }); - this._ready[GroupStore.STATE_KEY.GroupRooms] = true; - this._notifyListeners(); - }).catch((err) => { - this.emit('error', err); - }); + return clientPromise; } _notifyListeners() { @@ -108,10 +119,9 @@ export default class GroupStore extends EventEmitter { * immediately triggers an update to send the current state of the * store (which could be the initial state). * - * XXX: This also causes a fetch of all group data, which effectively - * causes 4 separate HTTP requests. This is bad, we should at least - * deduplicate these in order to fix: - * https://github.com/vector-im/riot-web/issues/5901 + * This also causes a fetch of all group data, which might cause + * 4 separate HTTP requests, but only said requests aren't already + * ongoing. * * @param {function} fn the function to call when the store updates. * @return {Object} tok a registration "token" with a single @@ -123,9 +133,11 @@ export default class GroupStore extends EventEmitter { this.on('update', fn); // Call to set initial state (before fetching starts) this.emit('update'); - this._fetchSummary(); - this._fetchRooms(); - this._fetchMembers(); + + this._fetchResource(GroupStore.STATE_KEY.Summary); + this._fetchResource(GroupStore.STATE_KEY.GroupRooms); + this._fetchResource(GroupStore.STATE_KEY.GroupMembers); + this._fetchResource(GroupStore.STATE_KEY.GroupInvitedMembers); // Similar to the Store of flux/utils, we return a "token" that // can be used to unregister the listener. @@ -145,90 +157,94 @@ export default class GroupStore extends EventEmitter { } getSummary() { - return this._summary; + return this._state[GroupStore.STATE_KEY.Summary]; } getGroupRooms() { - return this._rooms; + return this._state[GroupStore.STATE_KEY.GroupRooms]; } - getGroupMembers( ) { - return this._members; + getGroupMembers() { + return this._state[GroupStore.STATE_KEY.GroupMembers]; } - getGroupInvitedMembers( ) { - return this._invitedMembers; + getGroupInvitedMembers() { + return this._state[GroupStore.STATE_KEY.GroupInvitedMembers]; } getGroupPublicity() { - return this._summary.user ? this._summary.user.is_publicised : null; + return this._state[GroupStore.STATE_KEY.Summary].user ? + this._state[GroupStore.STATE_KEY.Summary].user.is_publicised : null; } isUserPrivileged() { - return this._summary.user ? this._summary.user.is_privileged : null; + return this._state[GroupStore.STATE_KEY.Summary].user ? + this._state[GroupStore.STATE_KEY.Summary].user.is_privileged : null; } addRoomToGroup(roomId, isPublic) { return MatrixClientPeg.get() .addRoomToGroup(this.groupId, roomId, isPublic) - .then(this._fetchRooms.bind(this)); + .then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupRooms)); } updateGroupRoomVisibility(roomId, isPublic) { return MatrixClientPeg.get() .updateGroupRoomVisibility(this.groupId, roomId, isPublic) - .then(this._fetchRooms.bind(this)); + .then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupRooms)); } removeRoomFromGroup(roomId) { return MatrixClientPeg.get() .removeRoomFromGroup(this.groupId, roomId) // Room might be in the summary, refresh just in case - .then(this._fetchSummary.bind(this)) - .then(this._fetchRooms.bind(this)); + .then(this._fetchResource.bind(this, GroupStore.STATE_KEY.Summary)) + .then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupRooms)); } inviteUserToGroup(userId) { return MatrixClientPeg.get().inviteUserToGroup(this.groupId, userId) - .then(this._fetchMembers.bind(this)); + .then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupInvitedMembers)); } acceptGroupInvite() { return MatrixClientPeg.get().acceptGroupInvite(this.groupId) // The user might be able to see more rooms now - .then(this._fetchRooms.bind(this)) + .then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupRooms)) // The user should now appear as a member - .then(this._fetchMembers.bind(this)); + .then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupMembers)) + // The user should now not appear as an invited member + .then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupInvitedMembers)); } addRoomToGroupSummary(roomId, categoryId) { return MatrixClientPeg.get() .addRoomToGroupSummary(this.groupId, roomId, categoryId) - .then(this._fetchSummary.bind(this)); + .then(this._fetchResource.bind(this, GroupStore.STATE_KEY.Summary)); } addUserToGroupSummary(userId, roleId) { return MatrixClientPeg.get() .addUserToGroupSummary(this.groupId, userId, roleId) - .then(this._fetchSummary.bind(this)); + .then(this._fetchResource.bind(this, GroupStore.STATE_KEY.Summary)); } removeRoomFromGroupSummary(roomId) { return MatrixClientPeg.get() .removeRoomFromGroupSummary(this.groupId, roomId) - .then(this._fetchSummary.bind(this)); + .then(this._fetchResource.bind(this, GroupStore.STATE_KEY.Summary)); } removeUserFromGroupSummary(userId) { return MatrixClientPeg.get() .removeUserFromGroupSummary(this.groupId, userId) - .then(this._fetchSummary.bind(this)); + .then(this._fetchResource.bind(this, GroupStore.STATE_KEY.Summary)); } setGroupPublicity(isPublished) { return MatrixClientPeg.get() .setGroupPublicity(this.groupId, isPublished) .then(() => { FlairStore.invalidatePublicisedGroups(MatrixClientPeg.get().credentials.userId); }) - .then(this._fetchSummary.bind(this)); + .then(this._fetchResource.bind(this, GroupStore.STATE_KEY.Summary)); } }