summary refs log tree commit diff
path: root/webclient/components/matrix
diff options
context:
space:
mode:
Diffstat (limited to 'webclient/components/matrix')
-rw-r--r--webclient/components/matrix/event-handler-service.js647
-rw-r--r--webclient/components/matrix/event-stream-service.js179
-rw-r--r--webclient/components/matrix/matrix-call.js607
-rw-r--r--webclient/components/matrix/matrix-filter.js146
-rw-r--r--webclient/components/matrix/matrix-phone-service.js155
-rw-r--r--webclient/components/matrix/matrix-service.js785
-rw-r--r--webclient/components/matrix/notification-service.js104
-rw-r--r--webclient/components/matrix/presence-service.js113
8 files changed, 0 insertions, 2736 deletions
diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js
deleted file mode 100644
index e63584510b..0000000000
--- a/webclient/components/matrix/event-handler-service.js
+++ /dev/null
@@ -1,647 +0,0 @@
-/*
-Copyright 2014 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';
-
-/*
-This service handles what should happen when you get an event. This service does
-not care where the event came from, it only needs enough context to be able to 
-process them. Events may be coming from the event stream, the REST API (via 
-direct GETs or via a pagination stream API), etc.
-
-Typically, this service will store events or broadcast them to any listeners
-(e.g. controllers) via $broadcast. Alternatively, it may update the $rootScope
-if typically all the $on method would do is update its own $scope.
-*/
-angular.module('eventHandlerService', [])
-.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', 'mPresence', 'notificationService',
-function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService) {
-    var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT";
-    var MSG_EVENT = "MSG_EVENT";
-    var MEMBER_EVENT = "MEMBER_EVENT";
-    var PRESENCE_EVENT = "PRESENCE_EVENT";
-    var POWERLEVEL_EVENT = "POWERLEVEL_EVENT";
-    var CALL_EVENT = "CALL_EVENT";
-    var NAME_EVENT = "NAME_EVENT";
-    var TOPIC_EVENT = "TOPIC_EVENT";
-    var RESET_EVENT = "RESET_EVENT";    // eventHandlerService has been resetted
-
-    // used for dedupping events - could be expanded in future...
-    // FIXME: means that we leak memory over time (along with lots of the rest
-    // of the app, given we never try to reap memory yet)
-    var eventMap = {};
-
-    $rootScope.presence = {};
-
-    var initialSyncDeferred;
-
-    var reset = function() {
-        initialSyncDeferred = $q.defer();
-
-        $rootScope.events = {
-            rooms: {} // will contain roomId: { messages:[], members:{userid1: event} }
-        };
-
-        $rootScope.presence = {};
-
-        eventMap = {};
-    };
-    reset();
-
-    var initRoom = function(room_id, room) {
-        if (!(room_id in $rootScope.events.rooms)) {
-            console.log("Creating new rooms entry for " + room_id);
-            $rootScope.events.rooms[room_id] = {
-                room_id: room_id,
-                messages: [],
-                members: {},
-                // Pagination information
-                pagination: {
-                    earliest_token: "END"   // how far back we've paginated
-                }
-            };
-        }
-
-        if (room) { // we got an existing room object from initialsync, seemingly.
-            // Report all other metadata of the room object (membership, inviter, visibility, ...)
-            for (var field in room) {
-                if (!room.hasOwnProperty(field)) continue;
-
-                if (-1 === ["room_id", "messages", "state"].indexOf(field)) { // why indexOf - why not ===? --Matthew
-                    $rootScope.events.rooms[room_id][field] = room[field];
-                }
-            }
-            $rootScope.events.rooms[room_id].membership = room.membership;
-        }
-    };
-
-    var resetRoomMessages = function(room_id) {
-        if ($rootScope.events.rooms[room_id]) {
-            $rootScope.events.rooms[room_id].messages = [];
-        }
-    };
-    
-    // Generic method to handle events data
-    var handleRoomDateEvent = function(event, isLiveEvent, addToRoomMessages) {
-        // Add topic changes as if they were a room message
-        if (addToRoomMessages) {
-            if (isLiveEvent) {
-                $rootScope.events.rooms[event.room_id].messages.push(event);
-            }
-            else {
-                $rootScope.events.rooms[event.room_id].messages.unshift(event);
-            }
-        }
-
-        // live events always update, but non-live events only update if the
-        // ts is later.
-        var latestData = true;
-        if (!isLiveEvent) {
-            var eventTs = event.origin_server_ts;
-            var storedEvent = $rootScope.events.rooms[event.room_id][event.type];
-            if (storedEvent) {
-                if (storedEvent.origin_server_ts > eventTs) {
-                    // ignore it, we have a newer one already.
-                    latestData = false;
-                }
-            }
-        }
-        if (latestData) {
-            $rootScope.events.rooms[event.room_id][event.type] = event;         
-        }
-    };
-    
-    var handleRoomCreate = function(event, isLiveEvent) {
-        // For now, we do not use the event data. Simply signal it to the app controllers
-        $rootScope.$broadcast(ROOM_CREATE_EVENT, event, isLiveEvent);
-    };
-
-    var handleRoomAliases = function(event, isLiveEvent) {
-        matrixService.createRoomIdToAliasMapping(event.room_id, event.content.aliases[0]);
-    };
-
-    var handleMessage = function(event, isLiveEvent) {
-        // Check for empty event content
-        var hasContent = false;
-        for (var prop in event.content) {
-            hasContent = true;
-            break;
-        }
-        if (!hasContent) {
-            // empty json object is a redacted event, so ignore.
-            return;
-        }
-
-        if (isLiveEvent) {
-            if (event.user_id === matrixService.config().user_id &&
-                (event.content.msgtype === "m.text" || event.content.msgtype === "m.emote") ) {
-                // Assume we've already echoed it. So, there is a fake event in the messages list of the room
-                // Replace this fake event by the true one
-                var index = getRoomEventIndex(event.room_id, event.event_id);
-                if (index) {
-                    $rootScope.events.rooms[event.room_id].messages[index] = event;
-                }
-                else {
-                    $rootScope.events.rooms[event.room_id].messages.push(event);
-                }
-            }
-            else {
-                $rootScope.events.rooms[event.room_id].messages.push(event);
-            }
-            
-            if (window.Notification && event.user_id != matrixService.config().user_id) {
-                var shouldBing = notificationService.containsBingWord(
-                    matrixService.config().user_id,
-                    matrixService.config().display_name,
-                    matrixService.config().bingWords,
-                    event.content.body
-                );
-
-                // Ideally we would notify only when the window is hidden (i.e. document.hidden = true).
-                //
-                // However, Chrome on Linux and OSX currently returns document.hidden = false unless the window is
-                // explicitly showing a different tab.  So we need another metric to determine hiddenness - we
-                // simply use idle time.  If the user has been idle enough that their presence goes to idle, then
-                // we also display notifs when things happen.
-                //
-                // This is far far better than notifying whenever anything happens anyway, otherwise you get spammed
-                // to death with notifications when the window is in the foreground, which is horrible UX (especially
-                // if you have not defined any bingers and so get notified for everything).
-                var isIdle = (document.hidden || matrixService.presence.unavailable === mPresence.getState());
-                
-                // We need a way to let people get notifications for everything, if they so desire.  The way to do this
-                // is to specify zero bingwords.
-                var bingWords = matrixService.config().bingWords;
-                if (bingWords === undefined || bingWords.length === 0) {
-                    shouldBing = true;
-                }
-                
-                if (shouldBing && isIdle) {
-                    console.log("Displaying notification for "+JSON.stringify(event));
-                    var member = getMember(event.room_id, event.user_id);
-                    var displayname = getUserDisplayName(event.room_id, event.user_id);
-
-                    var message = event.content.body;
-                    if (event.content.msgtype === "m.emote") {
-                        message = "* " + displayname + " " + message;
-                    }
-                    else if (event.content.msgtype === "m.image") {
-                        message = displayname + " sent an image.";
-                    }
-
-                    var roomTitle = matrixService.getRoomIdToAliasMapping(event.room_id);
-                    var theRoom = $rootScope.events.rooms[event.room_id];
-                    if (!roomTitle && theRoom && theRoom["m.room.name"] && theRoom["m.room.name"].content) {
-                        roomTitle = theRoom["m.room.name"].content.name;
-                    }
-
-                    if (!roomTitle) {
-                        roomTitle = event.room_id;
-                    }
-                    
-                    notificationService.showNotification(
-                        displayname + " (" + roomTitle + ")",
-                        message,
-                        member ? member.avatar_url : undefined,
-                        function() {
-                            console.log("notification.onclick() room=" + event.room_id);
-                            $rootScope.goToPage('room/' + event.room_id); 
-                        }
-                    );
-                }
-            }
-        }
-        else {
-            $rootScope.events.rooms[event.room_id].messages.unshift(event);
-        }
-        
-        // TODO send delivery receipt if isLiveEvent
-        
-        // $broadcast this, as controllers may want to do funky things such as
-        // scroll to the bottom, etc which cannot be expressed via simple $scope
-        // updates.
-        $rootScope.$broadcast(MSG_EVENT, event, isLiveEvent);
-    };
-    
-    var handleRoomMember = function(event, isLiveEvent, isStateEvent) {
-        
-        // add membership changes as if they were a room message if something interesting changed
-        // Exception: Do not do this if the event is a room state event because such events already come
-        // as room messages events. Moreover, when they come as room messages events, they are relatively ordered
-        // with other other room messages
-        if (!isStateEvent) {
-            // could be a membership change, display name change, etc.
-            // Find out which one.
-            var memberChanges = undefined;
-            if ((event.prev_content === undefined && event.content.membership) || (event.prev_content && (event.prev_content.membership !== event.content.membership))) {
-                memberChanges = "membership";
-            }
-            else if (event.prev_content && (event.prev_content.displayname !== event.content.displayname)) {
-                memberChanges = "displayname";
-            }
-
-            // mark the key which changed
-            event.changedKey = memberChanges;
-
-            // If there was a change we want to display, dump it in the message
-            // list.
-            if (memberChanges) {
-                if (isLiveEvent) {
-                    $rootScope.events.rooms[event.room_id].messages.push(event);
-                }
-                else {
-                    $rootScope.events.rooms[event.room_id].messages.unshift(event);
-                }
-            }
-        }
-        
-        // Use data from state event or the latest data from the stream.
-        // Do not care of events that come when paginating back
-        if (isStateEvent || isLiveEvent) {
-            $rootScope.events.rooms[event.room_id].members[event.state_key] = event;
-        }
-        
-        $rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent, isStateEvent);
-    };
-    
-    var handlePresence = function(event, isLiveEvent) {
-        $rootScope.presence[event.content.user_id] = event;
-        $rootScope.$broadcast(PRESENCE_EVENT, event, isLiveEvent);
-    };
-    
-    var handlePowerLevels = function(event, isLiveEvent) {
-        // Keep the latest data. Do not care of events that come when paginating back
-        if (!$rootScope.events.rooms[event.room_id][event.type] || isLiveEvent) {
-            $rootScope.events.rooms[event.room_id][event.type] = event;
-            $rootScope.$broadcast(POWERLEVEL_EVENT, event, isLiveEvent);   
-        }
-    };
-
-    var handleRoomName = function(event, isLiveEvent, isStateEvent) {
-        console.log("handleRoomName room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - name: " + event.content.name);
-        handleRoomDateEvent(event, isLiveEvent, !isStateEvent);
-        $rootScope.$broadcast(NAME_EVENT, event, isLiveEvent);
-    };
-    
-
-    var handleRoomTopic = function(event, isLiveEvent, isStateEvent) {
-        console.log("handleRoomTopic room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - topic: " + event.content.topic);
-        handleRoomDateEvent(event, isLiveEvent, !isStateEvent);
-        $rootScope.$broadcast(TOPIC_EVENT, event, isLiveEvent);
-    };
-
-    var handleCallEvent = function(event, isLiveEvent) {
-        $rootScope.$broadcast(CALL_EVENT, event, isLiveEvent);
-        if (event.type === 'm.call.invite') {
-            $rootScope.events.rooms[event.room_id].messages.push(event);
-        }
-    };
-
-    var handleRedaction = function(event, isLiveEvent) {
-        if (!isLiveEvent) {
-            // we have nothing to remove, so just ignore it.
-            console.log("Received redacted event: "+JSON.stringify(event));
-            return;
-        }
-
-        // we need to remove something possibly: do we know the redacted
-        // event ID?
-        if (eventMap[event.redacts]) {
-            // remove event from list of messages in this room.
-            var eventList = $rootScope.events.rooms[event.room_id].messages;
-            for (var i=0; i<eventList.length; i++) {
-                if (eventList[i].event_id === event.redacts) {
-                    console.log("Removing event " + event.redacts);
-                    eventList.splice(i, 1);
-                    break;
-                }
-            }
-
-            // broadcast the redaction so controllers can nuke this
-            console.log("Redacted an event.");
-        }
-    }
-    
-    /**
-     * Get the index of the event in $rootScope.events.rooms[room_id].messages
-     * @param {type} room_id the room id
-     * @param {type} event_id the event id to look for
-     * @returns {Number | undefined} the index. undefined if not found.
-     */
-    var getRoomEventIndex = function(room_id, event_id) {
-        var index;
-
-        var room = $rootScope.events.rooms[room_id];
-        if (room) {
-            // Start looking from the tail since the first goal of this function 
-            // is to find a messaged among the latest ones
-            for (var i = room.messages.length - 1; i > 0; i--) {
-                var message = room.messages[i];
-                if (event_id === message.event_id) {
-                    index = i;
-                    break;
-                }
-            }
-        }
-        return index;
-    };
-    
-    /**
-     * Get the member object of a room member
-     * @param {String} room_id the room id
-     * @param {String} user_id the id of the user
-     * @returns {undefined | Object} the member object of this user in this room if he is part of the room
-     */
-    var getMember = function(room_id, user_id) {
-        var member;
-
-        var room = $rootScope.events.rooms[room_id];
-        if (room) {
-            member = room.members[user_id];
-        }
-        return member;
-    };
-
-    /**
-     * Return the display name of an user acccording to data already downloaded
-     * @param {String} room_id the room id
-     * @param {String} user_id the id of the user
-     * @returns {String} the user displayname or user_id if not available
-     */
-    var getUserDisplayName = function(room_id, user_id) {
-        var displayName;
-
-        // Get the user display name from the member list of the room
-        var member = getMember(room_id, user_id);
-        if (member && member.content.displayname) { // Do not consider null displayname
-            displayName = member.content.displayname;
-
-            // Disambiguate users who have the same displayname in the room
-            if (user_id !== matrixService.config().user_id) {
-                var room = $rootScope.events.rooms[room_id];
-
-                for (var member_id in room.members) {
-                    if (room.members.hasOwnProperty(member_id) && member_id !== user_id) {
-                        var member2 = room.members[member_id];
-                        if (member2.content.displayname && member2.content.displayname === displayName) {
-                            displayName = displayName + " (" + user_id + ")";
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-
-        // The user may not have joined the room yet. So try to resolve display name from presence data
-        // Note: This data may not be available
-        if (undefined === displayName && user_id in $rootScope.presence) {
-            displayName = $rootScope.presence[user_id].content.displayname;
-        }
-
-        if (undefined === displayName) {
-            // By default, use the user ID
-            displayName = user_id;
-        }
-        return displayName;
-    };
-
-    return {
-        ROOM_CREATE_EVENT: ROOM_CREATE_EVENT,
-        MSG_EVENT: MSG_EVENT,
-        MEMBER_EVENT: MEMBER_EVENT,
-        PRESENCE_EVENT: PRESENCE_EVENT,
-        POWERLEVEL_EVENT: POWERLEVEL_EVENT,
-        CALL_EVENT: CALL_EVENT,
-        NAME_EVENT: NAME_EVENT,
-        TOPIC_EVENT: TOPIC_EVENT,
-        RESET_EVENT: RESET_EVENT,
-        
-        reset: function() {
-            reset();
-            $rootScope.$broadcast(RESET_EVENT);
-        },
-        
-        initRoom: function(room) {
-            initRoom(room.room_id, room);
-        },
-    
-        handleEvent: function(event, isLiveEvent, isStateEvent) {
-
-            // FIXME: /initialSync on a particular room is not yet available
-            // So initRoom on a new room is not called. Make sure the room data is initialised here
-            if (event.room_id) {
-                initRoom(event.room_id);
-            }
-
-            // Avoid duplicated events
-            // Needed for rooms where initialSync has not been done. 
-            // In this case, we do not know where to start pagination. So, it starts from the END
-            // and we can have the same event (ex: joined, invitation) coming from the pagination
-            // AND from the event stream.
-            // FIXME: This workaround should be no more required when /initialSync on a particular room
-            // will be available (as opposite to the global /initialSync done at startup)
-            if (!isStateEvent) {    // Do not consider state events
-                if (event.event_id && eventMap[event.event_id]) {
-                    console.log("discarding duplicate event: " + JSON.stringify(event, undefined, 4));
-                    return;
-                }
-                else {
-                    eventMap[event.event_id] = 1;
-                }
-            }
-
-            if (event.type.indexOf('m.call.') === 0) {
-                handleCallEvent(event, isLiveEvent);
-            }
-            else {            
-                switch(event.type) {
-                    case "m.room.create":
-                        handleRoomCreate(event, isLiveEvent);
-                        break;
-                    case "m.room.aliases":
-                        handleRoomAliases(event, isLiveEvent);
-                        break;
-                    case "m.room.message":
-                        handleMessage(event, isLiveEvent);
-                        break;
-                    case "m.room.member":
-                        handleRoomMember(event, isLiveEvent, isStateEvent);
-                        break;
-                    case "m.presence":
-                        handlePresence(event, isLiveEvent);
-                        break;
-                    case 'm.room.ops_levels':
-                    case 'm.room.send_event_level':
-                    case 'm.room.add_state_level':
-                    case 'm.room.join_rules':
-                    case 'm.room.power_levels':
-                        handlePowerLevels(event, isLiveEvent);
-                        break;
-                    case 'm.room.name':
-                        handleRoomName(event, isLiveEvent, isStateEvent);
-                        break;
-                    case 'm.room.topic':
-                        handleRoomTopic(event, isLiveEvent, isStateEvent);
-                        break;
-                    case 'm.room.redaction':
-                        handleRedaction(event, isLiveEvent);
-                        break;
-                    default:
-                        // if it is a state event, then just add it in so it
-                        // displays on the Room Info screen.
-                        if (typeof(event.state_key) === "string") { // incls. 0-len strings
-                            if (event.room_id) {
-                                handleRoomDateEvent(event, isLiveEvent, false);
-                            }
-                        }
-                        console.log("Unable to handle event type " + event.type);
-                        console.log(JSON.stringify(event, undefined, 4));
-                        break;
-                }
-            }
-        },
-        
-        // isLiveEvents determines whether notifications should be shown, whether
-        // messages get appended to the start/end of lists, etc.
-        handleEvents: function(events, isLiveEvents, isStateEvents) {
-            for (var i=0; i<events.length; i++) {
-                this.handleEvent(events[i], isLiveEvents, isStateEvents);
-            }
-        },
-
-        // Handle messages from /initialSync or /messages
-        handleRoomMessages: function(room_id, messages, isLiveEvents, dir) {
-            initRoom(room_id);
-
-            var events = messages.chunk;
-
-            // Handles messages according to their time order
-            if (dir && 'b' === dir) {
-                // paginateBackMessages requests messages to be in reverse chronological order
-                for (var i=0; i<events.length; i++) {
-                    this.handleEvent(events[i], isLiveEvents, isLiveEvents);
-                }
-                
-                // Store how far back we've paginated
-                $rootScope.events.rooms[room_id].pagination.earliest_token = messages.end;
-            }
-            else {
-                // InitialSync returns messages in chronological order
-                for (var i=events.length - 1; i>=0; i--) {
-                    this.handleEvent(events[i], isLiveEvents, isLiveEvents);
-                }
-                // Store where to start pagination
-                $rootScope.events.rooms[room_id].pagination.earliest_token = messages.start;
-            }
-        },
-
-        handleInitialSyncDone: function(initialSyncData) {
-            console.log("# handleInitialSyncDone");
-            initialSyncDeferred.resolve(initialSyncData);
-        },
-
-        // Returns a promise that resolves when the initialSync request has been processed
-        waitForInitialSyncCompletion: function() {
-            return initialSyncDeferred.promise;
-        },
-
-        resetRoomMessages: function(room_id) {
-            resetRoomMessages(room_id);
-        },
-        
-        /**
-         * Return the last message event of a room
-         * @param {String} room_id the room id
-         * @param {Boolean} filterFake true to not take into account fake messages
-         * @returns {undefined | Event} the last message event if available
-         */
-        getLastMessage: function(room_id, filterEcho) {
-            var lastMessage;
-
-            var room = $rootScope.events.rooms[room_id];
-            if (room) {
-                for (var i = room.messages.length - 1; i >= 0; i--) {
-                    var message = room.messages[i];
-
-                    if (!filterEcho || undefined === message.echo_msg_state) {
-                        lastMessage = message;
-                        break;
-                    }
-                }
-            }
-
-            return lastMessage;
-        },
-        
-        /**
-         * Compute the room users number, ie the number of members who has joined the room.
-         * @param {String} room_id the room id
-         * @returns {undefined | Number} the room users number if available
-         */
-        getUsersCountInRoom: function(room_id) {
-            var memberCount;
-
-            var room = $rootScope.events.rooms[room_id];
-            if (room) {
-                memberCount = 0;
-
-                for (var i in room.members) {
-                    if (!room.members.hasOwnProperty(i)) continue;
-
-                    var member = room.members[i];
-
-                    if ("join" === member.membership) {
-                        memberCount = memberCount + 1;
-                    }
-                }
-            }
-
-            return memberCount;
-        },
-        
-        /**
-         * Get the member object of a room member
-         * @param {String} room_id the room id
-         * @param {String} user_id the id of the user
-         * @returns {undefined | Object} the member object of this user in this room if he is part of the room
-         */
-        getMember: function(room_id, user_id) {
-            return getMember(room_id, user_id);
-        },
-        
-        /**
-         * Return the display name of an user acccording to data already downloaded
-         * @param {String} room_id the room id
-         * @param {String} user_id the id of the user
-         * @returns {String} the user displayname or user_id if not available
-         */
-        getUserDisplayName: function(room_id, user_id) {
-            return getUserDisplayName(room_id, user_id);
-        },
-
-        setRoomVisibility: function(room_id, visible) {
-            if (!visible) {
-                return;
-            }
-            initRoom(room_id);
-            
-            var room = $rootScope.events.rooms[room_id];
-            if (room) {
-                room.visibility = visible;
-            }
-        }
-    };
-}]);
diff --git a/webclient/components/matrix/event-stream-service.js b/webclient/components/matrix/event-stream-service.js
deleted file mode 100644
index 05469a3ded..0000000000
--- a/webclient/components/matrix/event-stream-service.js
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
-Copyright 2014 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';
-
-/*
-This service manages where in the event stream the web client currently is,
-repolling the event stream, and provides methods to resume/pause/stop the event 
-stream. This service is not responsible for parsing event data. For that, see 
-the eventHandlerService.
-*/
-angular.module('eventStreamService', [])
-.factory('eventStreamService', ['$q', '$timeout', 'matrixService', 'eventHandlerService', function($q, $timeout, matrixService, eventHandlerService) {
-    var END = "END";
-    var SERVER_TIMEOUT_MS = 30000;
-    var CLIENT_TIMEOUT_MS = 40000;
-    var ERR_TIMEOUT_MS = 5000;
-    
-    var settings = {
-        from: "END",
-        to: undefined,
-        limit: undefined,
-        shouldPoll: true,
-        isActive: false
-    };
-    
-    // interrupts the stream. Only valid if there is a stream conneciton 
-    // open.
-    var interrupt = function(shouldPoll) {
-        console.log("[EventStream] interrupt("+shouldPoll+") "+
-                    JSON.stringify(settings));
-        settings.shouldPoll = shouldPoll;
-        settings.isActive = false;
-    };
-    
-    var saveStreamSettings = function() {
-        localStorage.setItem("streamSettings", JSON.stringify(settings));
-    };
-
-    var doEventStream = function(deferred) {
-        settings.shouldPoll = true;
-        settings.isActive = true;
-        deferred = deferred || $q.defer();
-
-        // run the stream from the latest token
-        matrixService.getEventStream(settings.from, SERVER_TIMEOUT_MS, CLIENT_TIMEOUT_MS).then(
-            function(response) {
-                if (!settings.isActive) {
-                    console.log("[EventStream] Got response but now inactive. Dropping data.");
-                    return;
-                }
-                
-                settings.from = response.data.end;
-                
-                console.log(
-                    "[EventStream] Got response from "+settings.from+
-                    " to "+response.data.end
-                );
-                eventHandlerService.handleEvents(response.data.chunk, true);
-                
-                deferred.resolve(response);
-                
-                if (settings.shouldPoll) {
-                    $timeout(doEventStream, 0);
-                }
-                else {
-                    console.log("[EventStream] Stopping poll.");
-                }
-            },
-            function(error) {
-                if (error.status === 403) {
-                    settings.shouldPoll = false;
-                }
-                
-                deferred.reject(error);
-                
-                if (settings.shouldPoll) {
-                    $timeout(doEventStream, ERR_TIMEOUT_MS);
-                }
-                else {
-                    console.log("[EventStream] Stopping polling.");
-                }
-            }
-        );
-
-        return deferred.promise;
-    }; 
-
-    var startEventStream = function() {
-        settings.shouldPoll = true;
-        settings.isActive = true;
-        var deferred = $q.defer();
-
-        // Initial sync: get all information and the last 30 messages of all rooms of the user
-        // 30 messages should be enough to display a full page of messages in a room
-        // without requiring to make an additional request
-        matrixService.initialSync(30, false).then(
-            function(response) {
-                var rooms = response.data.rooms;
-                for (var i = 0; i < rooms.length; ++i) {
-                    var room = rooms[i];
-                    
-                    eventHandlerService.initRoom(room);
-
-                    if ("messages" in room) {
-                        eventHandlerService.handleRoomMessages(room.room_id, room.messages, false);
-                    }
-                    
-                    if ("state" in room) {
-                        eventHandlerService.handleEvents(room.state, false, true);
-                    }
-                }
-
-                var presence = response.data.presence;
-                eventHandlerService.handleEvents(presence, false);
-
-                // Initial sync is done
-                eventHandlerService.handleInitialSyncDone(response);
-
-                // Start event streaming from that point
-                settings.from = response.data.end;
-                doEventStream(deferred);        
-            },
-            function(error) {
-                $scope.feedback = "Failure: " + error.data;
-            }
-        );
-
-        return deferred.promise;
-    };
-    
-    return {
-        // resume the stream from whereever it last got up to. Typically used
-        // when the page is opened.
-        resume: function() {
-            if (settings.isActive) {
-                console.log("[EventStream] Already active, ignoring resume()");
-                return;
-            }
-        
-            console.log("[EventStream] resume "+JSON.stringify(settings));
-            return startEventStream();
-        },
-        
-        // pause the stream. Resuming it will continue from the current position
-        pause: function() {
-            console.log("[EventStream] pause "+JSON.stringify(settings));
-            // kill any running stream
-            interrupt(false);
-            // save the latest token
-            saveStreamSettings();
-        },
-        
-        // stop the stream and wipe the position in the stream. Typically used
-        // when logging out / logged out.
-        stop: function() {
-            console.log("[EventStream] stop "+JSON.stringify(settings));
-            // kill any running stream
-            interrupt(false);
-            // clear the latest token
-            settings.from = END;
-            saveStreamSettings();
-        }
-    };
-
-}]);
diff --git a/webclient/components/matrix/matrix-call.js b/webclient/components/matrix/matrix-call.js
deleted file mode 100644
index 3e8811e5fc..0000000000
--- a/webclient/components/matrix/matrix-call.js
+++ /dev/null
@@ -1,607 +0,0 @@
-/*
-Copyright 2014 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';
-
-var forAllVideoTracksOnStream = function(s, f) {
-    var tracks = s.getVideoTracks();
-    for (var i = 0; i < tracks.length; i++) {
-        f(tracks[i]);
-    }
-}
-
-var forAllAudioTracksOnStream = function(s, f) {
-    var tracks = s.getAudioTracks();
-    for (var i = 0; i < tracks.length; i++) {
-        f(tracks[i]);
-    }
-}
-
-var forAllTracksOnStream = function(s, f) {
-    forAllVideoTracksOnStream(s, f);
-    forAllAudioTracksOnStream(s, f);
-}
-
-navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
-window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection; // but not mozRTCPeerConnection because its interface is not compatible
-window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription;
-window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate;
-
-// Returns true if the browser supports all required features to make WebRTC call
-var isWebRTCSupported = function () {
-    return !!(navigator.getUserMedia || window.RTCPeerConnection || window.RTCSessionDescription || window.RTCIceCandidate);
-};
-
-angular.module('MatrixCall', [])
-.factory('MatrixCall', ['matrixService', 'matrixPhoneService', '$rootScope', '$timeout', function MatrixCallFactory(matrixService, matrixPhoneService, $rootScope, $timeout) {
-    $rootScope.isWebRTCSupported = isWebRTCSupported();
-
-    var MatrixCall = function(room_id) {
-        this.room_id = room_id;
-        this.call_id = "c" + new Date().getTime();
-        this.state = 'fledgling';
-        this.didConnect = false;
-
-        // a queue for candidates waiting to go out. We try to amalgamate candidates into a single candidate message where possible
-        this.candidateSendQueue = [];
-        this.candidateSendTries = 0;
-
-        var self = this;
-        $rootScope.$watch(this.remoteVideoElement, function (oldValue, newValue) {
-            self.tryPlayRemoteStream();
-        });
-
-    }
-
-    MatrixCall.getTurnServer = function() {
-        matrixService.getTurnServer().then(function(response) {
-            if (response.data.uris) {
-                console.log("Got TURN URIs: "+response.data.uris);
-                MatrixCall.turnServer = response.data;
-                $rootScope.haveTurn = true;
-                // re-fetch when we're about to reach the TTL
-                $timeout(MatrixCall.getTurnServer, MatrixCall.turnServer.ttl * 1000 * 0.9);
-            } else {
-                console.log("Got no TURN URIs from HS");
-                $rootScope.haveTurn = false;
-            }
-        }, function(error) {
-            console.log("Failed to get TURN URIs");
-            MatrixCall.turnServer = {};
-            $timeout(MatrixCall.getTurnServer, 60000);
-        });
-    }
-
-    // FIXME: we should prevent any class from being placed or accepted before this has finished
-    MatrixCall.getTurnServer();
-
-    MatrixCall.CALL_TIMEOUT = 60000;
-    MatrixCall.FALLBACK_STUN_SERVER = 'stun:stun.l.google.com:19302';
-
-    MatrixCall.prototype.createPeerConnection = function() {
-        var pc;
-        if (window.mozRTCPeerConnection) {
-            var iceServers = [];
-            if (MatrixCall.turnServer) {
-                if (MatrixCall.turnServer.uris) {
-                    for (var i = 0; i < MatrixCall.turnServer.uris.length; i++) {
-                        iceServers.push({
-                            'url': MatrixCall.turnServer.uris[i],
-                            'username': MatrixCall.turnServer.username,
-                            'credential': MatrixCall.turnServer.password,
-                        });
-                    }
-                } else {
-                    console.log("No TURN server: using fallback STUN server");
-                    iceServers.push({ 'url' : MatrixCall.FALLBACK_STUN_SERVER });
-                }
-            }
-          
-            pc = new window.mozRTCPeerConnection({"iceServers":iceServers});
-        } else {
-            var iceServers = [];
-            if (MatrixCall.turnServer) {
-                if (MatrixCall.turnServer.uris) {
-                    iceServers.push({
-                        'urls': MatrixCall.turnServer.uris,
-                        'username': MatrixCall.turnServer.username,
-                        'credential': MatrixCall.turnServer.password,
-                    });
-                } else {
-                    console.log("No TURN server: using fallback STUN server");
-                    iceServers.push({ 'urls' : MatrixCall.FALLBACK_STUN_SERVER });
-                }
-            }
-          
-            pc = new window.RTCPeerConnection({"iceServers":iceServers});
-        }
-        var self = this;
-        pc.oniceconnectionstatechange = function() { self.onIceConnectionStateChanged(); };
-        pc.onsignalingstatechange = function() { self.onSignallingStateChanged(); };
-        pc.onicecandidate = function(c) { self.gotLocalIceCandidate(c); };
-        pc.onaddstream = function(s) { self.onAddStream(s); };
-        return pc;
-    }
-
-    MatrixCall.prototype.getUserMediaVideoContraints = function(callType) {
-        switch (callType) {
-            case 'voice':
-                return ({audio: true, video: false});
-            case 'video':
-                return ({audio: true, video: {
-                    mandatory: {
-                        minWidth: 640,
-                        maxWidth: 640,
-                        minHeight: 360,
-                        maxHeight: 360,
-                    }
-                }});
-        }
-    };
-
-    MatrixCall.prototype.placeVoiceCall = function() {
-        this.placeCallWithConstraints(this.getUserMediaVideoContraints('voice'));
-        this.type = 'voice';
-    };
-
-    MatrixCall.prototype.placeVideoCall = function(config) {
-        this.placeCallWithConstraints(this.getUserMediaVideoContraints('video'));
-        this.type = 'video';
-    };
-
-    MatrixCall.prototype.placeCallWithConstraints = function(constraints) {
-        var self = this;
-        matrixPhoneService.callPlaced(this);
-        navigator.getUserMedia(constraints, function(s) { self.gotUserMediaForInvite(s); }, function(e) { self.getUserMediaFailed(e); });
-        this.state = 'wait_local_media';
-        this.direction = 'outbound';
-        this.config = constraints;
-    };
-
-    MatrixCall.prototype.initWithInvite = function(event) {
-        this.msg = event.content;
-        this.peerConn = this.createPeerConnection();
-        this.peerConn.setRemoteDescription(new RTCSessionDescription(this.msg.offer), this.onSetRemoteDescriptionSuccess, this.onSetRemoteDescriptionError);
-        this.state = 'ringing';
-        this.direction = 'inbound';
-
-        if (window.mozRTCPeerConnection) {
-            // firefox's RTCPeerConnection doesn't add streams until it starts getting media on them
-            // so we need to figure out whether a video channel has been offered by ourselves.
-            if (this.msg.offer.sdp.indexOf('m=video') > -1) {
-                this.type = 'video';
-            } else {
-                this.type = 'voice';
-            }
-        }
-
-        var self = this;
-        $timeout(function() {
-            if (self.state == 'ringing') {
-                self.state = 'ended';
-                self.hangupParty = 'remote'; // effectively
-                self.stopAllMedia();
-                if (self.peerConn.signalingState != 'closed') self.peerConn.close();
-                if (self.onHangup) self.onHangup(self);
-            }
-        }, this.msg.lifetime - event.age);
-    };
-
-    // perverse as it may seem, sometimes we want to instantiate a call with a hangup message
-    // (because when getting the state of the room on load, events come in reverse order and
-    // we want to remember that a call has been hung up)
-    MatrixCall.prototype.initWithHangup = function(event) {
-        this.msg = event.content;
-        this.state = 'ended';
-    };
-
-    MatrixCall.prototype.answer = function() {
-        console.log("Answering call "+this.call_id);
-
-        var self = this;
-
-        var roomMembers = $rootScope.events.rooms[this.room_id].members;
-        if (roomMembers[matrixService.config().user_id].membership != 'join') {
-            console.log("We need to join the room before we can accept this call");
-            matrixService.join(this.room_id).then(function() {
-                self.answer();
-            }, function() {
-                console.log("Failed to join room: can't answer call!");
-                self.onError("Unable to join room to answer call!");
-                self.hangup();
-            });
-            return;
-        }
-
-        if (!this.localAVStream && !this.waitForLocalAVStream) {
-            navigator.getUserMedia(this.getUserMediaVideoContraints(this.type), function(s) { self.gotUserMediaForAnswer(s); }, function(e) { self.getUserMediaFailed(e); });
-            this.state = 'wait_local_media';
-        } else if (this.localAVStream) {
-            this.gotUserMediaForAnswer(this.localAVStream);
-        } else if (this.waitForLocalAVStream) {
-            this.state = 'wait_local_media';
-        }
-    };
-
-    MatrixCall.prototype.stopAllMedia = function() {
-        if (this.localAVStream) {
-            forAllTracksOnStream(this.localAVStream, function(t) {
-                if (t.stop) t.stop();
-            });
-        }
-        if (this.remoteAVStream) {
-            forAllTracksOnStream(this.remoteAVStream, function(t) {
-                if (t.stop) t.stop();
-            });
-        }
-    };
-
-    MatrixCall.prototype.hangup = function(reason, suppressEvent) {
-        console.log("Ending call "+this.call_id);
-
-        // pausing now keeps the last frame (ish) of the video call in the video element
-        // rather than it just turning black straight away
-        if (this.remoteVideoElement) this.remoteVideoElement.pause();
-        if (this.localVideoElement) this.localVideoElement.pause();
-
-        this.stopAllMedia();
-        if (this.peerConn) this.peerConn.close();
-
-        this.hangupParty = 'local';
-        this.hangupReason = reason;
-
-        var content = {
-            version: 0,
-            call_id: this.call_id,
-            reason: reason
-        };
-        this.sendEventWithRetry('m.call.hangup', content);
-        this.state = 'ended';
-        if (this.onHangup && !suppressEvent) this.onHangup(this);
-    };
-
-    MatrixCall.prototype.gotUserMediaForInvite = function(stream) {
-        if (this.successor) {
-            this.successor.gotUserMediaForAnswer(stream);
-            return;
-        }
-        if (this.state == 'ended') return;
-
-        if (this.localVideoElement && this.type == 'video') {
-            var vidTrack = stream.getVideoTracks()[0];
-            this.localVideoElement.src = URL.createObjectURL(stream);
-            this.localVideoElement.muted = true;
-            this.localVideoElement.play();
-        }
-
-        this.localAVStream = stream;
-        var audioTracks = stream.getAudioTracks();
-        for (var i = 0; i < audioTracks.length; i++) {
-            audioTracks[i].enabled = true;
-        }
-        this.peerConn = this.createPeerConnection();
-        this.peerConn.addStream(stream);
-        var self = this;
-        this.peerConn.createOffer(function(d) {
-            self.gotLocalOffer(d);
-        }, function(e) {
-            self.getLocalOfferFailed(e);
-        });
-        $rootScope.$apply(function() {
-            self.state = 'create_offer';
-        });
-    };
-
-    MatrixCall.prototype.gotUserMediaForAnswer = function(stream) {
-        if (this.state == 'ended') return;
-
-        if (this.localVideoElement && this.type == 'video') {
-            var vidTrack = stream.getVideoTracks()[0];
-            this.localVideoElement.src = URL.createObjectURL(stream);
-            this.localVideoElement.muted = true;
-            this.localVideoElement.play();
-        }
-
-        this.localAVStream = stream;
-        var audioTracks = stream.getAudioTracks();
-        for (var i = 0; i < audioTracks.length; i++) {
-            audioTracks[i].enabled = true;
-        }
-        this.peerConn.addStream(stream);
-        var self = this;
-        var constraints = {
-            'mandatory': {
-                'OfferToReceiveAudio': true,
-                'OfferToReceiveVideo': this.type == 'video'
-            },
-        };
-        this.peerConn.createAnswer(function(d) { self.createdAnswer(d); }, function(e) {}, constraints);
-        // This can't be in an apply() because it's called by a predecessor call under glare conditions :(
-        self.state = 'create_answer';
-    };
-
-    MatrixCall.prototype.gotLocalIceCandidate = function(event) {
-        if (event.candidate) {
-            console.log("Got local ICE "+event.candidate.sdpMid+" candidate: "+event.candidate.candidate);
-            this.sendCandidate(event.candidate);
-        }
-    }
-
-    MatrixCall.prototype.gotRemoteIceCandidate = function(cand) {
-        console.log("Got remote ICE "+cand.sdpMid+" candidate: "+cand.candidate);
-        if (this.state == 'ended') {
-            console.log("Ignoring remote ICE candidate because call has ended");
-            return;
-        }
-        this.peerConn.addIceCandidate(new RTCIceCandidate(cand), function() {}, function(e) {});
-    };
-
-    MatrixCall.prototype.receivedAnswer = function(msg) {
-        if (this.state == 'ended') return;
-
-        this.peerConn.setRemoteDescription(new RTCSessionDescription(msg.answer), this.onSetRemoteDescriptionSuccess, this.onSetRemoteDescriptionError);
-        this.state = 'connecting';
-    };
-
-
-    MatrixCall.prototype.gotLocalOffer = function(description) {
-        console.log("Created offer: "+description);
-
-        if (this.state == 'ended') {
-            console.log("Ignoring newly created offer on call ID "+this.call_id+" because the call has ended");
-            return;
-        }
-
-        this.peerConn.setLocalDescription(description);
-
-        var content = {
-            version: 0,
-            call_id: this.call_id,
-            offer: description,
-            lifetime: MatrixCall.CALL_TIMEOUT
-        };
-        this.sendEventWithRetry('m.call.invite', content);
-
-        var self = this;
-        $timeout(function() {
-            if (self.state == 'invite_sent') {
-                self.hangup('invite_timeout');
-            }
-        }, MatrixCall.CALL_TIMEOUT);
-
-        $rootScope.$apply(function() {
-            self.state = 'invite_sent';
-        });
-    };
-
-    MatrixCall.prototype.createdAnswer = function(description) {
-        console.log("Created answer: "+description);
-        this.peerConn.setLocalDescription(description);
-        var content = {
-            version: 0,
-            call_id: this.call_id,
-            answer: description
-        };
-        this.sendEventWithRetry('m.call.answer', content);
-        var self = this;
-        $rootScope.$apply(function() {
-            self.state = 'connecting';
-        });
-    };
-
-    MatrixCall.prototype.getLocalOfferFailed = function(error) {
-        this.onError("Failed to start audio for call!");
-    };
-
-    MatrixCall.prototype.getUserMediaFailed = function() {
-        this.onError("Couldn't start capturing! Is your microphone set up?");
-        this.hangup();
-    };
-
-    MatrixCall.prototype.onIceConnectionStateChanged = function() {
-        if (this.state == 'ended') return; // because ICE can still complete as we're ending the call
-        console.log("Ice connection state changed to: "+this.peerConn.iceConnectionState);
-        // ideally we'd consider the call to be connected when we get media but chrome doesn't implement nay of the 'onstarted' events yet
-        if (this.peerConn.iceConnectionState == 'completed' || this.peerConn.iceConnectionState == 'connected') {
-            var self = this;
-            $rootScope.$apply(function() {
-                self.state = 'connected';
-                self.didConnect = true;
-            });
-        } else if (this.peerConn.iceConnectionState == 'failed') {
-            this.hangup('ice_failed');
-        }
-    };
-
-    MatrixCall.prototype.onSignallingStateChanged = function() {
-        console.log("call "+this.call_id+": Signalling state changed to: "+this.peerConn.signalingState);
-    };
-
-    MatrixCall.prototype.onSetRemoteDescriptionSuccess = function() {
-        console.log("Set remote description");
-    };
-    
-    MatrixCall.prototype.onSetRemoteDescriptionError = function(e) {
-        console.log("Failed to set remote description"+e);
-    };
-
-    MatrixCall.prototype.onAddStream = function(event) {
-        console.log("Stream added"+event);
-
-        var s = event.stream;
-
-        this.remoteAVStream = s;
-
-        if (this.direction == 'inbound') {
-            if (s.getVideoTracks().length > 0) {
-                this.type = 'video';
-            } else {
-                this.type = 'voice';
-            }
-        }
-
-        var self = this;
-        forAllTracksOnStream(s, function(t) {
-            // not currently implemented in chrome
-            t.onstarted = self.onRemoteStreamTrackStarted;
-        });
-
-        event.stream.onended = function(e) { self.onRemoteStreamEnded(e); }; 
-        // not currently implemented in chrome
-        event.stream.onstarted = function(e) { self.onRemoteStreamStarted(e); };
-
-        this.tryPlayRemoteStream();
-    };
-
-    MatrixCall.prototype.tryPlayRemoteStream = function(event) {
-        if (this.remoteVideoElement && this.remoteAVStream) {
-            var player = this.remoteVideoElement;
-            player.src = URL.createObjectURL(this.remoteAVStream);
-            player.play();
-        }
-    };
-
-    MatrixCall.prototype.onRemoteStreamStarted = function(event) {
-        var self = this;
-        $rootScope.$apply(function() {
-            self.state = 'connected';
-        });
-    };
-
-    MatrixCall.prototype.onRemoteStreamEnded = function(event) {
-        console.log("Remote stream ended");
-        var self = this;
-        $rootScope.$apply(function() {
-            self.state = 'ended';
-            self.hangupParty = 'remote';
-            self.stopAllMedia();
-            if (self.peerConn.signalingState != 'closed') self.peerConn.close();
-            if (self.onHangup) self.onHangup(self);
-        });
-    };
-
-    MatrixCall.prototype.onRemoteStreamTrackStarted = function(event) {
-        var self = this;
-        $rootScope.$apply(function() {
-            self.state = 'connected';
-        });
-    };
-
-    MatrixCall.prototype.onHangupReceived = function(msg) {
-        console.log("Hangup received");
-        if (this.remoteVideoElement) this.remoteVideoElement.pause();
-        if (this.localVideoElement) this.localVideoElement.pause();
-        this.state = 'ended';
-        this.hangupParty = 'remote';
-        this.hangupReason = msg.reason;
-        this.stopAllMedia();
-        if (this.peerConn && this.peerConn.signalingState != 'closed') this.peerConn.close();
-        if (this.onHangup) this.onHangup(this);
-    };
-
-    MatrixCall.prototype.replacedBy = function(newCall) {
-        console.log(this.call_id+" being replaced by "+newCall.call_id);
-        if (this.state == 'wait_local_media') {
-            console.log("Telling new call to wait for local media");
-            newCall.waitForLocalAVStream = true;
-        } else if (this.state == 'create_offer') {
-            console.log("Handing local stream to new call");
-            newCall.gotUserMediaForAnswer(this.localAVStream);
-            delete(this.localAVStream);
-        } else if (this.state == 'invite_sent') {
-            console.log("Handing local stream to new call");
-            newCall.gotUserMediaForAnswer(this.localAVStream);
-            delete(this.localAVStream);
-        }
-        newCall.localVideoElement = this.localVideoElement;
-        newCall.remoteVideoElement = this.remoteVideoElement;
-        this.successor = newCall;
-        this.hangup(true);
-    };
-
-    MatrixCall.prototype.sendEventWithRetry = function(evType, content) {
-        var ev = { type:evType, content:content, tries:1 };
-        var self = this;
-        matrixService.sendEvent(this.room_id, evType, undefined, content).then(this.eventSent, function(error) { self.eventSendFailed(ev, error); } );
-    };
-
-    MatrixCall.prototype.eventSent = function() {
-    };
-
-    MatrixCall.prototype.eventSendFailed = function(ev, error) {
-        if (ev.tries > 5) {
-            console.log("Failed to send event of type "+ev.type+" on attempt "+ev.tries+". Giving up.");
-            return;
-        }
-        var delayMs = 500 * Math.pow(2, ev.tries);
-        console.log("Failed to send event of type "+ev.type+". Retrying in "+delayMs+"ms");
-        ++ev.tries;
-        var self = this;
-        $timeout(function() {
-            matrixService.sendEvent(self.room_id, ev.type, undefined, ev.content).then(self.eventSent, function(error) { self.eventSendFailed(ev, error); } );
-        }, delayMs);
-    };
-
-    // Sends candidates with are sent in a special way because we try to amalgamate them into one message
-    MatrixCall.prototype.sendCandidate = function(content) {
-        this.candidateSendQueue.push(content);
-        var self = this;
-        if (this.candidateSendTries == 0) $timeout(function() { self.sendCandidateQueue(); }, 100);
-    };
-
-    MatrixCall.prototype.sendCandidateQueue = function(content) {
-        if (this.candidateSendQueue.length == 0) return;
-
-        var cands = this.candidateSendQueue;
-        this.candidateSendQueue = [];
-        ++this.candidateSendTries;
-        var content = {
-            version: 0,
-            call_id: this.call_id,
-            candidates: cands
-        };
-        var self = this;
-        console.log("Attempting to send "+cands.length+" candidates");
-        matrixService.sendEvent(self.room_id, 'm.call.candidates', undefined, content).then(function() { self.candsSent(); }, function(error) { self.candsSendFailed(cands, error); } );
-    };
-
-    MatrixCall.prototype.candsSent = function() {
-        this.candidateSendTries = 0;
-        this.sendCandidateQueue();
-    };
-
-    MatrixCall.prototype.candsSendFailed = function(cands, error) {
-        for (var i = 0; i < cands.length; ++i) {
-            this.candidateSendQueue.push(cands[i]);
-        }
-
-        if (this.candidateSendTries > 5) {
-            console.log("Failed to send candidates on attempt "+this.candidateSendTries+". Giving up for now.");
-            this.candidateSendTries = 0;
-            return;
-        }
-
-        var delayMs = 500 * Math.pow(2, this.candidateSendTries);
-        ++this.candidateSendTries;
-        console.log("Failed to send candidates. Retrying in "+delayMs+"ms");
-        var self = this;
-        $timeout(function() {
-            self.sendCandidateQueue();
-        }, delayMs);
-    };
-
-    return MatrixCall;
-}]);
diff --git a/webclient/components/matrix/matrix-filter.js b/webclient/components/matrix/matrix-filter.js
deleted file mode 100644
index 3d64a569a1..0000000000
--- a/webclient/components/matrix/matrix-filter.js
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- Copyright 2014 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';
-
-angular.module('matrixFilter', [])
-
-// Compute the room name according to information we have
-.filter('mRoomName', ['$rootScope', 'matrixService', 'eventHandlerService', function($rootScope, matrixService, eventHandlerService) {
-    return function(room_id) {
-        var roomName;
-
-        // If there is an alias, use it
-        // TODO: only one alias is managed for now
-        var alias = matrixService.getRoomIdToAliasMapping(room_id);
-
-        var room = $rootScope.events.rooms[room_id];
-        if (room) {
-            // Get name from room state date
-            var room_name_event = room["m.room.name"];
-
-            // Determine if it is a public room
-            var isPublicRoom = false;
-            if (room["m.room.join_rules"] && room["m.room.join_rules"].content) {
-                isPublicRoom = ("public" === room["m.room.join_rules"].content.join_rule);
-            }
-
-            if (room_name_event) {
-                roomName = room_name_event.content.name;
-            }
-            else if (alias) {
-                roomName = alias;
-            }
-            else if (room.members && !isPublicRoom) {    // Do not rename public room
-            
-                var user_id = matrixService.config().user_id;
-                // Else, build the name from its users
-                // Limit the room renaming to 1:1 room
-                if (2 === Object.keys(room.members).length) {
-                    for (var i in room.members) {
-                        if (!room.members.hasOwnProperty(i)) continue;
-
-                        var member = room.members[i];
-                        if (member.state_key !== user_id) {
-                            roomName = eventHandlerService.getUserDisplayName(room_id, member.state_key);
-                            break;
-                        }
-                    }
-                }
-                else if (Object.keys(room.members).length <= 1) {
-                    
-                    var otherUserId;
-
-                    if (Object.keys(room.members)[0]) {
-                        otherUserId = Object.keys(room.members)[0];
-                        // this could be an invite event (from event stream)
-                        if (otherUserId === user_id && 
-                                room.members[user_id].content.membership === "invite") {
-                            // this is us being invited to this room, so the
-                            // *user_id* is the other user ID and not the state
-                            // key.
-                            otherUserId = room.members[user_id].user_id;
-                        }
-                    }
-                    else {
-                        // it's got to be an invite, or failing that a self-chat;
-                        otherUserId = room.inviter || user_id;
-/*                        
-                        // XXX: This should all be unnecessary now thanks to using the /rooms/<room>/roomid API
-
-                        // The other member may be in the invite list, get all invited users
-                        var invitedUserIDs = [];
-                        
-                        // XXX: *SURELY* we shouldn't have to trawl through the whole messages list to
-                        // find invite - surely the other user should be in room.members with state invited? :/ --Matthew
-                        for (var i in room.messages) {
-                            var message = room.messages[i];
-                            if ("m.room.member" === message.type && "invite" === message.content.membership) {
-                                // Filter out the current user
-                                var member_id = message.state_key;
-                                if (member_id === user_id) {
-                                    member_id = message.user_id;
-                                }
-                                if (member_id !== user_id) {
-                                    // Make sure there is no duplicate user
-                                    if (-1 === invitedUserIDs.indexOf(member_id)) {
-                                        invitedUserIDs.push(member_id);
-                                    }
-                                }
-                            } 
-                        }
-
-                        // For now, only 1:1 room needs to be renamed. It means only 1 invited user
-                        if (1 === invitedUserIDs.length) {
-                            otherUserId = invitedUserIDs[0];
-                        }
-*/                        
-                    }
-                    
-                    // Get the user display name
-                    roomName = eventHandlerService.getUserDisplayName(room_id, otherUserId);
-                }
-            }
-        }
-
-        // Always show the alias in the room displayed name
-        if (roomName && alias && alias !== roomName) {
-            roomName += " (" + alias + ")";
-        }
-
-        if (undefined === roomName) {
-            // By default, use the room ID
-            roomName = room_id;
-
-            // XXX: this is *INCREDIBLY* heavy logging for a function that calls every single
-            // time any kind of digest runs which refreshes a room name...
-            // commenting it out for now.
-
-            // Log some information that lead to this leak
-            // console.log("Room ID leak for " + room_id);
-            // console.log("room object: " + JSON.stringify(room, undefined, 4));   
-        }
-
-        return roomName;
-    };
-}])
-
-// Return the user display name
-.filter('mUserDisplayName', ['eventHandlerService', function(eventHandlerService) {
-    return function(user_id, room_id) {
-        return eventHandlerService.getUserDisplayName(room_id, user_id);
-    };
-}]);
diff --git a/webclient/components/matrix/matrix-phone-service.js b/webclient/components/matrix/matrix-phone-service.js
deleted file mode 100644
index 06465ed821..0000000000
--- a/webclient/components/matrix/matrix-phone-service.js
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
-Copyright 2014 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';
-
-angular.module('matrixPhoneService', [])
-.factory('matrixPhoneService', ['$rootScope', '$injector', 'matrixService', 'eventHandlerService', function MatrixPhoneService($rootScope, $injector, matrixService, eventHandlerService) {
-    var matrixPhoneService = function() {
-    };
-
-    matrixPhoneService.INCOMING_CALL_EVENT = "INCOMING_CALL_EVENT";
-    matrixPhoneService.REPLACED_CALL_EVENT = "REPLACED_CALL_EVENT";
-    matrixPhoneService.allCalls = {};
-    // a place to save candidates that come in for calls we haven't got invites for yet (when paginating backwards)
-    matrixPhoneService.candidatesByCall = {};
-
-    matrixPhoneService.callPlaced = function(call) {
-        matrixPhoneService.allCalls[call.call_id] = call;
-    };
-
-    $rootScope.$on(eventHandlerService.CALL_EVENT, function(ngEvent, event, isLive) {
-        if (event.user_id == matrixService.config().user_id) return;
-
-        var msg = event.content;
-
-        if (event.type == 'm.call.invite') {
-            if (event.age == undefined || msg.lifetime == undefined) {
-                // if the event doesn't have either an age (the HS is too old) or a lifetime
-                // (the sending client was too old when it sent it) then fall back to old behaviour
-                if (!isLive) return; // until matrix supports expiring messages
-            }
-
-            if (event.age > msg.lifetime) {
-                console.log("Ignoring expired call event of type "+event.type);
-                return;
-            }
-
-            var call = undefined;
-            if (!isLive) {
-                // if this event wasn't live then this call may already be over
-                call = matrixPhoneService.allCalls[msg.call_id];
-                if (call && call.state == 'ended') {
-                    return;
-                }
-            }
-
-            var MatrixCall = $injector.get('MatrixCall');
-            var call = new MatrixCall(event.room_id);
-
-            if (!isWebRTCSupported()) {
-                console.log("Incoming call ID "+msg.call_id+" but this browser doesn't support WebRTC");
-                // don't hang up the call: there could be other clients connected that do support WebRTC and declining the
-                // the call on their behalf would be really annoying.
-                // instead, we broadcast a fake call event with a non-functional call object
-                $rootScope.$broadcast(matrixPhoneService.INCOMING_CALL_EVENT, call);
-                return;
-            }
-
-            call.call_id = msg.call_id;
-            call.initWithInvite(event);
-            matrixPhoneService.allCalls[call.call_id] = call;
-
-            // if we stashed candidate events for that call ID, play them back now
-            if (!isLive && matrixPhoneService.candidatesByCall[call.call_id] != undefined) {
-                for (var i = 0; i < matrixPhoneService.candidatesByCall[call.call_id].length; ++i) {
-                    call.gotRemoteIceCandidate(matrixPhoneService.candidatesByCall[call.call_id][i]);
-                }
-            }
-
-            // Were we trying to call that user (room)?
-            var existingCall;
-            var callIds = Object.keys(matrixPhoneService.allCalls);
-            for (var i = 0; i < callIds.length; ++i) {
-                var thisCallId = callIds[i];
-                var thisCall = matrixPhoneService.allCalls[thisCallId];
-
-                if (call.room_id == thisCall.room_id && thisCall.direction == 'outbound'
-                     && (thisCall.state == 'wait_local_media' || thisCall.state == 'create_offer' || thisCall.state == 'invite_sent')) {
-                    existingCall = thisCall;
-                    break;
-                }
-            }
-
-            if (existingCall) {
-                // If we've only got to wait_local_media or create_offer and we've got an invite,
-                // pick the incoming call because we know we haven't sent our invite yet
-                // otherwise, pick whichever call has the lowest call ID (by string comparison)
-                if (existingCall.state == 'wait_local_media' || existingCall.state == 'create_offer' || existingCall.call_id > call.call_id) {
-                    console.log("Glare detected: answering incoming call "+call.call_id+" and canceling outgoing call "+existingCall.call_id);
-                    existingCall.replacedBy(call);
-                    call.answer();
-                    $rootScope.$broadcast(matrixPhoneService.REPLACED_CALL_EVENT, existingCall, call);
-                } else {
-                    console.log("Glare detected: rejecting incoming call "+call.call_id+" and keeping outgoing call "+existingCall.call_id);
-                    call.hangup();
-                }
-            } else {
-                $rootScope.$broadcast(matrixPhoneService.INCOMING_CALL_EVENT, call);
-            }
-        } else if (event.type == 'm.call.answer') {
-            var call = matrixPhoneService.allCalls[msg.call_id];
-            if (!call) {
-                console.log("Got answer for unknown call ID "+msg.call_id);
-                return;
-            }
-            call.receivedAnswer(msg);
-        } else if (event.type == 'm.call.candidates') {
-            var call = matrixPhoneService.allCalls[msg.call_id];
-            if (!call && isLive) {
-                console.log("Got candidates for unknown call ID "+msg.call_id);
-                return;
-            } else if (!call) {
-                if (matrixPhoneService.candidatesByCall[msg.call_id] == undefined) {
-                    matrixPhoneService.candidatesByCall[msg.call_id] = [];
-                }
-                matrixPhoneService.candidatesByCall[msg.call_id] = matrixPhoneService.candidatesByCall[msg.call_id].concat(msg.candidates);
-            } else {
-                for (var i = 0; i < msg.candidates.length; ++i) {
-                    call.gotRemoteIceCandidate(msg.candidates[i]);
-                }
-            }
-        } else if (event.type == 'm.call.hangup') {
-            var call = matrixPhoneService.allCalls[msg.call_id];
-            if (!call && isLive) {
-                console.log("Got hangup for unknown call ID "+msg.call_id);
-            } else if (!call) {
-                // if not live, store the fact that the call has ended because we're probably getting events backwards so
-                // the hangup will come before the invite
-                var MatrixCall = $injector.get('MatrixCall');
-                var call = new MatrixCall(event.room_id);
-                call.call_id = msg.call_id;
-                call.initWithHangup(event);
-                matrixPhoneService.allCalls[msg.call_id] = call;
-            } else {
-                call.onHangupReceived(msg);
-                delete(matrixPhoneService.allCalls[msg.call_id]);
-            }
-        }
-    });
-    
-    return matrixPhoneService;
-}]);
diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js
deleted file mode 100644
index 1840cf46c0..0000000000
--- a/webclient/components/matrix/matrix-service.js
+++ /dev/null
@@ -1,785 +0,0 @@
-/*
-Copyright 2014 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';
-
-/*
-This service wraps up Matrix API calls. 
-
-This serves to isolate the caller from changes to the underlying url paths, as
-well as attach common params (e.g. access_token) to requests.
-*/
-angular.module('matrixService', [])
-.factory('matrixService', ['$http', '$q', '$rootScope', function($http, $q, $rootScope) {
-        
-   /* 
-    * Permanent storage of user information
-    * The config contains:
-    *    - homeserver url
-    *    - Identity server url
-    *    - user_id
-    *    - access_token
-    *    - version: the version of this cache
-    */    
-    var config;
-    
-    var roomIdToAlias = {};
-    var aliasToRoomId = {};
-    
-    // Current version of permanent storage
-    var configVersion = 0;
-    var prefixPath = "/_matrix/client/api/v1";
-    var MAPPING_PREFIX = "alias_for_";
-
-    var doRequest = function(method, path, params, data, $httpParams) {
-        if (!config) {
-            console.warn("No config exists. Cannot perform request to "+path);
-            return;
-        }
-    
-        // Inject the access token
-        if (!params) {
-            params = {};
-        }
-        
-        params.access_token = config.access_token;
-        
-        if (path.indexOf(prefixPath) !== 0) {
-            path = prefixPath + path;
-        }
-        
-        return doBaseRequest(config.homeserver, method, path, params, data, undefined, $httpParams);
-    };
-
-    var doBaseRequest = function(baseUrl, method, path, params, data, headers, $httpParams) {
-
-        var request = {
-            method: method,
-            url: baseUrl + path,
-            params: params,
-            data: data,
-            headers: headers
-        };
-
-        // Add additional $http parameters
-        if ($httpParams) {
-            angular.extend(request, $httpParams);
-        }
-
-        return $http(request);
-    };
-    
-    var doRegisterLogin = function(path, loginType, sessionId, userName, password, threepidCreds) {
-        var data = {};
-        if (loginType === "m.login.recaptcha") {
-            var challengeToken = Recaptcha.get_challenge();
-            var captchaEntry = Recaptcha.get_response();
-            data = {
-                type: "m.login.recaptcha",
-                challenge: challengeToken,
-                response: captchaEntry
-            };
-        }
-        else if (loginType === "m.login.email.identity") {
-            data = {
-                threepidCreds: threepidCreds
-            };
-        }
-        else if (loginType === "m.login.password") {
-            data = {
-                user: userName,
-                password: password
-            };
-        }
-        
-        if (sessionId) {
-            data.session = sessionId;
-        }
-        data.type = loginType;
-        console.log("doRegisterLogin >>> " + loginType);
-        return doRequest("POST", path, undefined, data);
-    };
-
-    return {
-        /****** Home server API ******/
-        prefix: prefixPath,
-
-        // Register an user
-        register: function(user_name, password, threepidCreds, useCaptcha) {
-            // registration is composed of multiple requests, to check you can
-            // register, then to actually register. This deferred will fire when
-            // all the requests are done, along with the final response.
-            var deferred = $q.defer();
-            var path = "/register";
-            
-            // check we can actually register with this HS.
-            doRequest("GET", path, undefined, undefined).then(
-                function(response) {
-                    console.log("/register [1] : "+JSON.stringify(response));
-                    var flows = response.data.flows;
-                    var knownTypes = [
-                        "m.login.password",
-                        "m.login.recaptcha",
-                        "m.login.email.identity"
-                    ];
-                    // if they entered 3pid creds, we want to use a flow which uses it.
-                    var useThreePidFlow = threepidCreds != undefined;
-                    var flowIndex = 0;
-                    var firstRegType = undefined;
-                    
-                    for (var i=0; i<flows.length; i++) {
-                        var isThreePidFlow = false;
-                        if (flows[i].stages) {
-                            for (var j=0; j<flows[i].stages.length; j++) {
-                                var regType = flows[i].stages[j];
-                                if (knownTypes.indexOf(regType) === -1) {
-                                    deferred.reject("Unknown type: "+regType);
-                                    return;
-                                }
-                                if (regType == "m.login.email.identity") {
-                                    isThreePidFlow = true;
-                                }
-                                if (!useCaptcha && regType == "m.login.recaptcha") {
-                                    console.error("Web client setup to not use captcha, but HS demands a captcha.");
-                                    deferred.reject({
-                                        data: {
-                                            errcode: "M_CAPTCHA_NEEDED",
-                                            error: "Home server requires a captcha."
-                                        }
-                                    });
-                                    return;
-                                }
-                            }
-                        }
-                        
-                        if ( (isThreePidFlow && useThreePidFlow) || (!isThreePidFlow && !useThreePidFlow) ) {
-                            flowIndex = i;
-                        }
-                        
-                        if (knownTypes.indexOf(flows[i].type) == -1) {
-                            deferred.reject("Unknown type: "+flows[i].type);
-                            return;
-                        }
-                    }
-                    
-                    // looks like we can register fine, go ahead and do it.
-                    console.log("Using flow " + JSON.stringify(flows[flowIndex]));
-                    firstRegType = flows[flowIndex].type;
-                    var sessionId = undefined;
-                    
-                    // generic response processor so it can loop as many times as required
-                    var loginResponseFunc = function(response) {
-                        if (response.data.session) {
-                            sessionId = response.data.session;
-                        }
-                        console.log("login response: " + JSON.stringify(response.data));
-                        if (response.data.access_token) {
-                            deferred.resolve(response);
-                        }
-                        else if (response.data.next) {
-                            var nextType = response.data.next;
-                            if (response.data.next instanceof Array) {
-                                for (var i=0; i<response.data.next.length; i++) {
-                                    if (useThreePidFlow && response.data.next[i] == "m.login.email.identity") {
-                                        nextType = response.data.next[i];
-                                        break;
-                                    }
-                                    else if (!useThreePidFlow && response.data.next[i] != "m.login.email.identity") {
-                                        nextType = response.data.next[i];
-                                        break;
-                                    }
-                                }
-                            }
-                            return doRegisterLogin(path, nextType, sessionId, user_name, password, threepidCreds).then(
-                                loginResponseFunc,
-                                function(err) {
-                                    deferred.reject(err);
-                                }
-                            );
-                        }
-                        else {
-                            deferred.reject("Unknown continuation: "+JSON.stringify(response));
-                        }
-                    };
-                    
-                    // set the ball rolling
-                    doRegisterLogin(path, firstRegType, undefined, user_name, password, threepidCreds).then(
-                        loginResponseFunc,
-                        function(err) {
-                            deferred.reject(err);
-                        }
-                    );
-                    
-                },
-                function(err) {
-                    deferred.reject(err);
-                }
-            );
-            
-            return deferred.promise;
-        },
-
-        // Create a room
-        create: function(room_alias, visibility) {
-            // The REST path spec
-            var path = "/createRoom";
-
-            var req = {
-                "visibility": visibility
-            };
-            if (room_alias) {
-                req.room_alias_name = room_alias;
-            }
-            
-            return doRequest("POST", path, undefined, req);
-        },
-
-        // Get the user's current state: his presence, the list of his rooms with
-        // the last {limit} events
-        initialSync: function(limit, feedback) {
-            // The REST path spec
-
-            var path = "/initialSync";
-
-            var params = {};
-            if (limit) {
-                params.limit = limit;
-            }
-            if (feedback) {
-                params.feedback = feedback;
-            }
-
-            return doRequest("GET", path, params);
-        },
-        
-        // get room state for a specific room
-        roomState: function(room_id) {
-            var path = "/rooms/" + room_id + "/state";
-            return doRequest("GET", path);
-        },
-        
-        // Joins a room
-        join: function(room_id) {
-            return this.membershipChange(room_id, undefined, "join");
-        },
-
-        joinAlias: function(room_alias) {
-            var path = "/join/$room_alias";
-            room_alias = encodeURIComponent(room_alias);
-
-            path = path.replace("$room_alias", room_alias);
-
-            // TODO: PUT with txn ID
-            return doRequest("POST", path, undefined, {});
-        },
-
-        // Invite a user to a room
-        invite: function(room_id, user_id) {
-            return this.membershipChange(room_id, user_id, "invite");
-        },
-
-        // Leaves a room
-        leave: function(room_id) {
-            return this.membershipChange(room_id, undefined, "leave");
-        },
-
-        membershipChange: function(room_id, user_id, membershipValue) {
-            // The REST path spec
-            var path = "/rooms/$room_id/$membership";
-            path = path.replace("$room_id", encodeURIComponent(room_id));
-            path = path.replace("$membership", encodeURIComponent(membershipValue));
-
-            var data = {};
-            if (user_id !== undefined) {
-                data = { user_id: user_id };
-            }
-
-            // TODO: Use PUT with transaction IDs
-            return doRequest("POST", path, undefined, data);
-        },
-
-        // Change the membership of an another user
-        setMembership: function(room_id, user_id, membershipValue, reason) {
-            
-            // The REST path spec
-            var path = "/rooms/$room_id/state/m.room.member/$user_id";
-            path = path.replace("$room_id", encodeURIComponent(room_id));
-            path = path.replace("$user_id", user_id);
-
-            return doRequest("PUT", path, undefined, {
-                membership : membershipValue,
-                reason: reason
-            });
-        },
-           
-        // Bans a user from a room
-        ban: function(room_id, user_id, reason) {
-            var path = "/rooms/$room_id/ban";
-            path = path.replace("$room_id", encodeURIComponent(room_id));
-            
-            return doRequest("POST", path, undefined, {
-                user_id: user_id,
-                reason: reason
-            });
-        },
-        
-        // Unbans a user in a room
-        unban: function(room_id, user_id) {
-            // FIXME: To update when there will be homeserver API for unban 
-            // For now, do an unban by resetting the user membership to "leave"
-            return this.setMembership(room_id, user_id, "leave");
-        },
-        
-        // Kicks a user from a room
-        kick: function(room_id, user_id, reason) {
-            // Set the user membership to "leave" to kick him
-            return this.setMembership(room_id, user_id, "leave", reason);
-        },
-        
-        // Retrieves the room ID corresponding to a room alias
-        resolveRoomAlias:function(room_alias) {
-            var path = "/_matrix/client/api/v1/directory/room/$room_alias";
-            room_alias = encodeURIComponent(room_alias);
-
-            path = path.replace("$room_alias", room_alias);
-
-            return doRequest("GET", path, undefined, {});
-        },
-        
-        setName: function(room_id, name) {
-            var data = {
-                name: name
-            };
-            return this.sendStateEvent(room_id, "m.room.name", data);
-        },
-        
-        setTopic: function(room_id, topic) {
-            var data = {
-                topic: topic
-            };
-            return this.sendStateEvent(room_id, "m.room.topic", data);
-        },
-        
-        
-        sendStateEvent: function(room_id, eventType, content, state_key) {
-            var path = "/rooms/$room_id/state/"+eventType;
-            if (state_key !== undefined) {
-                path += "/" + state_key;
-            }
-            room_id = encodeURIComponent(room_id);
-            path = path.replace("$room_id", room_id);
-
-            return doRequest("PUT", path, undefined, content);
-        },
-
-        sendEvent: function(room_id, eventType, txn_id, content) {
-            // The REST path spec
-            var path = "/rooms/$room_id/send/"+eventType+"/$txn_id";
-
-            if (!txn_id) {
-                txn_id = "m" + new Date().getTime();
-            }
-
-            // Like the cmd client, escape room ids
-            room_id = encodeURIComponent(room_id);            
-
-            // Customize it
-            path = path.replace("$room_id", room_id);
-            path = path.replace("$txn_id", txn_id);
-
-            return doRequest("PUT", path, undefined, content);
-        },
-
-        sendMessage: function(room_id, txn_id, content) {
-            return this.sendEvent(room_id, 'm.room.message', txn_id, content);
-        },
-
-        // Send a text message
-        sendTextMessage: function(room_id, body, msg_id) {
-            var content = {
-                 msgtype: "m.text",
-                 body: body
-            };
-
-            return this.sendMessage(room_id, msg_id, content);
-        },
-
-        // Send an image message
-        sendImageMessage: function(room_id, image_url, image_body, msg_id) {
-            var content = {
-                 msgtype: "m.image",
-                 url: image_url,
-                 body: image_body
-            };
-
-            return this.sendMessage(room_id, msg_id, content);
-        },
-
-        // Send an emote message
-        sendEmoteMessage: function(room_id, body, msg_id) {
-            var content = {
-                 msgtype: "m.emote",
-                 body: body
-            };
-
-            return this.sendMessage(room_id, msg_id, content);
-        },
-
-        redactEvent: function(room_id, event_id) {
-            var path = "/rooms/$room_id/redact/$event_id";
-            path = path.replace("$room_id", room_id);
-            path = path.replace("$event_id", event_id);
-            var content = {};
-            return doRequest("POST", path, undefined, content);
-        },
-
-        // get a snapshot of the members in a room.
-        getMemberList: function(room_id) {
-            // Like the cmd client, escape room ids
-            room_id = encodeURIComponent(room_id);
-
-            var path = "/rooms/$room_id/members";
-            path = path.replace("$room_id", room_id);
-            return doRequest("GET", path);
-        },
-        
-        paginateBackMessages: function(room_id, from_token, limit) {
-            var path = "/rooms/$room_id/messages";
-            path = path.replace("$room_id", room_id);
-            var params = {
-                from: from_token,
-                limit: limit,
-                dir: 'b'
-            };
-            return doRequest("GET", path, params);
-        },
-
-        // get a list of public rooms on your home server
-        publicRooms: function() {
-            var path = "/publicRooms";
-            return doRequest("GET", path);
-        },
-        
-        // get a user's profile
-        getProfile: function(userId) {
-            return this.getProfileInfo(userId);
-        },
-
-        // get a display name for this user ID
-        getDisplayName: function(userId) {
-            return this.getProfileInfo(userId, "displayname");
-        },
-
-        // get the profile picture url for this user ID
-        getProfilePictureUrl: function(userId) {
-            return this.getProfileInfo(userId, "avatar_url");
-        },
-
-        // update your display name
-        setDisplayName: function(newName) {
-            var content = {
-                displayname: newName
-            };
-            return this.setProfileInfo(content, "displayname");
-        },
-
-        // update your profile picture url
-        setProfilePictureUrl: function(newUrl) {
-            var content = {
-                avatar_url: newUrl
-            };
-            return this.setProfileInfo(content, "avatar_url");
-        },
-
-        setProfileInfo: function(data, info_segment) {
-            var path = "/profile/$user/" + info_segment;
-            path = path.replace("$user", config.user_id);
-            return doRequest("PUT", path, undefined, data);
-        },
-
-        getProfileInfo: function(userId, info_segment) {
-            var path = "/profile/"+userId
-            if (info_segment) path += '/' + info_segment;
-            return doRequest("GET", path);
-        },
-        
-        login: function(userId, password) {
-            // TODO We should be checking to make sure the client can support
-            // logging in to this HS, else use the fallback.
-            var path = "/login";
-            var data = {
-                "type": "m.login.password",
-                "user": userId,
-                "password": password  
-            };
-            return doRequest("POST", path, undefined, data);
-        },
-
-        // hit the Identity Server for a 3PID request.
-        linkEmail: function(email, clientSecret, sendAttempt) {
-            var path = "/_matrix/identity/api/v1/validate/email/requestToken";
-            var data = "clientSecret="+clientSecret+"&email=" + encodeURIComponent(email)+"&sendAttempt="+sendAttempt;
-            var headers = {};
-            headers["Content-Type"] = "application/x-www-form-urlencoded";
-            return doBaseRequest(config.identityServer, "POST", path, {}, data, headers); 
-        },
-
-        authEmail: function(clientSecret, sid, code) {
-            var path = "/_matrix/identity/api/v1/validate/email/submitToken";
-            var data = "token="+code+"&sid="+sid+"&clientSecret="+clientSecret;
-            var headers = {};
-            headers["Content-Type"] = "application/x-www-form-urlencoded";
-            return doBaseRequest(config.identityServer, "POST", path, {}, data, headers);
-        },
-
-        bindEmail: function(userId, tokenId, clientSecret) {
-            var path = "/_matrix/identity/api/v1/3pid/bind";
-            var data = "mxid="+encodeURIComponent(userId)+"&sid="+tokenId+"&clientSecret="+clientSecret;
-            var headers = {};
-            headers["Content-Type"] = "application/x-www-form-urlencoded";
-            return doBaseRequest(config.identityServer, "POST", path, {}, data, headers); 
-        },
-
-        lookup3pid: function(medium, address) {
-            var path = "/_matrix/identity/api/v1/lookup?medium="+encodeURIComponent(medium)+"&address="+encodeURIComponent(address);
-            return doBaseRequest(config.identityServer, "GET", path, {}, undefined, {}); 
-        },
-        
-        uploadContent: function(file) {
-            var path = "/_matrix/content";
-            var headers = {
-                "Content-Type": undefined // undefined means angular will figure it out
-            };
-            var params = {
-                access_token: config.access_token
-            };
-
-            // If the file is actually a Blob object, prevent $http from JSON-stringified it before sending
-            // (Equivalent to jQuery ajax processData = false)
-            var $httpParams;
-            if (file instanceof Blob) {
-                $httpParams = {
-                    transformRequest: angular.identity
-                };
-            }
-
-            return doBaseRequest(config.homeserver, "POST", path, params, file, headers, $httpParams);
-        },
-
-        /**
-         * Start listening on /events
-         * @param {String} from the token from which to listen events to
-         * @param {Integer} serverTimeout the time in ms the server will hold open the connection
-         * @param {Integer} clientTimeout the timeout in ms used at the client HTTP request level
-         * @returns a promise
-         */
-        getEventStream: function(from, serverTimeout, clientTimeout) {
-            var path = "/events";
-            var params = {
-                from: from,
-                timeout: serverTimeout
-            };
-
-            var $httpParams;
-            if (clientTimeout) {
-                // If the Internet connection is lost, this timeout is used to be able to
-                // cancel the current request and notify the client so that it can retry with a new request.
-                $httpParams = {
-                    timeout: clientTimeout
-                };
-            }
-
-            return doRequest("GET", path, params, undefined, $httpParams);
-        },
-
-        // Indicates if user authentications details are stored in cache
-        isUserLoggedIn: function() {
-            var config = this.config();
-
-            // User is considered logged in if his cache is not empty and contains
-            // an access token
-            if (config && config.access_token) {
-                return true;
-            }
-            else {
-                return false;
-            }
-        },
-        
-        // Enum of presence state
-        presence: {
-            offline: "offline",
-            unavailable: "unavailable",
-            online: "online",
-            free_for_chat: "free_for_chat"
-        },
-        
-        // Set the logged in user presence state
-        setUserPresence: function(presence) {
-            var path = "/presence/$user_id/status";
-            path = path.replace("$user_id", config.user_id);
-            return doRequest("PUT", path, undefined, {
-                presence: presence
-            });
-        },
-        
-        
-        /****** Permanent storage of user information ******/
-        
-        // Returns the current config
-        config: function() {
-            if (!config) {
-                config = localStorage.getItem("config");
-                if (config) {
-                    config = JSON.parse(config);
-
-                    // Reset the cache if the version loaded is not the expected one
-                    if (configVersion !== config.version) {
-                        config = undefined;
-                        this.saveConfig();
-                    }
-                }
-            }
-            return config;
-        },
-        
-        // Set a new config (Use saveConfig to actually store it permanently)
-        setConfig: function(newConfig) {
-            config = newConfig;
-            console.log("new IS: "+config.identityServer);
-        },
-        
-        // Commits config into permanent storage
-        saveConfig: function() {
-            config.version = configVersion;
-            localStorage.setItem("config", JSON.stringify(config));
-        },
-
-
-        /****** Room aliases management ******/
-
-        /**
-         * Get the room_alias & room_display_name which are computed from data 
-         * already retrieved from the server.
-         * @param {Room object} room one element of the array returned by the response
-         *  of rooms() and publicRooms()
-         * @returns {Object} {room_alias: "...", room_display_name: "..."}
-         */
-        getRoomAliasAndDisplayName: function(room) {
-            var result = {
-                room_alias: undefined,
-                room_display_name: undefined
-            };
-            var alias = this.getRoomIdToAliasMapping(room.room_id);
-            if (alias) {
-                // use the existing alias from storage
-                result.room_alias = alias;
-                result.room_display_name = alias;
-            }
-            // XXX: this only lets us learn aliases from our local HS - we should
-            // make the client stop returning this if we can trust m.room.aliases state events
-            else if (room.aliases && room.aliases[0]) {
-                // save the mapping
-                // TODO: select the smarter alias from the array
-                this.createRoomIdToAliasMapping(room.room_id, room.aliases[0]);
-                result.room_display_name = room.aliases[0];
-                result.room_alias = room.aliases[0];
-            }
-            else if (room.membership === "invite" && "inviter" in room) {
-                result.room_display_name = room.inviter + "'s room";
-            }
-            else {
-                // last resort use the room id
-                result.room_display_name = room.room_id;
-            }
-            return result;
-        },
-        
-        createRoomIdToAliasMapping: function(roomId, alias) {
-            roomIdToAlias[roomId] = alias;
-            aliasToRoomId[alias] = roomId;
-        },
-        
-        getRoomIdToAliasMapping: function(roomId) {
-            var alias = roomIdToAlias[roomId];
-            //console.log("looking for alias for " + roomId + "; found: " + alias);
-            return alias;
-        },
-
-        getAliasToRoomIdMapping: function(alias) {
-            var roomId = aliasToRoomId[alias];
-            //console.log("looking for roomId for " + alias + "; found: " + roomId);
-            return roomId;
-        },
-
-        /****** Power levels management ******/
-
-        /**
-         * Return the power level of an user in a particular room
-         * @param {String} room_id the room id
-         * @param {String} user_id the user id
-         * @returns {Number} a value between 0 and 10
-         */
-        getUserPowerLevel: function(room_id, user_id) {
-            var powerLevel = 0;
-            var room = $rootScope.events.rooms[room_id];
-            if (room && room["m.room.power_levels"]) {
-                if (user_id in room["m.room.power_levels"].content) {
-                    powerLevel = room["m.room.power_levels"].content[user_id];
-                }
-                else {
-                    // Use the room default user power
-                    powerLevel = room["m.room.power_levels"].content["default"];
-                }
-            }
-            return powerLevel;
-        },
-            
-        /**
-         * Change or reset the power level of a user
-         * @param {String} room_id the room id
-         * @param {String} user_id the user id
-         * @param {Number} powerLevel a value between 0 and 10
-         *    If undefined, the user power level will be reset, ie he will use the default room user power level
-         * @returns {promise} an $http promise
-         */
-        setUserPowerLevel: function(room_id, user_id, powerLevel) {
-            
-            // Hack: currently, there is no home server API so do it by hand by updating
-            // the current m.room.power_levels of the room and send it to the server
-            var room = $rootScope.events.rooms[room_id];
-            if (room && room["m.room.power_levels"]) {
-                var content = angular.copy(room["m.room.power_levels"].content);
-                content[user_id] = powerLevel;
-                
-                var path = "/rooms/$room_id/state/m.room.power_levels";
-                path = path.replace("$room_id", encodeURIComponent(room_id));
-                
-                return doRequest("PUT", path, undefined, content);
-            }
-            
-            // The room does not exist or does not contain power_levels data
-            var deferred = $q.defer();
-            deferred.reject({data:{error: "Invalid room: " + room_id}});
-            return deferred.promise;
-        },
-
-        getTurnServer: function() {
-            return doRequest("GET", "/voip/turnServer");
-        }
-
-    };
-}]);
diff --git a/webclient/components/matrix/notification-service.js b/webclient/components/matrix/notification-service.js
deleted file mode 100644
index 9a911413c3..0000000000
--- a/webclient/components/matrix/notification-service.js
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
-Copyright 2014 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';
-
-/*
-This service manages notifications: enabling, creating and showing them. This
-also contains 'bing word' logic.
-*/
-angular.module('notificationService', [])
-.factory('notificationService', ['$timeout', function($timeout) {
-
-    var getLocalPartFromUserId = function(user_id) {
-        if (!user_id) {
-            return null;
-        }
-        var localpartRegex = /@(.*):\w+/i
-        var results = localpartRegex.exec(user_id);
-        if (results && results.length == 2) {
-            return results[1];
-        }
-        return null;
-    };
-    
-    return {
-    
-        containsBingWord: function(userId, displayName, bingWords, content) {
-            // case-insensitive name check for user_id OR display_name if they exist
-            var userRegex = "";
-            if (userId) {
-                var localpart = getLocalPartFromUserId(userId);
-                if (localpart) {
-                    localpart = localpart.toLocaleLowerCase();
-                    userRegex += "\\b" + localpart + "\\b";
-                }
-            }
-            if (displayName) {
-                displayName = displayName.toLocaleLowerCase();
-                if (userRegex.length > 0) {
-                    userRegex += "|";
-                }
-                userRegex += "\\b" + displayName + "\\b";
-            }
-
-            var regexList = [new RegExp(userRegex, 'i')];
-            
-            // bing word list check
-            if (bingWords && bingWords.length > 0) {
-                for (var i=0; i<bingWords.length; i++) {
-                    var re = RegExp(bingWords[i], 'i');
-                    regexList.push(re);
-                }
-            }
-            return this.hasMatch(regexList, content);
-        },
-    
-        hasMatch: function(regExps, content) {
-            if (!content || $.type(content) != "string") {
-                return false;
-            }
-            
-            if (regExps && regExps.length > 0) {
-                for (var i=0; i<regExps.length; i++) {
-                    if (content.search(regExps[i]) != -1) {
-                        return true;
-                    }
-                }
-            }
-            return false;
-        },
-        
-        showNotification: function(title, body, icon, onclick) {
-            var notification = new window.Notification(
-                title,
-                {
-                    "body": body,
-                    "icon": icon
-                }
-            );
-
-            if (onclick) {
-                notification.onclick = onclick;
-            }
-
-            $timeout(function() {
-                notification.close();
-            }, 5 * 1000);
-        }
-    };
-
-}]);
diff --git a/webclient/components/matrix/presence-service.js b/webclient/components/matrix/presence-service.js
deleted file mode 100644
index b487e3d3bd..0000000000
--- a/webclient/components/matrix/presence-service.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- Copyright 2014 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';
-
-/*
- * This service tracks user activity on the page to determine his presence state.
- * Any state change will be sent to the Home Server.
- */
-angular.module('mPresence', [])
-.service('mPresence', ['$timeout', 'matrixService', function ($timeout, matrixService) {
-
-    // Time in ms after that a user is considered as unavailable/away
-    var UNAVAILABLE_TIME = 3 * 60000; // 3 mins
-   
-    // The current presence state
-    var state = undefined;
-
-    var self =this;
-    var timer;
-    
-    /**
-     * Start listening the user activity to evaluate his presence state.
-     * Any state change will be sent to the Home Server.
-     */
-    this.start = function() {
-        if (undefined === state) {
-            // The user is online if he moves the mouser or press a key
-            document.onmousemove = resetTimer;
-            document.onkeypress = resetTimer;
-            
-            resetTimer();
-        }
-    };
-    
-    /**
-     * Stop tracking user activity
-     */
-    this.stop = function() {
-        if (timer) {
-            $timeout.cancel(timer);
-            timer = undefined;
-        }
-        state = undefined;
-    };
-    
-    /**
-     * Get the current presence state.
-     * @returns {matrixService.presence} the presence state
-     */
-    this.getState = function() {
-        return state;
-    };
-    
-    /**
-     * Set the presence state.
-     * If the state has changed, the Home Server will be notified.
-     * @param {matrixService.presence} newState the new presence state
-     */
-    this.setState = function(newState) {
-        if (newState !== state) {
-            console.log("mPresence - New state: " + newState);
-
-            state = newState;
-
-            // Inform the HS on the new user state
-            matrixService.setUserPresence(state).then(
-                function() {
-
-                },
-                function(error) {
-                    console.log("mPresence - Failed to send new presence state: " + JSON.stringify(error));
-                });
-        }
-    };
-    
-    /**
-     * Callback called when the user made no action on the page for UNAVAILABLE_TIME ms.
-     * @private
-     */
-    function onUnvailableTimerFire() {
-        self.setState(matrixService.presence.unavailable);
-    }
-
-    /**
-     * Callback called when the user made an action on the page
-     * @private
-     */
-    function resetTimer() {
-        // User is still here
-        self.setState(matrixService.presence.online);
-        
-        // Re-arm the timer
-        $timeout.cancel(timer);
-        timer = $timeout(onUnvailableTimerFire, UNAVAILABLE_TIME);
-    }    
-
-}]);
-
-