diff options
author | Erik Johnston <erik@matrix.org> | 2014-08-22 15:50:23 +0100 |
---|---|---|
committer | Erik Johnston <erik@matrix.org> | 2014-08-22 15:50:23 +0100 |
commit | e1297c922d7f88eed088b365bd6cf15951443506 (patch) | |
tree | 57fb71ff724a4046d3a8e3ea4ce3de4735b9dd69 /webclient/components | |
parent | Added initial swagger REST API spec. (diff) | |
parent | Merge branch 'develop' of github.com:matrix-org/synapse into release-v0.0.1 (diff) | |
download | synapse-e1297c922d7f88eed088b365bd6cf15951443506.tar.xz |
Merge branch 'release-v0.0.1' of github.com:matrix-org/synapse
Diffstat (limited to 'webclient/components')
-rw-r--r-- | webclient/components/fileUpload/file-upload-service.js | 140 | ||||
-rw-r--r-- | webclient/components/matrix/event-handler-service.js | 26 | ||||
-rw-r--r-- | webclient/components/matrix/event-stream-service.js | 52 | ||||
-rw-r--r-- | webclient/components/matrix/matrix-service.js | 52 | ||||
-rw-r--r-- | webclient/components/utilities/utilities-service.js | 151 |
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 |