summary refs log tree commit diff
path: root/webclient/components
diff options
context:
space:
mode:
Diffstat (limited to 'webclient/components')
-rw-r--r--webclient/components/fileUpload/file-upload-service.js140
-rw-r--r--webclient/components/matrix/event-handler-service.js26
-rw-r--r--webclient/components/matrix/event-stream-service.js52
-rw-r--r--webclient/components/matrix/matrix-service.js52
-rw-r--r--webclient/components/utilities/utilities-service.js151
5 files changed, 396 insertions, 25 deletions
diff --git a/webclient/components/fileUpload/file-upload-service.js b/webclient/components/fileUpload/file-upload-service.js
index d620e6a4d0..5f01478fd1 100644
--- a/webclient/components/fileUpload/file-upload-service.js
+++ b/webclient/components/fileUpload/file-upload-service.js
@@ -20,19 +20,20 @@
 /*
  * Upload an HTML5 file to a server
  */
-angular.module('mFileUpload', [])
-.service('mFileUpload', ['matrixService', '$q', function (matrixService, $q) {
+angular.module('mFileUpload', ['matrixService', 'mUtilities'])
+.service('mFileUpload', ['$q', 'matrixService', 'mUtilities', function ($q, matrixService, mUtilities) {
         
     /*
-     * Upload an HTML5 file to a server and returned a promise
+     * 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 = location.origin + "/matrix/content/" + response.data.content_token;
+                var content_url = response.data.content_token;
                 console.log("   -> Successfully uploaded! Available at " + content_url);
                 deferred.resolve(content_url);
             },
@@ -44,4 +45,135 @@ angular.module('mFileUpload', [])
         
         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: {
+                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.body = {
+                                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.body;
+                            }
+
+                            // 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
index b8529895fe..b5eb73d92b 100644
--- a/webclient/components/matrix/event-handler-service.js
+++ b/webclient/components/matrix/event-handler-service.js
@@ -35,6 +35,8 @@ angular.module('eventHandlerService', [])
     $rootScope.events = {
         rooms: {}, // will contain roomId: { messages:[], members:{userid1: event} }
     };
+
+    $rootScope.presence = {};
     
     var initRoom = function(room_id) {
         if (!(room_id in $rootScope.events.rooms)) {
@@ -44,6 +46,12 @@ angular.module('eventHandlerService', [])
             $rootScope.events.rooms[room_id].members = {};
         }
     }
+
+    var reInitRoom = function(room_id) {
+        $rootScope.events.rooms[room_id] = {};
+        $rootScope.events.rooms[room_id].messages = [];
+        $rootScope.events.rooms[room_id].members = {};
+    }
     
     var handleMessage = function(event, isLiveEvent) {
         if ("membership_target" in event.content) {
@@ -69,11 +77,23 @@ angular.module('eventHandlerService', [])
     
     var handleRoomMember = function(event, isLiveEvent) {
         initRoom(event.room_id);
+        
+        // add membership changes as if they were a room message if something interesting changed
+        if (event.content.prev !== event.content.membership) {
+            if (isLiveEvent) {
+                $rootScope.events.rooms[event.room_id].messages.push(event);
+            }
+            else {
+                $rootScope.events.rooms[event.room_id].messages.unshift(event);
+            }
+        }
+        
         $rootScope.events.rooms[event.room_id].members[event.user_id] = event;
         $rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent);
     };
     
     var handlePresence = function(event, isLiveEvent) {
+        $rootScope.presence[event.content.user_id] = event;
         $rootScope.$broadcast(PRESENCE_EVENT, event, isLiveEvent);
     };
     
@@ -107,6 +127,10 @@ angular.module('eventHandlerService', [])
             for (var i=0; i<events.length; i++) {
                 this.handleEvent(events[i], isLiveEvents);
             }
-        }
+        },
+
+        reInitRoom: function(room_id) {
+            reInitRoom(room_id);
+        },
     };
 }]);
diff --git a/webclient/components/matrix/event-stream-service.js b/webclient/components/matrix/event-stream-service.js
index c94cf0fe72..a1a98b2a36 100644
--- a/webclient/components/matrix/event-stream-service.js
+++ b/webclient/components/matrix/event-stream-service.js
@@ -25,7 +25,6 @@ the eventHandlerService.
 angular.module('eventStreamService', [])
 .factory('eventStreamService', ['$q', '$timeout', 'matrixService', 'eventHandlerService', function($q, $timeout, matrixService, eventHandlerService) {
     var END = "END";
-    var START = "START";
     var TIMEOUT_MS = 30000;
     var ERR_TIMEOUT_MS = 5000;
     
@@ -49,11 +48,12 @@ angular.module('eventStreamService', [])
     var saveStreamSettings = function() {
         localStorage.setItem("streamSettings", JSON.stringify(settings));
     };
-    
-    var startEventStream = function() {
+
+    var doEventStream = function(deferred) {
         settings.shouldPoll = true;
         settings.isActive = true;
-        var deferred = $q.defer();
+        deferred = deferred || $q.defer();
+
         // run the stream from the latest token
         matrixService.getEventStream(settings.from, TIMEOUT_MS).then(
             function(response) {
@@ -64,13 +64,16 @@ angular.module('eventStreamService', [])
                 
                 settings.from = response.data.end;
                 
-                console.log("[EventStream] Got response from "+settings.from+" to "+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(startEventStream, 0);
+                    $timeout(doEventStream, 0);
                 }
                 else {
                     console.log("[EventStream] Stopping poll.");
@@ -84,13 +87,48 @@ angular.module('eventStreamService', [])
                 deferred.reject(error);
                 
                 if (settings.shouldPoll) {
-                    $timeout(startEventStream, ERR_TIMEOUT_MS);
+                    $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();
+
+        // FIXME: We are discarding all the messages.
+        matrixService.rooms().then(
+            function(response) {
+                var rooms = response.data.rooms;
+                for (var i = 0; i < rooms.length; ++i) {
+                    var room = rooms[i];
+                    if ("state" in room) {
+                        for (var j = 0; j < room.state.length; ++j) {
+                            eventHandlerService.handleEvents(room.state[j], false);
+                        }
+                    }
+                }
+
+                var presence = response.data.presence;
+                for (var i = 0; i < presence.length; ++i) {
+                    eventHandlerService.handleEvent(presence[i], false);
+                }
+
+                settings.from = response.data.end
+                doEventStream(deferred);        
+            },
+            function(error) {
+                $scope.feedback = "Failure: " + error.data;
+            }
+        );
+
         return deferred.promise;
     };
     
diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js
index 2463b51203..d5738e01c8 100644
--- a/webclient/components/matrix/matrix-service.js
+++ b/webclient/components/matrix/matrix-service.js
@@ -61,16 +61,23 @@ angular.module('matrixService', [])
         return doBaseRequest(config.homeserver, method, path, params, data, undefined);
     };
 
-    var doBaseRequest = function(baseUrl, method, path, params, data, headers) {
-        return $http({
+    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);
+    };
 
     return {
         /****** Home server API ******/
@@ -204,11 +211,11 @@ angular.module('matrixService', [])
         },
 
         // Send an image message
-        sendImageMessage: function(room_id, image_url, image_alt, msg_id) {
+        sendImageMessage: function(room_id, image_url, image_body, msg_id) {
             var content = {
                  msgtype: "m.image",
                  url: image_url,
-                 body: image_alt
+                 body: image_body
             };
 
             return this.sendMessage(room_id, msg_id, content);
@@ -239,8 +246,8 @@ angular.module('matrixService', [])
             path = path.replace("$room_id", room_id);
             var params = {
                 from: from_token,
-                to: "START",
-                limit: limit
+                limit: limit,
+                dir: 'b'
             };
             return doRequest("GET", path, params);
         },
@@ -302,17 +309,25 @@ angular.module('matrixService', [])
         },
 
         // hit the Identity Server for a 3PID request.
-        linkEmail: function(email) {
+        linkEmail: function(email, clientSecret, sendAttempt) {
             var path = "/matrix/identity/api/v1/validate/email/requestToken"
-            var data = "clientSecret=abc123&email=" + encodeURIComponent(email);
+            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(userId, tokenId, code) {
+        authEmail: function(clientSecret, tokenId, code) {
             var path = "/matrix/identity/api/v1/validate/email/submitToken";
-            var data = "token="+code+"&mxId="+encodeURIComponent(userId)+"&tokenId="+tokenId;
+            var data = "token="+code+"&sid="+tokenId+"&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); 
@@ -326,7 +341,17 @@ angular.module('matrixService', [])
             var params = {
                 access_token: config.access_token
             };
-            return doBaseRequest(config.homeserver, "POST", path, params, file, headers);
+
+            // 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
@@ -375,6 +400,7 @@ angular.module('matrixService', [])
         // 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
diff --git a/webclient/components/utilities/utilities-service.js b/webclient/components/utilities/utilities-service.js
new file mode 100644
index 0000000000..3df2f04458
--- /dev/null
+++ b/webclient/components/utilities/utilities-service.js
@@ -0,0 +1,151 @@
+/*
+ Copyright 2014 matrix.org
+ 
+ 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