diff --git a/src/base-apis.js b/src/base-apis.js index 56b7d2fbb..e3bbbd2bb 100644 --- a/src/base-apis.js +++ b/src/base-apis.js @@ -579,11 +579,26 @@ MatrixBaseApis.prototype.addRoomToGroup = function(groupId, roomId, isPublic) { }; /** - * Alias for addRoomToGroup. - * @see module:base-apis.addRoomToGroup + * Configure the visibility of a room-group association. + * @param {string} groupId + * @param {string} roomId + * @param {bool} isPublic Whether the room-group association is visible to non-members + * @return {module:client.Promise} Resolves: Empty object + * @return {module:http-api.MatrixError} Rejects: with an error response. */ -MatrixBaseApis.prototype.updateGroupRoomAssociation = -MatrixBaseApis.prototype.addRoomToGroup; +MatrixBaseApis.prototype.updateGroupRoomVisibility = function(groupId, roomId, isPublic) { + // NB: The /config API is generic but there's not much point in exposing this yet as synapse + // is the only server to implement this. In future we should consider an API that allows + // arbitrary configuration, i.e. "config/$configKey". + + const path = utils.encodeUri( + "/groups/$groupId/admin/rooms/$roomId/config/m.visibility", + {$groupId: groupId, $roomId: roomId}, + ); + return this._http.authedRequest(undefined, "PUT", path, undefined, + { type: isPublic ? "public" : "private" }, + ); +}; /** * @param {string} groupId @@ -601,15 +616,16 @@ MatrixBaseApis.prototype.removeRoomFromGroup = function(groupId, roomId) { /** * @param {string} groupId + * @param {Object} opts Additional options to send alongside the acceptance. * @return {module:client.Promise} Resolves: Empty object * @return {module:http-api.MatrixError} Rejects: with an error response. */ -MatrixBaseApis.prototype.acceptGroupInvite = function(groupId) { +MatrixBaseApis.prototype.acceptGroupInvite = function(groupId, opts = null) { const path = utils.encodeUri( "/groups/$groupId/self/accept_invite", {$groupId: groupId}, ); - return this._http.authedRequest(undefined, "PUT", path, undefined, {}); + return this._http.authedRequest(undefined, "PUT", path, undefined, opts || {}); }; /** diff --git a/src/client.js b/src/client.js index 0f1bcf0a5..37bca7ba5 100644 --- a/src/client.js +++ b/src/client.js @@ -2981,6 +2981,9 @@ MatrixClient.prototype.getTurnServers = function() { * * @param {Filter=} opts.filter The filter to apply to /sync calls. This will override * the opts.initialSyncLimit, which would normally result in a timeline limit filter. + * + * @param {Boolean=} opts.disablePresence True to perform syncing without automatically + * updating presence. */ MatrixClient.prototype.startClient = function(opts) { if (this.clientRunning) { diff --git a/src/models/room-state.js b/src/models/room-state.js index ab69e49a1..327569df2 100644 --- a/src/models/room-state.js +++ b/src/models/room-state.js @@ -268,7 +268,11 @@ RoomState.prototype.maySendRedactionForEvent = function(mxEvent, userId) { if (!member || member.membership === 'leave') return false; if (mxEvent.status || mxEvent.isRedacted()) return false; - if (mxEvent.getSender() === userId) return true; + + // The user may have been the sender, but they can't redact their own message + // if redactions are blocked. + const canRedact = this.maySendEvent("m.room.redaction", userId); + if (mxEvent.getSender() === userId) return canRedact; return this._hasSufficientPowerLevelFor('redact', member.powerLevel); }; diff --git a/src/sync.js b/src/sync.js index a7dabdf74..71fb866d5 100644 --- a/src/sync.js +++ b/src/sync.js @@ -71,6 +71,8 @@ function debuglog(...params) { * SAFELY remove events from this room. It may not be safe to remove events if * there are other references to the timelines for this room. * Default: returns false. + * @param {Boolean=} opts.disablePresence True to perform syncing without automatically + * updating presence. */ function SyncApi(client, opts) { this.client = client; @@ -545,6 +547,10 @@ SyncApi.prototype._sync = async function(syncOptions) { timeout: pollTimeout, }; + if (this.opts.disablePresence) { + qps.set_presence = "offline"; + } + if (syncToken) { qps.since = syncToken; } else { @@ -616,7 +622,7 @@ SyncApi.prototype._sync = async function(syncOptions) { } try { - await this._processSyncResponse(syncToken, data); + await this._processSyncResponse(syncToken, data, isCachedResponse); } catch(e) { // log the exception with stack if we have it, else fall back // to the plain description @@ -699,8 +705,11 @@ SyncApi.prototype._onSyncError = function(err, syncOptions) { * @param {string} syncToken the old next_batch token sent to this * sync request. * @param {Object} data The response from /sync + * @param {bool} isCachedResponse True if this response is from our local cache */ -SyncApi.prototype._processSyncResponse = async function(syncToken, data) { +SyncApi.prototype._processSyncResponse = async function( + syncToken, data, isCachedResponse, +) { const client = this.client; const self = this; @@ -779,7 +788,13 @@ SyncApi.prototype._processSyncResponse = async function(syncToken, data) { client.store.storeAccountDataEvents(events); events.forEach( function(accountDataEvent) { - if (accountDataEvent.getType() == 'm.push_rules') { + // XXX: This is awful: ignore push rules from our cached sync. We fetch the + // push rules before syncing so we actually have up-to-date ones. We do want + // to honour new push rules that come down the sync but synapse doesn't + // put new push rules in the sync stream when the base rules change, so + // if the base rules change, we do need to refresh. We therefore ignore + // the push rules in our cached sync response. + if (accountDataEvent.getType() == 'm.push_rules' && !isCachedResponse) { client.pushRules = accountDataEvent.getContent(); } client.emit("accountData", accountDataEvent);