diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js
new file mode 100644
index 0000000000..f7411fd80a
--- /dev/null
+++ b/webclient/components/matrix/event-handler-service.js
@@ -0,0 +1,109 @@
+/*
+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 handles what should happen when you get an event. This service does
+not care where the event came from, it only needs enough context to be able to
+process them. Events may be coming from the event stream, the REST API (via
+direct GETs or via a pagination stream API), etc.
+
+Typically, this service will store events or broadcast them to any listeners
+(e.g. controllers) via $broadcast. Alternatively, it may update the $rootScope
+if typically all the $on method would do is update its own $scope.
+*/
+angular.module('eventHandlerService', [])
+.factory('eventHandlerService', ['matrixService', '$rootScope', function(matrixService, $rootScope) {
+ var MSG_EVENT = "MSG_EVENT";
+ var MEMBER_EVENT = "MEMBER_EVENT";
+ var PRESENCE_EVENT = "PRESENCE_EVENT";
+
+ $rootScope.events = {
+ rooms: {}, // will contain roomId: { messages:[], members:[] }
+ };
+
+ var initRoom = function(room_id) {
+ console.log("Creating new handler entry for " + 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) {
+ event.user_id = event.content.membership_target;
+ }
+ if (!(event.room_id in $rootScope.events.rooms)) {
+ initRoom(event.room_id);
+ }
+
+ if (isLiveEvent) {
+ $rootScope.events.rooms[event.room_id].messages.push(event);
+ }
+ else {
+ $rootScope.events.rooms[event.room_id].messages.unshift(event);
+ }
+
+ // TODO send delivery receipt if isLiveEvent
+
+ // $broadcast this, as controllers may want to do funky things such as
+ // scroll to the bottom, etc which cannot be expressed via simple $scope
+ // updates.
+ $rootScope.$broadcast(MSG_EVENT, event, isLiveEvent);
+ };
+
+ var handleRoomMember = function(event, isLiveEvent) {
+ $rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent);
+ };
+
+ var handlePresence = function(event, isLiveEvent) {
+ $rootScope.$broadcast(PRESENCE_EVENT, event, isLiveEvent);
+ };
+
+
+ return {
+ MSG_EVENT: MSG_EVENT,
+ MEMBER_EVENT: MEMBER_EVENT,
+ PRESENCE_EVENT: PRESENCE_EVENT,
+
+
+ handleEvent: function(event, isLiveEvent) {
+ switch(event.type) {
+ case "m.room.message":
+ handleMessage(event, isLiveEvent);
+ break;
+ case "m.room.member":
+ handleRoomMember(event, isLiveEvent);
+ break;
+ case "m.presence":
+ handlePresence(event, isLiveEvent);
+ break;
+ default:
+ console.log("Unable to handle event type " + event.type);
+ break;
+ }
+ },
+
+ // isLiveEvents determines whether notifications should be shown, whether
+ // messages get appended to the start/end of lists, etc.
+ handleEvents: function(events, isLiveEvents) {
+ for (var i=0; i<events.length; i++) {
+ this.handleEvent(events[i], isLiveEvents);
+ }
+ }
+ };
+}]);
diff --git a/webclient/components/matrix/event-stream-service.js b/webclient/components/matrix/event-stream-service.js
new file mode 100644
index 0000000000..9f678e8454
--- /dev/null
+++ b/webclient/components/matrix/event-stream-service.js
@@ -0,0 +1,131 @@
+/*
+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 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 START = "START";
+ var TIMEOUT_MS = 5000;
+ 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 startEventStream = function() {
+ settings.shouldPoll = true;
+ settings.isActive = true;
+ var deferred = $q.defer();
+ // run the stream from the latest token
+ matrixService.getEventStream(settings.from, 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(startEventStream, 0);
+ }
+ else {
+ console.log("[EventStream] Stopping poll.");
+ }
+ },
+ function(error) {
+ if (error.status == 403) {
+ settings.shouldPoll = false;
+ }
+
+ deferred.reject(error);
+
+ if (settings.shouldPoll) {
+ $timeout(startEventStream, ERR_TIMEOUT_MS);
+ }
+ else {
+ console.log("[EventStream] Stopping polling.");
+ }
+ }
+ );
+ 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-service.js b/webclient/components/matrix/matrix-service.js
index 6d66111469..0cc85db28e 100644
--- a/webclient/components/matrix/matrix-service.js
+++ b/webclient/components/matrix/matrix-service.js
@@ -16,6 +16,12 @@ 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) {
@@ -36,10 +42,16 @@ angular.module('matrixService', [])
var MAPPING_PREFIX = "alias_for_";
var doRequest = function(method, path, params, data) {
+ 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;
return doBaseRequest(config.homeserver, method, path, params, data, undefined);
@@ -297,6 +309,15 @@ angular.module('matrixService', [])
return doBaseRequest(config.identityServer, "POST", path, {}, data, headers);
},
+ // start listening on /events
+ getEventStream: function(from, timeout) {
+ var path = "/events";
+ var params = {
+ from: from,
+ timeout: timeout
+ };
+ return doRequest("GET", path, params);
+ },
//
testLogin: function() {
|