
+
);
});
},
diff --git a/src/CallHandler.js b/src/CallHandler.js
index 671d8278ff..0915a65af2 100644
--- a/src/CallHandler.js
+++ b/src/CallHandler.js
@@ -116,6 +116,10 @@ function _setCallListeners(call) {
_setCallState(call, call.roomId, "busy");
pause("ringbackAudio");
play("busyAudio");
+ Modal.createDialog(ErrorDialog, {
+ title: "Call Timeout",
+ description: "The remote side failed to pick up."
+ });
}
else if (oldState === "invite_sent") {
_setCallState(call, call.roomId, "stop_ringback");
diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js
index 0b6c26496f..1acd2a7015 100644
--- a/src/MatrixClientPeg.js
+++ b/src/MatrixClientPeg.js
@@ -22,18 +22,25 @@ var Matrix = require("matrix-js-sdk");
var matrixClient = null;
var localStorage = window.localStorage;
+
+function createClient(hs_url, is_url, user_id, access_token) {
+ var opts = {
+ baseUrl: hs_url,
+ idBaseUrl: is_url,
+ accessToken: access_token,
+ userId: user_id
+ };
+
+ matrixClient = Matrix.createClient(opts);
+}
+
if (localStorage) {
var hs_url = localStorage.getItem("mx_hs_url");
var is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org';
var access_token = localStorage.getItem("mx_access_token");
var user_id = localStorage.getItem("mx_user_id");
if (access_token && user_id && hs_url) {
- matrixClient = Matrix.createClient({
- baseUrl: hs_url,
- idBaseUrl: is_url,
- accessToken: access_token,
- userId: user_id
- });
+ createClient(hs_url, is_url, user_id, access_token);
}
}
@@ -42,8 +49,8 @@ module.exports = {
return matrixClient;
},
- replace: function(cli) {
- matrixClient = cli;
+ unset: function() {
+ matrixClient = null;
},
replaceUsingUrls: function(hs_url, is_url) {
@@ -51,6 +58,23 @@ module.exports = {
baseUrl: hs_url,
idBaseUrl: is_url
});
+ },
+
+ replaceUsingAccessToken: function(hs_url, is_url, user_id, access_token) {
+ createClient(hs_url, is_url, user_id, access_token);
+ if (localStorage) {
+ try {
+ localStorage.clear();
+ localStorage.setItem("mx_hs_url", hs_url);
+ localStorage.setItem("mx_is_url", is_url);
+ localStorage.setItem("mx_user_id", user_id);
+ localStorage.setItem("mx_access_token", access_token);
+ } catch (e) {
+ console.warn("Error using local storage: can't persist session!");
+ }
+ } else {
+ console.warn("No local storage available: can't persist session!");
+ }
}
};
diff --git a/src/Presence.js b/src/Presence.js
new file mode 100644
index 0000000000..ce1d5d10fc
--- /dev/null
+++ b/src/Presence.js
@@ -0,0 +1,105 @@
+/*
+Copyright 2015 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 MatrixClientPeg = require("./MatrixClientPeg");
+
+ // Time in ms after that a user is considered as unavailable/away
+var UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
+var PRESENCE_STATES = ["online", "offline", "unavailable"];
+
+// The current presence state
+var state, timer;
+
+module.exports = {
+
+ /**
+ * Start listening the user activity to evaluate his presence state.
+ * Any state change will be sent to the Home Server.
+ */
+ start: function() {
+ var self = this;
+ this.running = true;
+ if (undefined === state) {
+ // The user is online if they move the mouse or press a key
+ document.onmousemove = function() { self._resetTimer(); };
+ document.onkeypress = function() { self._resetTimer(); };
+ this._resetTimer();
+ }
+ },
+
+ /**
+ * Stop tracking user activity
+ */
+ stop: function() {
+ this.running = false;
+ if (timer) {
+ clearTimeout(timer);
+ timer = undefined;
+ }
+ state = undefined;
+ },
+
+ /**
+ * Get the current presence state.
+ * @returns {string} the presence state (see PRESENCE enum)
+ */
+ getState: function() {
+ return state;
+ },
+
+ /**
+ * Set the presence state.
+ * If the state has changed, the Home Server will be notified.
+ * @param {string} newState the new presence state (see PRESENCE enum)
+ */
+ setState: function(newState) {
+ if (newState === state) {
+ return;
+ }
+ if (PRESENCE_STATES.indexOf(newState) === -1) {
+ throw new Error("Bad presence state: " + newState);
+ }
+ if (!this.running) {
+ return;
+ }
+ state = newState;
+ MatrixClientPeg.get().setPresence(state).catch(function(err) {
+ console.error("Failed to set presence: %s", err);
+ });
+ },
+
+ /**
+ * Callback called when the user made no action on the page for UNAVAILABLE_TIME ms.
+ * @private
+ */
+ _onUnavailableTimerFire: function() {
+ this.setState("unavailable");
+ },
+
+ /**
+ * Callback called when the user made an action on the page
+ * @private
+ */
+ _resetTimer: function() {
+ var self = this;
+ this.setState("online");
+ // Re-arm the timer
+ clearTimeout(timer);
+ timer = setTimeout(function() {
+ self._onUnavailableTimerFire();
+ }, UNAVAILABLE_TIME_MS);
+ }
+};
\ No newline at end of file
diff --git a/src/controllers/molecules/MessageComposer.js b/src/controllers/molecules/MessageComposer.js
index 0fa8066595..a9de008d6f 100644
--- a/src/controllers/molecules/MessageComposer.js
+++ b/src/controllers/molecules/MessageComposer.js
@@ -106,7 +106,7 @@ module.exports = {
// show the message
this.element.value = this.data[this.position];
}
- else if (this.originalText) {
+ else if (this.originalText !== undefined) {
// restore the original text the user was typing.
this.element.value = this.originalText;
}
diff --git a/src/controllers/organisms/MemberList.js b/src/controllers/organisms/MemberList.js
index 6021d0fc9a..0695270066 100644
--- a/src/controllers/organisms/MemberList.js
+++ b/src/controllers/organisms/MemberList.js
@@ -18,6 +18,9 @@ limitations under the License.
var React = require("react");
var MatrixClientPeg = require("../../MatrixClientPeg");
+var Modal = require("../../Modal");
+var ComponentBroker = require('../../ComponentBroker');
+var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog");
var INITIAL_LOAD_NUM_MEMBERS = 50;
@@ -37,6 +40,7 @@ module.exports = {
componentWillUnmount: function() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
+ MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn);
}
},
@@ -48,8 +52,19 @@ module.exports = {
memberDict: self.roomMembers()
});
}, 50);
- },
+ // Attach a SINGLE listener for global presence changes then locate the
+ // member tile and re-render it. This is more efficient than every tile
+ // evar attaching their own listener.
+ function updateUserState(event, user) {
+ var tile = self.refs[user.userId];
+ if (tile) {
+ tile.forceUpdate();
+ }
+ }
+ MatrixClientPeg.get().on("User.presence", updateUserState);
+ this.userPresenceFn = updateUserState;
+ },
// Remember to set 'key' on a MemberList to the ID of the room it's for
/*componentWillReceiveProps: function(newProps) {
},*/
@@ -67,6 +82,10 @@ module.exports = {
inputText = inputText.trim(); // react requires es5-shim so we know trim() exists
if (inputText[0] !== '@' || inputText.indexOf(":") === -1) {
console.error("Bad user ID to invite: %s", inputText);
+ Modal.createDialog(ErrorDialog, {
+ title: "Invite Error",
+ description: "Malformed user ID. Should look like '@localpart:domain'"
+ });
return;
}
self.setState({
@@ -81,6 +100,10 @@ module.exports = {
});
}, function(err) {
console.error("Failed to invite: %s", JSON.stringify(err));
+ Modal.createDialog(ErrorDialog, {
+ title: "Invite Server Error",
+ description: err.message
+ });
self.setState({
inviting: false
});
diff --git a/src/controllers/pages/MatrixChat.js b/src/controllers/pages/MatrixChat.js
index 48c55fe090..1e38a2150c 100644
--- a/src/controllers/pages/MatrixChat.js
+++ b/src/controllers/pages/MatrixChat.js
@@ -21,7 +21,7 @@ var Loader = require("react-loader");
var MatrixClientPeg = require("../../MatrixClientPeg");
var RoomListSorter = require("../../RoomListSorter");
-
+var Presence = require("../../Presence");
var dis = require("../../dispatcher");
var ComponentBroker = require('../../ComponentBroker');
@@ -89,8 +89,9 @@ module.exports = {
window.localStorage.clear();
}
Notifier.stop();
+ Presence.stop();
MatrixClientPeg.get().removeAllListeners();
- MatrixClientPeg.replace(null);
+ MatrixClientPeg.unset();
break;
case 'start_registration':
if (this.state.logged_in) return;
@@ -187,6 +188,7 @@ module.exports = {
});
});
Notifier.start();
+ Presence.start();
cli.startClient();
},
diff --git a/src/controllers/templates/Login.js b/src/controllers/templates/Login.js
index 276692719d..37a957850a 100644
--- a/src/controllers/templates/Login.js
+++ b/src/controllers/templates/Login.js
@@ -75,27 +75,10 @@ module.exports = {
'user': formVals.username,
'password': formVals.password
}).done(function(data) {
- // XXX: we assume this means we're logged in, but there could be a next stage
- MatrixClientPeg.replace(Matrix.createClient({
- baseUrl: self.state.hs_url,
- idBaseUrl: self.state.is_url,
- userId: data.user_id,
- accessToken: data.access_token
- }));
- var localStorage = window.localStorage;
- if (localStorage) {
- try {
- localStorage.clear();
- localStorage.setItem("mx_hs_url", self.state.hs_url);
- localStorage.setItem("mx_is_url", self.state.is_url);
- localStorage.setItem("mx_user_id", data.user_id);
- localStorage.setItem("mx_access_token", data.access_token);
- } catch (e) {
- console.warn("Error using local storage: can't persist session!");
- }
- } else {
- console.warn("No local storage available: can't persist session!");
- }
+ MatrixClientPeg.replaceUsingAccessToken(
+ self.state.hs_url, self.state.is_url,
+ data.user_id, data.access_token
+ );
if (self.props.onLoggedIn) {
self.props.onLoggedIn();
}
diff --git a/src/controllers/templates/Register.js b/src/controllers/templates/Register.js
index 89a3872dce..faff4c66fc 100644
--- a/src/controllers/templates/Register.js
+++ b/src/controllers/templates/Register.js
@@ -259,20 +259,9 @@ module.exports = {
},
onRegistered: function(user_id, access_token) {
- MatrixClientPeg.replace(Matrix.createClient({
- baseUrl: this.state.hs_url,
- idBaseUrl: this.state.is_url,
- userId: user_id,
- accessToken: access_token
- }));
- var localStorage = window.localStorage;
- if (localStorage) {
- localStorage.setItem("mx_hs_url", this.state.hs_url);
- localStorage.setItem("mx_user_id", user_id);
- localStorage.setItem("mx_access_token", access_token);
- } else {
- console.warn("No local storage available: can't persist session!");
- }
+ MatrixClientPeg.replaceUsingAccessToken(
+ this.state.hs_url, this.state.is_url, user_id, access_token
+ );
if (this.props.onLoggedIn) {
this.props.onLoggedIn();
}