1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-29 16:43:09 +03:00

WIP filtered timelines

This commit is contained in:
Matthew Hodgson
2016-08-23 14:31:47 +01:00
parent 6432a64442
commit dd5878015a
3 changed files with 209 additions and 0 deletions

119
lib/filter-component.js Normal file
View File

@@ -0,0 +1,119 @@
/*
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.
*/
"use strict";
/**
* @module filter-component
*/
function _matches_wildcard(actual_value, filter_value) {
if (filter_value.endsWith("*")) {
type_prefix = filter_value.slice(0, -1);
return actual_value.substr(0, type_prefix.length) === type_prefix;
}
else {
return actual_value === filter_value;
}
}
/**
* A FilterComponent is a section of a Filter definition which defines the
* types, rooms, senders filters etc to be applied to a particular type of resource.
*
* This is all ported from synapse's Filter object.
*/
FilterComponent = function(filter_json) {
this.filter_json = filter_json;
this.types = filter_json.types || null;
this.not_types = filter_json.not_types || [];
self.rooms = filter_json.rooms || null;
self.not_rooms = filter_json.not_rooms || [];
self.senders = filter_json.senders || null;
self.not_senders = filter_json.not_senders || [];
self.contains_url = filter_json.contains_url || null;
};
/**
* Checks with the filter component matches the given event
*/
FilterComponent.prototype.check = function(event) {
var sender = event.sender;
if (!sender) {
// Presence events have their 'sender' in content.user_id
if (event.content) {
sender = event.content.user_id;
}
}
return this.checkFields(
event.room_id,
sender,
event.type,
event.content ? event.content.url !== undefined : false,
);
};
/**
* Checks whether the filter matches the given event fields.
*/
FilterComponent.prototype.checkFields =
function(room_id, sender, event_type, contains_url) {
var literal_keys = {
"rooms": function(v) { return room_id === v; },
"senders": function(v) { return sender === v; },
"types": function(v) { return _matches_wildcard(event_type, v); },
};
Object.keys(literal_keys).forEach(function(name) {
var match_func = literal_keys[name];
var not_name = "not_" + name;
var disallowed_values = this[not_name];
if (disallowed_values.map(match_func)) {
return false;
}
var allowed_values = this[name];
if (allowed_values) {
if (!allowed_values.map(match_func)) {
return false;
}
}
});
contains_url_filter = this.filter_json.contains_url;
if (contains_url_filter !== undefined) {
if (contains_url_filter !== contains_url) {
return false;
}
}
return true;
}
};
FilterComponent.prototype.filter = function(events) {
return events.filter(this.check);
};
FilterComponent.prototype.limit = function() {
return this.filter_json.limit !== undefined ? this.filter_json.limit : 10;
};
/** The FilterComponent class */
module.exports = FilterComponent;

View File

@@ -18,6 +18,8 @@ limitations under the License.
* @module filter * @module filter
*/ */
var FilterComponent = require("./filter-component");
/** /**
* @param {Object} obj * @param {Object} obj
* @param {string} keyNesting * @param {string} keyNesting
@@ -63,6 +65,70 @@ Filter.prototype.getDefinition = function() {
*/ */
Filter.prototype.setDefinition = function(definition) { Filter.prototype.setDefinition = function(definition) {
this.definition = definition; this.definition = definition;
// This is all ported from synapse's FilterCollection()
// definitions look something like:
// {
// "room": {
// "rooms": ["!abcde:example.com"],
// "not_rooms": ["!123456:example.com"],
// "state": {
// "types": ["m.room.*"],
// "not_rooms": ["!726s6s6q:example.com"]
// },
// "timeline": {
// "limit": 10,
// "types": ["m.room.message"],
// "not_rooms": ["!726s6s6q:example.com"],
// "not_senders": ["@spam:example.com"]
// },
// "ephemeral": {
// "types": ["m.receipt", "m.typing"],
// "not_rooms": ["!726s6s6q:example.com"],
// "not_senders": ["@spam:example.com"]
// }
// },
// "presence": {
// "types": ["m.presence"],
// "not_senders": ["@alice:example.com"]
// },
// "event_format": "client",
// "event_fields": ["type", "content", "sender"]
// }
var room_filter_json = definition.room;
// consider the top level rooms/not_rooms filter
var room_filter_fields = {};
if (room_filter_json) {
if (room_filter_json.rooms) {
room_filter_fields.rooms = room_filter_json.rooms;
}
if (room_filter_json.rooms) {
room_filter_fields.not_rooms = room_filter_json.not_rooms;
}
this._include_leave = room_filter_json.include_leave || false;
}
this._room_filter = new FilterComponent(room_filter_fields);
this._room_timeline_filter = new FilterComponent(room_filter_json.timeline || {});
// don't bother porting this from synapse yet:
// this._room_state_filter = new FilterComponent(room_filter_json.state || {});
// this._room_ephemeral_filter = new FilterComponent(room_filter_json.ephemeral || {});
// this._room_account_data_filter = new FilterComponent(room_filter_json.account_data || {});
// this._presence_filter = new FilterComponent(definition.presence || {});
// this._account_data_filter = new FilterComponent(definition.account_data || {});
};
/**
* Filter the list of events based on whether they are allowed in a timeline
* based on this filter
*/
Filter.prototype.filterRoomTimeline = function(events) {
return this._room_timeline_filter.filter(this._room_filter.filter(events));
}; };
/** /**

View File

@@ -36,6 +36,7 @@ function EventTimeline(roomId) {
this._startState.paginationToken = null; this._startState.paginationToken = null;
this._endState = new RoomState(roomId); this._endState = new RoomState(roomId);
this._endState.paginationToken = null; this._endState.paginationToken = null;
this._filter = null;
this._prevTimeline = null; this._prevTimeline = null;
this._nextTimeline = null; this._nextTimeline = null;
@@ -58,6 +59,21 @@ EventTimeline.BACKWARDS = "b";
*/ */
EventTimeline.FORWARDS = "f"; EventTimeline.FORWARDS = "f";
/**
* Get the filter object this timeline is filtered on
*/
EventTimeline.prototype.getFilter = function() {
return this._filter;
}
/**
* Set the filter object this timeline is filtered on
* (passed to the server when paginating via /messages).
*/
EventTimeline.prototype.setFilter = function(filter) {
this._filter = filter;
}
/** /**
* Initialise the start and end state with the given events * Initialise the start and end state with the given events
* *
@@ -217,6 +233,14 @@ EventTimeline.prototype.setNeighbouringTimeline = function(neighbour, direction)
EventTimeline.prototype.addEvent = function(event, atStart) { EventTimeline.prototype.addEvent = function(event, atStart) {
var stateContext = atStart ? this._startState : this._endState; var stateContext = atStart ? this._startState : this._endState;
// manually filter the event if we have a filter, as currently we insert
// events incrementally only from the main /sync rather than a filtered
// /sync to avoid running multiple redundant /syncs.
if (this._filter) {
var events = this._filter.filterRoomTimeline([event]);
if (!events) return;
}
setEventMetadata(event, stateContext, atStart); setEventMetadata(event, stateContext, atStart);
// modify state // modify state