better error handling and reporting

This commit is contained in:
or-else
2017-09-17 18:36:01 -07:00
parent d2a226852b
commit 8f13d2c856
3 changed files with 201 additions and 102 deletions
+159 -90
View File
@@ -15,9 +15,9 @@
<!-- Styles for local elements -->
<link rel="stylesheet" href="css/base.css" />
<!-- ReactJs scripts -->
<script src="https://unpkg.com/react@15.4/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.4/dist/react-dom.js"></script>
<script src="https://unpkg.com/babel-standalone@6.24.0/babel.min.js"></script>
<script src="https://unpkg.com/react@15/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.min.js"></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<script src="tinode.js"></script>
<script src="drafty.js"></script>
</head>
@@ -133,7 +133,7 @@
/* BEGIN Context Menu: popup/dropdown menu */
// Function is used by context menu to set permissions.
function topicPermissionSetter(mode, topicName, uid) {
function topicPermissionSetter(mode, topicName, uid, errorHandler) {
var topic = Tinode.getTopic(topicName);
if (!topic) {
console.log("Topic not found: " + topicName);
@@ -151,7 +151,14 @@
} else {
am = topic.getAccessMode().updateWant(mode).getWant();
}
topic.setMeta({sub: {user: uid, mode: am}});
var instance = this;
topic.setMeta({sub: {user: uid, mode: am}}).catch(function(err) {
if (errorHandler) {
errorHandler(err.message, "err");
} else {
console.log(err);
}
});
}
// Context menu items.
@@ -164,6 +171,7 @@
console.log("Topic not found: " + topicName);
return;
}
topic.delMessagesAll(false).then(function(){
console.log("refresh message list");
});
@@ -232,7 +240,7 @@
e.stopPropagation();
this.props.hide();
var item = this.props.items[e.currentTarget.dataset.id];
item.handler(this.props.topic, this.props.user);
item.handler(this.props.topic, this.props.user, this.props.onError);
},
render: function() {
@@ -280,13 +288,17 @@
/* END Popup/dropdown menu */
/* The X menu to be displayed in title bars */
var MenuCancel = React.createClass({
render: function() {
class MenuCancel extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<a href="#" onClick={this.props.onCancel}><i className="material-icons">close</i></a>
);
}
});
}
class LoadSpinner extends React.PureComponent {
render() {
@@ -714,31 +726,20 @@
login: this.props.login,
password: '',
hostName: this.props.serverAddress,
errorCleared: false
};
},
clearError: function() {
if (!this.state.errorCleared) {
this.props.onClearError();
this.setState({errorCleared: true});
}
},
handleLoginChange: function(e) {
this.clearError();
this.setState({login: e.target.value});
},
handlePasswordChange: function(e) {
this.clearError();
this.setState({password: e.target.value});
},
handleServerAddressChange: function(name) {
this.clearError();
this.setState({hostName: name});
this.props.onServerAddressChange(name);
},
handleSubmit: function(e) {
e.preventDefault();
this.setState({errorCleared: false});
this.props.onLogin(this.state.login.trim(), this.state.password.trim());
},
@@ -1000,20 +1001,11 @@
</div>
);
}
})
});
var SidepanelView = React.createClass({
getInitialState: function() {
return {
showErrorText: true
};
},
handleClearErrorText: function() {
this.setState({showErrorText: false});
},
handleLoginRequested: function(login, password) {
this.props.onLoginRequest(login, password);
this.setState({showErrorText: true});
},
render: function() {
var title = null;
@@ -1046,17 +1038,16 @@
onNewTopic={this.props.onNewTopic}
onCancel={this.props.onCancel} />
<SidepanelView.ErrorPanel
showError={this.state.showErrorText}
errorText={this.props.errorText} />
<ErrorPanel
level={this.props.errorLevel}
text={this.props.errorText} />
{this.props.state === 'login' ?
<LoginView login={this.props.login}
disabled={this.props.loginDisabled}
serverAddress={this.props.serverAddress}
onLogin={this.handleLoginRequested}
onServerAddressChange={this.props.onConnectionSettings}
onClearError={this.handleClearErrorText} /> :
onServerAddressChange={this.props.onConnectionSettings} /> :
this.props.state === 'register' ?
<CreateAccountView
@@ -1095,16 +1086,31 @@
}
});
SidepanelView.ErrorPanel = React.createClass({
var ErrorPanel = React.createClass({
getInitialState: function() {
return {
show: false,
};
},
componentWillReceiveProps: function(newProps) {
this.setState({show: !(!newProps.level)});
},
hide: function() {
this.setState({show: false});
},
render: function() {
var errorClass = null;
var errorText = ' ';
if (this.props.errorText && this.props.showError) {
errorClass = "error-alert";
errorText = this.props.errorText;
}
var icon = this.props.level == "err" ? "error": "warning";
return (
<div id="alert-box" className={errorClass}>{errorText}</div>
<div className={this.state.show ?
(this.props.level == "err" ? "alert-box error" : "alert-box warning") :
"alert-box"}>
<div className="icon"><i className="material-icons">{icon}</i></div>
{this.props.text}
<div className="cancel"><MenuCancel onCancel={this.hide} /></div>
</div>
);
}
});
@@ -1891,8 +1897,6 @@
resetDesc: function(topic) {
var defacs = topic.getDefaultAccess() || {};
var acs = topic.getAccessMode();
console.log("InfoView: reset desc");
console.log(topic.public);
this.setState({
owner: acs && acs.isOwner(),
admin: acs && acs.isAdmin(),
@@ -2104,6 +2108,10 @@
<MenuCancel onCancel={this.props.onCancel} />
</div>
</div>
{this.props.displayMobile ?
<ErrorPanel
level={this.props.errorLevel}
text={this.props.errorText} /> : null}
{this.state.showMemberPanel ?
<GroupManager
members={this.state.contactList}
@@ -2381,6 +2389,7 @@
this.propsChange(nextProps);
},
propsChange: function(props) {
var instance = this;
if (!props || !props.topic) {
this.setState({messages: [], topic: null});
this.leave();
@@ -2405,7 +2414,9 @@
.withLaterDesc()
.withLaterSub()
.withLaterData(MESSAGES_PAGE)
.build());
.build()).catch(function(err){
instance.props.onError(err.message, "err");
});
}
// The user switched to the new topic before the timer for
@@ -2425,7 +2436,10 @@
var oldTopic = Tinode.getTopic(this.state.topic);
if (oldTopic) {
if (oldTopic.isSubscribed()) {
oldTopic.leave(false);
oldTopic.leave(false).catch(function(err) {
// do nothing
console.log(err);
});
}
oldTopic.onData = undefined;
oldTopic.onInfo = undefined;
@@ -2454,6 +2468,9 @@
newState.fetchingMessages = true;
topic.getMessagesPage(MESSAGES_PAGE).then(function() {
instance.setState({fetchingMessages: false});
}).catch(function(err) {
instance.setState({fetchingMessages: false});
instance.props.onError(err.message, "err");
});
}
}
@@ -2623,6 +2640,10 @@
</a>
</div>
</div>
{this.props.displayMobile ?
<ErrorPanel
level={this.props.errorLevel}
text={this.props.errorText} /> : null}
<LoadSpinner show={this.state.fetchingMessages} />
<div id="messages-panel" ref={this.handleScrollReference}>
<ul id="scroller" className={chatBoxClass}>
@@ -2770,6 +2791,8 @@
password: '',
myUserId: null,
errorText: '',
errorLevel: null,
liveConnection: navigator.onLine,
topicSelected: '',
loginDisabled: false,
subscriptions: {},
@@ -2788,7 +2811,11 @@
};
},
componentDidMount: function() {
var instance = this;
window.addEventListener('resize', this.handleResize);
window.addEventListener('online', function(e) { instance.handleOnline(true); });
window.addEventListener('offline', function(e) { instance.handleOnline(false); });
Tinode.enableLogging(true, true);
Tinode.onConnect = this.handleConnected;
Tinode.onDisconnect = this.handleDisconnect;
@@ -2798,25 +2825,39 @@
tnSetup: function(serverAddress, transport) {
Tinode.setup(APP_NAME, serverAddress, API_KEY, transport);
},
componentWillUnmount: function(){
window.removeEventListener('resize', this.handleResize);
},
handleResize: function(){
handleResize: function() {
var mobile = document.documentElement.clientWidth <= MEDIA_BREAKPOINT;
if (this.state.displayMobile != mobile) {
this.setState({displayMobile: mobile});
}
},
handleOnline: function(online) {
var newState = {liveConnection: online};
if (online) {
this.handleError("", null);
} else {
this.handleError("No connection", "warn");
}
this.setState({liveConnection: online});
},
handleError: function(err, level) {
this.setState({errorText: err, errorLevel: level});
},
// User clicked Login button in the side panel.
handleLoginRequest: function(login, password) {
var instance = this;
this.setState({loginDisabled: true, errorText: '', login: login, password: password});
this.setState({loginDisabled: true, login: login, password: password});
this.handleError("", null);
if (Tinode.isConnected()) {
this.doLogin(login, password);
} else {
Tinode.connect().catch(function(err) {
// Socket error
instance.setState({loginDisabled: false, errorText: err.message});
instance.setState({loginDisabled: false});
instance.handleError(err.message, "err");
});
}
},
@@ -2844,39 +2885,33 @@
if (promise) {
promise.then(function() {
instance.handleLoginSuccessful(instance);
})
.catch(function(err) {
}).catch(function(err) {
// Login failed, report error
instance.setState({loginDisabled: false, errorText: err.message});
instance.setState({loginDisabled: false});
instance.handleError(err.message, "err");
});
}
},
handleLoginSuccessful: function(instance) {
instance.setState({errorText: ''});
instance.handleError("", null);
// Logged in fine, subscribe to 'me' attaching callbacks from the contacts view.
var me = Tinode.getMeTopic();
me.onMetaDesc = instance.tnMeMetaDesc;
instance.setState({sidePanelSelected: "contacts", myUserId: Tinode.getCurrentUserID()});
// Subscribe, fetch topic desc, the list of subscriptions. Messages are not fetched.
me.subscribe(me.startMetaQuery().withLaterSub().withDesc().build());
me.subscribe(me.startMetaQuery().withLaterSub().withDesc().build()).catch(function(err){
instance.handleError(err.message, "err");
});
},
handleDisconnect: function(err) {
this.setState({
sidePanelSelected: 'login',
sidePanelTitle: null,
sidePanelAvatar: null,
dialogSelected: null,
myUserId: null,
errorText: err && err.message ? err.message : 'disconnected',
topicSelected: '',
errorText: err && err.message ? err.message : 'Disconnected',
errorLevel: err && err.message ? "err" : "warn",
loginDisabled: false,
subscriptions: {},
showInfoPanel: false,
infoPanelShowMembers: false,
mobilePanel: 'sidepanel',
contextMenuVisible: false,
contextMenuBounds: null,
contextMenuClickAt: null,
@@ -2907,11 +2942,14 @@
var fnd = Tinode.getFndTopic();
fnd.onSubsUpdated = this.tnFndSubsUpdated;
fnd.onMetaDesc = this.tnFndMetaDesc;
var instance = this;
if (fnd.isSubscribed()) {
this.setState({contactsSearchQuery: fnd.private ? fnd.private.toString() : ''});
this.tnFndSubsUpdated();
} else {
fnd.subscribe(fnd.startMetaQuery().withSub().withLaterDesc().build());
fnd.subscribe(fnd.startMetaQuery().withSub().withLaterDesc().build()).catch(function(err){
instance.handleError(err.message, "err");
});
}
},
@@ -2933,10 +2971,13 @@
@param query {Array} is an array of contacts to search for
*/
handleSearchContacts: function(query) {
var fnd = Tinode.getFndTopic();
fnd.setMeta({desc: {private: query}}).then(function(ctrl) {
fnd.getMeta(fnd.startMetaQuery().withSub().withLaterDesc().build());
});
var fnd = Tinode.getFndTopic();
var instance = this;
fnd.setMeta({desc: {private: query}}).then(function(ctrl) {
fnd.getMeta(fnd.startMetaQuery().withSub().withLaterDesc().build());
}).catch(function(err){
instance.handleError(err.message, "err");
});
},
// User clicked on a contact in the side panel or deleted a contact.
@@ -2968,14 +3009,19 @@
// User clicked on a message send button.
handleSendMessage: function(msg) {
var topic = Tinode.getTopic(this.state.topicSelected);
var instance = this;
// Check if the message is formatted.
var dft = Drafty.parse(msg);
if (Drafty.isPlainText(dft)) {
// The message is simple text. Sned if as text/plain.
topic.publish(msg);
topic.publish(msg).catch(function(err) {
instance.handleError(err.message, "err");
});;
} else {
// Formatted message. Send it as a Drafty object.
topic.publish(dft, undefined, Drafty.getContentType());
topic.publish(dft, undefined, Drafty.getContentType()).catch(function(err) {
instance.handleError(err.message, "err");
});
}
},
// User chose a Sign Up menu item.
@@ -2990,21 +3036,24 @@
.then(function() {
return Tinode.createAccountBasic(login_, password_,
{public: public_, private: private_, tags: tags_});
})
.then(function() {
}).then(function() {
instance.handleLoginSuccessful(instance);
})
.catch(function(err) {
instance.setState({errorText: err.message});
}).catch(function(err) {
instance.handleError(err.message, "err");
});
},
handleUpdateAccountRequest: function(password, pub, priv) {
var instance = this;
if (pub || priv) {
Tinode.getMeTopic().setMeta({desc: {public: pub, private: priv}});
Tinode.getMeTopic().setMeta({desc: {public: pub, private: priv}}).catch(function(err) {
instance.handleError(err.message, "err");
});
}
if (password) {
Tinode.updateAccountBasic(null, Tinode.getCurrentLogin(), password);
Tinode.updateAccountBasic(null, Tinode.getCurrentLogin(), password).catch(function(err) {
instance.handleError(err.message, "err");
});
}
},
// User chose Settings menu item.
@@ -3043,7 +3092,6 @@
topic.subscribe(query.build(), setParams).then(function() {
instance.setState({
errorText: '',
topicSelected: topic.name,
sidePanelSelected: 'contacts',
mobilePanel: 'topic-view',
@@ -3051,11 +3099,12 @@
infoPanelShowMembers: peerName ? false : true
});
}).catch(function(err) {
instance.setState({errorText: err.message});
instance.handleError(err.message, "err");
});
},
handleTopicUpdateRequest: function(topicName, pub, priv, permissions) {
var instance = this;
var topic = Tinode.getTopic(topicName);
if (topic) {
var params = {};
@@ -3068,11 +3117,14 @@
if (permissions) {
params.defacs = permissions;
}
topic.setMeta({desc: params});
topic.setMeta({desc: params}).catch(function(err) {
instance.handleError(err.message, "err");
});
}
},
handleChangePermissions: function(topicName, mode, uid) {
var instance = this;
var topic = Tinode.getTopic(topicName);
if (topic) {
var am = topic.getAccessMode();
@@ -3083,7 +3135,9 @@
am.updateWant(mode);
mode = am.getWant();
}
topic.setMeta({sub: {user: uid, mode: mode}});
topic.setMeta({sub: {user: uid, mode: mode}}).catch(function(err) {
instance.handleError(err.message, "err");
});
}
},
@@ -3100,6 +3154,8 @@
showInfoPanel: false,
infoPanelShowMembers: false
});
}).catch(function(err) {
instance.handleError(err.message, "err");
});
},
@@ -3171,9 +3227,12 @@
});
}
var instance = this;
if (removed && removed.length > 0) {
removed.map(function(uid) {
topic.delSubscription(uid);
topic.delSubscription(uid).catch(function(err) {
instance.handleError(err.message, "err");
});;
});
}
},
@@ -3194,11 +3253,12 @@
user={this.state.contextMenuUser}
items={this.state.contextMenuItems}
hide={this.handleHideContextMenu}
onAction={this.handleContextMenuAction} />
onAction={this.handleContextMenuAction}
onError={this.handleError} />
<SidepanelView
displayMobile={this.state.displayMobile}
hideSelf={(this.state.displayMobile && this.state.mobilePanel !== 'sidepanel')}
hideSelf={this.state.displayMobile && this.state.mobilePanel !== 'sidepanel'}
state={this.state.sidePanelSelected}
title={this.state.sidePanelTitle}
avatar={this.state.sidePanelAvatar}
@@ -3207,6 +3267,7 @@
myUserId={this.state.myUserId}
loginDisabled={this.state.loginDisabled}
errorText={this.state.errorText}
errorLevel={this.state.errorLevel}
topicSelected={this.state.topicSelected}
serverAddress={this.state.serverAddress}
@@ -3230,7 +3291,6 @@
showContextMenu={this.handleShowContextMenu} />
<MessagesView
onHideMessagesView={this.handleHideMessagesView}
displayMobile={this.state.displayMobile}
hideSelf={(this.state.displayMobile &&
(this.state.mobilePanel !== 'topic-view' || this.state.showInfoPanel))}
@@ -3238,15 +3298,24 @@
myUserId={this.state.myUserId}
serverVersion={this.state.serverVersion}
serverAddress={this.state.serverAddress}
errorText={this.state.errorText}
errorLevel={this.state.errorLevel}
onHideMessagesView={this.handleHideMessagesView}
onData={this.tnData}
onError={this.handleError}
showContextMenu={this.handleShowContextMenu}
sendMessage={this.handleSendMessage} />
{this.state.showInfoPanel ?
<InfoView
displayMobile={this.state.displayMobile}
topic={this.state.topicSelected}
foundContacts={this.state.foundContacts}
myUserId={this.state.myUserId}
errorText={this.state.errorText}
errorLevel={this.state.errorLevel}
showMemberPanel={this.state.infoPanelShowMembers}
onTopicDescUpdate={this.handleTopicUpdateRequest}
onCancel={this.handleHideInfoView}