diff --git a/webclient/components/fileInput/file-input-directive.js b/webclient/components/fileInput/file-input-directive.js
deleted file mode 100644
index 9c849a140f..0000000000
--- a/webclient/components/fileInput/file-input-directive.js
+++ /dev/null
@@ -1,56 +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';
-
-/*
- * Transform an element into an image file input button.
- * Watch to the passed variable change. It will contain the selected HTML5 file object.
- */
-angular.module('mFileInput', [])
-.directive('mFileInput', function() {
- return {
- restrict: 'A',
- transclude: 'true',
- template: '<div ng-transclude></div><input ng-hide="true" type="file" accept="image/*"/>',
- scope: {
- selectedFile: '=mFileInput'
- },
-
- link: function(scope, element, attrs, ctrl) {
-
- // Check if HTML5 file selection is supported
- if (window.FileList) {
- element.bind("click", function() {
- element.find("input")[0].click();
- element.find("input").bind("change", function(e) {
- scope.selectedFile = this.files[0];
- scope.$apply();
- });
- });
- }
- else {
- setTimeout(function() {
- element.attr("disabled", true);
- element.attr("title", "The app uses the HTML5 File API to send files. Your browser does not support it.");
- }, 1);
- }
-
- // Change the mouse icon on mouseover on this element
- element.css("cursor", "pointer");
- }
- };
-});
\ No newline at end of file
diff --git a/webclient/components/fileUpload/file-upload-service.js b/webclient/components/fileUpload/file-upload-service.js
deleted file mode 100644
index b544e29509..0000000000
--- a/webclient/components/fileUpload/file-upload-service.js
+++ /dev/null
@@ -1,180 +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';
-
-// TODO determine if this is really required as a separate service to matrixService.
-/*
- * Upload an HTML5 file to a server
- */
-angular.module('mFileUpload', ['matrixService', 'mUtilities'])
-.service('mFileUpload', ['$q', 'matrixService', 'mUtilities', function ($q, matrixService, mUtilities) {
-
- /*
- * Upload an HTML5 file or blob to a server and returned a promise
- * that will provide the URL of the uploaded file.
- * @param {File|Blob} file the file data to send
- */
- this.uploadFile = function(file) {
- var deferred = $q.defer();
- console.log("Uploading " + file.name + "... to /_matrix/content");
- matrixService.uploadContent(file).then(
- function(response) {
- var content_url = response.data.content_token;
- console.log(" -> Successfully uploaded! Available at " + content_url);
- deferred.resolve(content_url);
- },
- function(error) {
- console.log(" -> Failed to upload " + file.name);
- deferred.reject(error);
- }
- );
-
- return deferred.promise;
- };
-
- /*
- * Upload an image file plus generate a thumbnail of it and upload it so that
- * we will have all information to fulfill an image message request data.
- * @param {File} imageFile the imageFile to send
- * @param {Integer} thumbnailSize the max side size of the thumbnail to create
- * @returns {promise} A promise that will be resolved by a image message object
- * ready to be send with the Matrix API
- */
- this.uploadImageAndThumbnail = function(imageFile, thumbnailSize) {
- var self = this;
- var deferred = $q.defer();
-
- console.log("uploadImageAndThumbnail " + imageFile.name + " - thumbnailSize: " + thumbnailSize);
-
- // The message structure that will be returned in the promise
- var imageMessage = {
- msgtype: "m.image",
- url: undefined,
- body: "Image",
- info: {
- size: undefined,
- w: undefined,
- h: undefined,
- mimetype: undefined
- },
- thumbnail_url: undefined,
- thumbnail_info: {
- size: undefined,
- w: undefined,
- h: undefined,
- mimetype: undefined
- }
- };
-
- // First, get the image size
- mUtilities.getImageSize(imageFile).then(
- function(size) {
- console.log("image size: " + JSON.stringify(size));
-
- // The final operation: send imageFile
- var uploadImage = function() {
- self.uploadFile(imageFile).then(
- function(url) {
- // Update message metadata
- imageMessage.url = url;
- imageMessage.info = {
- size: imageFile.size,
- w: size.width,
- h: size.height,
- mimetype: imageFile.type
- };
-
- // If there is no thumbnail (because the original image is smaller than thumbnailSize),
- // reuse the original image info for thumbnail data
- if (!imageMessage.thumbnail_url) {
- imageMessage.thumbnail_url = imageMessage.url;
- imageMessage.thumbnail_info = imageMessage.info;
- }
-
- // We are done
- deferred.resolve(imageMessage);
- },
- function(error) {
- console.log(" -> Can't upload image");
- deferred.reject(error);
- }
- );
- };
-
- // Create a thumbnail if the image size exceeds thumbnailSize
- if (Math.max(size.width, size.height) > thumbnailSize) {
- console.log(" Creating thumbnail...");
- mUtilities.resizeImage(imageFile, thumbnailSize).then(
- function(thumbnailBlob) {
-
- // Get its size
- mUtilities.getImageSize(thumbnailBlob).then(
- function(thumbnailSize) {
- console.log(" -> Thumbnail size: " + JSON.stringify(thumbnailSize));
-
- // Upload it to the server
- self.uploadFile(thumbnailBlob).then(
- function(thumbnailUrl) {
-
- // Update image message data
- imageMessage.thumbnail_url = thumbnailUrl;
- imageMessage.thumbnail_info = {
- size: thumbnailBlob.size,
- w: thumbnailSize.width,
- h: thumbnailSize.height,
- mimetype: thumbnailBlob.type
- };
-
- // Then, upload the original image
- uploadImage();
- },
- function(error) {
- console.log(" -> Can't upload thumbnail");
- deferred.reject(error);
- }
- );
- },
- function(error) {
- console.log(" -> Failed to get thumbnail size");
- deferred.reject(error);
- }
- );
-
- },
- function(error) {
- console.log(" -> Failed to create thumbnail: " + error);
- deferred.reject(error);
- }
- );
- }
- else {
- // No need of thumbnail
- console.log(" Thumbnail is not required");
- uploadImage();
- }
-
- },
- function(error) {
- console.log(" -> Failed to get image size");
- deferred.reject(error);
- }
- );
-
- return deferred.promise;
- };
-
-}]);
diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js
deleted file mode 100644
index 027c80a1b6..0000000000
--- a/webclient/components/matrix/event-handler-service.js
+++ /dev/null
@@ -1,603 +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 and broadcast them to any listeners
-(e.g. controllers) via $broadcast.
-*/
-angular.module('eventHandlerService', [])
-.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', 'mPresence', 'notificationService', 'modelService',
-function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService, modelService) {
- 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 = {};
-
- // TODO: Remove this and replace with modelService.User objects.
- $rootScope.presence = {};
-
- var initialSyncDeferred;
-
- var reset = function() {
- initialSyncDeferred = $q.defer();
-
- $rootScope.presence = {};
-
- eventMap = {};
- };
- reset();
-
- var resetRoomMessages = function(room_id) {
- var room = modelService.getRoom(room_id);
- room.events = [];
- };
-
- // Generic method to handle events data
- var handleRoomStateEvent = function(event, isLiveEvent, addToRoomMessages) {
- var room = modelService.getRoom(event.room_id);
- if (addToRoomMessages) {
- // some state events are displayed as messages, so add them.
- room.addMessageEvent(event, !isLiveEvent);
- }
-
- if (isLiveEvent) {
- // update the current room state with the latest state
- room.current_room_state.storeStateEvent(event);
- }
- else {
- var eventTs = event.origin_server_ts;
- var storedEvent = room.current_room_state.getStateEvent(event.type, event.state_key);
- if (storedEvent) {
- if (storedEvent.origin_server_ts < eventTs) {
- // the incoming event is newer, use it.
- room.current_room_state.storeStateEvent(event);
- }
- }
- }
- // TODO: handle old_room_state
- };
-
- var handleRoomCreate = function(event, isLiveEvent) {
- $rootScope.$broadcast(ROOM_CREATE_EVENT, event, isLiveEvent);
- };
-
- var handleRoomAliases = function(event, isLiveEvent) {
- matrixService.createRoomIdToAliasMapping(event.room_id, event.content.aliases[0]);
- };
-
- var displayNotification = function(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 = modelService.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 = modelService.getRoom(event.room_id);
- if (!roomTitle && theRoom.current_room_state.state("m.room.name") && theRoom.current_room_state.state("m.room.name").content) {
- roomTitle = theRoom.current_room_state.state("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);
- }
- );
- }
- }
- };
-
- 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;
- }
-
- // =======================
-
- var room = modelService.getRoom(event.room_id);
-
- if (event.user_id !== matrixService.config().user_id) {
- room.addMessageEvent(event, !isLiveEvent);
- displayNotification(event);
- }
- else {
- // we may have locally echoed this, so we should replace the event
- // instead of just adding.
- room.addOrReplaceMessageEvent(event, !isLiveEvent);
- }
-
- // TODO send delivery receipt if isLiveEvent
-
- $rootScope.$broadcast(MSG_EVENT, event, isLiveEvent);
- };
-
- var handleRoomMember = function(event, isLiveEvent, isStateEvent) {
- var room = modelService.getRoom(event.room_id);
-
- // did something change?
- var memberChanges = undefined;
- if (!isStateEvent) {
- // could be a membership change, display name change, etc.
- // Find out which one.
- 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;
- }
-
-
- // modify state before adding the message so it points to the right thing.
- // The events are copied to avoid referencing the same event when adding
- // the message (circular json structures)
- if (isStateEvent || isLiveEvent) {
- var newEvent = angular.copy(event);
- newEvent.cnt = event.content;
- room.current_room_state.storeStateEvent(newEvent);
- }
- else if (!isLiveEvent) {
- // mutate the old room state
- var oldEvent = angular.copy(event);
- oldEvent.cnt = event.content;
- if (event.prev_content) {
- // the m.room.member event we are handling is the NEW event. When
- // we keep going back in time, we want the PREVIOUS value for displaying
- // names/etc, hence the clobber here.
- oldEvent.cnt = event.prev_content;
- }
-
- if (event.changedKey === "membership" && event.content.membership === "join") {
- // join has a prev_content but it doesn't contain all the info unlike the join, so use that.
- oldEvent.cnt = event.content;
- }
-
- room.old_room_state.storeStateEvent(oldEvent);
- }
-
- // If there was a change we want to display, dump it in the message
- // list. This has to be done after room state is updated.
- if (memberChanges) {
- room.addMessageEvent(event, !isLiveEvent);
- }
-
-
-
- $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) {
- handleRoomStateEvent(event, isLiveEvent);
- $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);
- handleRoomStateEvent(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);
- handleRoomStateEvent(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') {
- var room = modelService.getRoom(event.room_id);
- room.addMessageEvent(event, !isLiveEvent);
- }
- };
-
- 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]) {
- var room = modelService.getRoom(event.room_id);
- // remove event from list of messages in this room.
- var eventList = room.events;
- 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;
- }
- }
-
- console.log("Redacted an event.");
- }
- }
-
- /**
- * 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 = modelService.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 = modelService.getRoom(room_id);
-
- for (var member_id in room.current_room_state.members) {
- if (room.current_room_state.members.hasOwnProperty(member_id) && member_id !== user_id) {
- var member2 = room.current_room_state.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);
- },
-
- handleEvent: function(event, isLiveEvent, isStateEvent) {
-
- // 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) {
- handleRoomStateEvent(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) {
- 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
- var room = modelService.getRoom(room_id);
- room.old_room_state.pagination_token = messages.end;
-
- }
- else {
- // InitialSync returns messages in chronological order, so invert
- // it to get most recent > oldest
- for (var i=events.length - 1; i>=0; i--) {
- this.handleEvent(events[i], isLiveEvents, isLiveEvents);
- }
- // Store where to start pagination
- var room = modelService.getRoom(room_id);
- room.old_room_state.pagination_token = messages.start;
- }
- },
-
- handleInitialSyncDone: function(response) {
- console.log("# handleInitialSyncDone");
-
- var rooms = response.data.rooms;
- for (var i = 0; i < rooms.length; ++i) {
- var room = rooms[i];
-
- // FIXME: This is ming: the HS should be sending down the m.room.member
- // event for the invite in .state but it isn't, so fudge it for now.
- if (room.inviter && room.membership === "invite") {
- var me = matrixService.config().user_id;
- var fakeEvent = {
- event_id: "__FAKE__" + room.room_id,
- user_id: room.inviter,
- origin_server_ts: 0,
- room_id: room.room_id,
- state_key: me,
- type: "m.room.member",
- content: {
- membership: "invite"
- }
- };
- if (!room.state) {
- room.state = [];
- }
- room.state.push(fakeEvent);
- console.log("RECV /initialSync invite >> "+room.room_id);
- }
-
- var newRoom = modelService.getRoom(room.room_id);
- newRoom.current_room_state.storeStateEvents(room.state);
- newRoom.old_room_state.storeStateEvents(room.state);
-
- // this should be done AFTER storing state events since these
- // messages may make the old_room_state diverge.
- if ("messages" in room) {
- this.handleRoomMessages(room.room_id, room.messages, false);
- newRoom.current_room_state.pagination_token = room.messages.end;
- newRoom.old_room_state.pagination_token = room.messages.start;
- }
- }
- var presence = response.data.presence;
- this.handleEvents(presence, false);
-
- initialSyncDeferred.resolve(response);
- },
-
- // 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 events = modelService.getRoom(room_id).events;
- for (var i = events.length - 1; i >= 0; i--) {
- var message = events[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 = modelService.getRoom(room_id);
- memberCount = 0;
- for (var i in room.current_room_state.members) {
- if (!room.current_room_state.members.hasOwnProperty(i)) continue;
-
- var member = room.current_room_state.members[i];
-
- if ("join" === member.content.membership) {
- memberCount = memberCount + 1;
- }
- }
-
- return memberCount;
- },
-
- /**
- * 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 = modelService.getRoom(room_id).current_room_state;
- if (room.state("m.room.power_levels")) {
- if (user_id in room.state("m.room.power_levels").content) {
- powerLevel = room.state("m.room.power_levels").content[user_id];
- }
- else {
- // Use the room default user power
- powerLevel = room.state("m.room.power_levels").content["default"];
- }
- }
- return powerLevel;
- },
-
- /**
- * 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);
- }
- };
-}]);
diff --git a/webclient/components/matrix/event-stream-service.js b/webclient/components/matrix/event-stream-service.js
deleted file mode 100644
index c03f0b953b..0000000000
--- a/webclient/components/matrix/event-stream-service.js
+++ /dev/null
@@ -1,160 +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) {
- 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 5a2807c755..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', 'modelService', '$rootScope', '$timeout', function MatrixCallFactory(matrixService, matrixPhoneService, modelService, $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 = modelService.getRoom(this.room_id).current_room_state.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 4d264e93f3..0000000000
--- a/webclient/components/matrix/matrix-filter.js
+++ /dev/null
@@ -1,108 +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
-// TODO: It would be nice if this was stateless and had no dependencies. That would
-// make the business logic here a lot easier to see.
-.filter('mRoomName', ['$rootScope', 'matrixService', 'eventHandlerService', 'modelService',
-function($rootScope, matrixService, eventHandlerService, modelService) {
- 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 = modelService.getRoom(room_id).current_room_state;
-
- var room_name_event = room.state("m.room.name");
-
- // Determine if it is a public room
- var isPublicRoom = false;
- if (room.state("m.room.join_rules") && room.state("m.room.join_rules").content) {
- isPublicRoom = ("public" === room.state("m.room.join_rules").content.join_rule);
- }
-
- if (room_name_event) {
- roomName = room_name_event.content.name;
- }
- else if (alias) {
- roomName = alias;
- }
- else if (Object.keys(room.members).length > 0 && !isPublicRoom) { // Do not rename public room
- var user_id = matrixService.config().user_id;
-
- // this is a "one to one" room and should have the name of the other user.
- if (Object.keys(room.members).length === 2) {
- 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) {
- // this could be just us (self-chat) or could be the other person
- // in a room if they have invited us to the room. Find out which.
- var otherUserId = Object.keys(room.members)[0];
- if (otherUserId === user_id) {
- // it's us, we may have been invited to this room or it could
- // be a self chat.
- if (room.members[otherUserId].content.membership === "invite") {
- // someone invited us, use the right ID.
- roomName = eventHandlerService.getUserDisplayName(room_id, room.members[otherUserId].user_id);
- }
- else {
- roomName = eventHandlerService.getUserDisplayName(room_id, otherUserId);
- }
- }
- else { // it isn't us, so use their name if we know it.
- roomName = eventHandlerService.getUserDisplayName(room_id, otherUserId);
- }
- }
- else if (Object.keys(room.members).length === 0) {
- // this shouldn't be possible
- console.error("0 members in room >> " + room_id);
- }
- }
-
-
- // 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;
- }
-
- 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 fedfb8910d..0000000000
--- a/webclient/components/matrix/matrix-service.js
+++ /dev/null
@@ -1,759 +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,
- info: image_body,
- body: "Image"
- };
-
- 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;
- },
-
- /**
- * 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 The desired power level.
- * If undefined, the user power level will be reset, ie he will use the default room user power level
- * @param event The existing m.room.power_levels event if one exists.
- * @returns {promise} an $http promise
- */
- setUserPowerLevel: function(room_id, user_id, powerLevel, event) {
- var content = {};
- if (event) {
- // if there is an existing event, copy the content as it contains
- // the power level values for other members which we do not want
- // to modify.
- content = angular.copy(event.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);
- },
-
- getTurnServer: function() {
- return doRequest("GET", "/voip/turnServer");
- }
-
- };
-}]);
diff --git a/webclient/components/matrix/model-service.js b/webclient/components/matrix/model-service.js
deleted file mode 100644
index 8b2ee877b1..0000000000
--- a/webclient/components/matrix/model-service.js
+++ /dev/null
@@ -1,170 +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 serves as the entry point for all models in the app. If access to
-underlying data in a room is required, then this service should be used as the
-dependency.
-*/
-// NB: This is more explicit than linking top-level models to $rootScope
-// in that by adding this service as a dep you are clearly saying "this X
-// needs access to the underlying data store", rather than polluting the
-// $rootScope.
-angular.module('modelService', [])
-.factory('modelService', ['matrixService', function(matrixService) {
-
- /***** Room Object *****/
- var Room = function Room(room_id) {
- this.room_id = room_id;
- this.old_room_state = new RoomState();
- this.current_room_state = new RoomState();
- this.events = []; // events which can be displayed on the UI. TODO move?
- };
- Room.prototype = {
- addMessageEvents: function addMessageEvents(events, toFront) {
- for (var i=0; i<events.length; i++) {
- this.addMessageEvent(events[i], toFront);
- }
- },
-
- addMessageEvent: function addMessageEvent(event, toFront) {
- // every message must reference the RoomMember which made it *at
- // that time* so things like display names display correctly.
- var stateAtTheTime = toFront ? this.old_room_state : this.current_room_state;
- event.__room_member = stateAtTheTime.getStateEvent("m.room.member", event.user_id);
- if (event.type === "m.room.member" && event.content.membership === "invite") {
- // give information on both the inviter and invitee
- event.__target_room_member = stateAtTheTime.getStateEvent("m.room.member", event.state_key);
- }
-
- if (toFront) {
- this.events.unshift(event);
- }
- else {
- this.events.push(event);
- }
- },
-
- addOrReplaceMessageEvent: function addOrReplaceMessageEvent(event, toFront) {
- // Start looking from the tail since the first goal of this function
- // is to find a message among the latest ones
- for (var i = this.events.length - 1; i >= 0; i--) {
- var storedEvent = this.events[i];
- if (storedEvent.event_id === event.event_id) {
- // It's clobbering time!
- this.events[i] = event;
- return;
- }
- }
- this.addMessageEvent(event, toFront);
- },
-
- leave: function leave() {
- return matrixService.leave(this.room_id);
- }
- };
-
- /***** Room State Object *****/
- var RoomState = function RoomState() {
- // list of RoomMember
- this.members = {};
- // state events, the key is a compound of event type + state_key
- this.state_events = {};
- this.pagination_token = "";
- };
- RoomState.prototype = {
- // get a state event for this room from this.state_events. State events
- // are unique per type+state_key tuple, with a lot of events using 0-len
- // state keys. To make it not Really Annoying to access, this method is
- // provided which can just be given the type and it will return the
- // 0-len event by default.
- state: function state(type, state_key) {
- if (!type) {
- return undefined; // event type MUST be specified
- }
- if (!state_key) {
- return this.state_events[type]; // treat as 0-len state key
- }
- return this.state_events[type + state_key];
- },
-
- storeStateEvent: function storeState(event) {
- this.state_events[event.type + event.state_key] = event;
- if (event.type === "m.room.member") {
- this.members[event.state_key] = event;
- }
- },
-
- storeStateEvents: function storeState(events) {
- if (!events) {
- return;
- }
- for (var i=0; i<events.length; i++) {
- this.storeStateEvent(events[i]);
- }
- },
-
- getStateEvent: function getStateEvent(event_type, state_key) {
- return this.state_events[event_type + state_key];
- }
- };
-
- /***** Room Member Object *****/
- var RoomMember = function RoomMember() {
- this.event = {}; // the m.room.member event representing the RoomMember.
- this.user = undefined; // the User
- };
-
- /***** User Object *****/
- var User = function User() {
- this.event = {}; // the m.presence event representing the User.
- };
-
- // rooms are stored here when they come in.
- var rooms = {
- // roomid: <Room>
- };
-
- console.log("Models inited.");
-
- return {
-
- getRoom: function(roomId) {
- if(!rooms[roomId]) {
- rooms[roomId] = new Room(roomId);
- }
- return rooms[roomId];
- },
-
- getRooms: function() {
- return rooms;
- },
-
- /**
- * 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) {
- var room = this.getRoom(room_id);
- return room.current_room_state.members[user_id];
- }
-
- };
-}]);
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);
- }
-
-}]);
-
-
diff --git a/webclient/components/utilities/utilities-service.js b/webclient/components/utilities/utilities-service.js
deleted file mode 100644
index b417cc5b39..0000000000
--- a/webclient/components/utilities/utilities-service.js
+++ /dev/null
@@ -1,151 +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 contains multipurpose helper functions.
- */
-angular.module('mUtilities', [])
-.service('mUtilities', ['$q', function ($q) {
- /*
- * Get the size of an image
- * @param {File|Blob} imageFile the file containing the image
- * @returns {promise} A promise that will be resolved by an object with 2 members:
- * width & height
- */
- this.getImageSize = function(imageFile) {
- var deferred = $q.defer();
-
- // Load the file into an html element
- var img = document.createElement("img");
-
- var reader = new FileReader();
- reader.onload = function(e) {
- img.src = e.target.result;
-
- // Once ready, returns its size
- img.onload = function() {
- deferred.resolve({
- width: img.width,
- height: img.height
- });
- };
- img.onerror = function(e) {
- deferred.reject(e);
- };
- };
- reader.onerror = function(e) {
- deferred.reject(e);
- };
- reader.readAsDataURL(imageFile);
-
- return deferred.promise;
- };
-
- /*
- * Resize the image to fit in a square of the side maxSize.
- * The aspect ratio is kept. The returned image data uses JPEG compression.
- * Source: http://hacks.mozilla.org/2011/01/how-to-develop-a-html5-image-uploader/
- * @param {File} imageFile the file containing the image
- * @param {Integer} maxSize the max side size
- * @returns {promise} A promise that will be resolved by a Blob object containing
- * the resized image data
- */
- this.resizeImage = function(imageFile, maxSize) {
- var self = this;
- var deferred = $q.defer();
-
- var canvas = document.createElement("canvas");
-
- var img = document.createElement("img");
- var reader = new FileReader();
- reader.onload = function(e) {
-
- img.src = e.target.result;
-
- // Once ready, returns its size
- img.onload = function() {
- var ctx = canvas.getContext("2d");
- ctx.drawImage(img, 0, 0);
-
- var MAX_WIDTH = maxSize;
- var MAX_HEIGHT = maxSize;
- var width = img.width;
- var height = img.height;
-
- if (width > height) {
- if (width > MAX_WIDTH) {
- height *= MAX_WIDTH / width;
- width = MAX_WIDTH;
- }
- } else {
- if (height > MAX_HEIGHT) {
- width *= MAX_HEIGHT / height;
- height = MAX_HEIGHT;
- }
- }
- canvas.width = width;
- canvas.height = height;
- var ctx = canvas.getContext("2d");
- ctx.drawImage(img, 0, 0, width, height);
-
- // Extract image data in the same format as the original one.
- // The 0.7 compression value will work with formats that supports it like JPEG.
- var dataUrl = canvas.toDataURL(imageFile.type, 0.7);
- deferred.resolve(self.dataURItoBlob(dataUrl));
- };
- img.onerror = function(e) {
- deferred.reject(e);
- };
- };
- reader.onerror = function(e) {
- deferred.reject(e);
- };
- reader.readAsDataURL(imageFile);
-
- return deferred.promise;
- };
-
- /*
- * Convert a dataURI string to a blob
- * Source: http://stackoverflow.com/a/17682951
- * @param {String} dataURI the dataURI can be a base64 encoded string or an URL encoded string.
- * @returns {Blob} the blob
- */
- this.dataURItoBlob = function(dataURI) {
- // convert base64 to raw binary data held in a string
- // doesn't handle URLEncoded DataURIs
- var byteString;
- if (dataURI.split(',')[0].indexOf('base64') >= 0)
- byteString = atob(dataURI.split(',')[1]);
- else
- byteString = unescape(dataURI.split(',')[1]);
- // separate out the mime component
- var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
-
- // write the bytes of the string to an ArrayBuffer
- var ab = new ArrayBuffer(byteString.length);
- var ia = new Uint8Array(ab);
- for (var i = 0; i < byteString.length; i++) {
- ia[i] = byteString.charCodeAt(i);
- }
-
- // write the ArrayBuffer to a blob, and you're done
- return new Blob([ab],{type: mimeString});
- };
-
-}]);
\ No newline at end of file
|