diff --git a/.babelrc b/.babelrc index d0cf03df..34739bdc 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,9 @@ { "presets": ["react-native"], - "plugins": ["transform-decorators-legacy"] + "plugins": ["transform-decorators-legacy"], + "env": { + "production": { + "plugins": ["transform-remove-console"] + } + } } diff --git a/app/lib/realm.js b/app/lib/realm.js index 72481b5e..ee44086d 100644 --- a/app/lib/realm.js +++ b/app/lib/realm.js @@ -18,7 +18,9 @@ const settingsSchema = { _server: 'servers', valueAsString: { type: 'string', optional: true }, valueAsBoolean: { type: 'bool', optional: true }, - valueAsNumber: { type: 'int', optional: true } + valueAsNumber: { type: 'int', optional: true }, + + _updatedAt: { type: 'date', optional: true } } }; diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 35a83216..da645a95 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -26,9 +26,7 @@ const RocketChat = { TOKEN_KEY, createChannel({ name, users, type }) { - return new Promise((resolve, reject) => { - Meteor.call(type ? 'createChannel' : 'createPrivateGroup', name, users, type, (err, res) => (err ? reject(err) : resolve(res))); - }); + return call(type ? 'createChannel' : 'createPrivateGroup', name, users, type); }, async getUserToken() { @@ -52,61 +50,63 @@ const RocketChat = { const url = `${ _url }/websocket`; Meteor.connect(url, { autoConnect: true, autoReconnect: true }); + Meteor.ddp.on('disconnected', () => { reduxStore.dispatch(disconnect()); }); + Meteor.ddp.on('connected', () => { reduxStore.dispatch(connectSuccess()); resolve(); }); - Meteor.ddp.on('connected', () => { - Meteor.call('public-settings/get', (err, data) => { - if (err) { - console.error(err); - } - - const settings = {}; - realm.write(() => { - data.forEach((item) => { - const setting = { - _id: item._id - }; - setting._server = { id: reduxStore.getState().server.server }; - if (settingsType[item.type]) { - setting[settingsType[item.type]] = item.value; - realm.create('settings', setting, true); - } - - settings[item._id] = item.value; - }); - }); - reduxStore.dispatch(actions.setAllSettings(settings)); - }); + Meteor.ddp.on('connected', async() => { Meteor.ddp.on('changed', (ddbMessage) => { + const server = { id: reduxStore.getState().server.server }; if (ddbMessage.collection === 'stream-room-messages') { realm.write(() => { const message = ddbMessage.fields.args[0]; message.temp = false; - message._server = { id: reduxStore.getState().server.server }; + message._server = server; + message.attachments = message.attachments || []; message.starred = !!message.starred; realm.create('messages', message, true); }); } if (ddbMessage.collection === 'stream-notify-user') { - realm.write(() => { - const data = ddbMessage.fields.args[1]; - data._server = { id: reduxStore.getState().server.server }; - realm.create('subscriptions', data, true); - }); + const [type, data] = ddbMessage.fields.args; + const [, ev] = ddbMessage.fields.eventName.split('/'); + if (/subscriptions/.test(ev)) { + switch (type) { + case 'inserted': + data._server = server; + realm.write(() => { + realm.create('subscriptions', data, true); + }); + break; + case 'updated': + delete data._updatedAt; + realm.write(() => { + realm.create('subscriptions', data, true); + }); + break; + default: + } + } + if (/rooms/.test(ev) && type === 'updated') { + const sub = realm.objects('subscriptions').filtered('rid == $0', data._id)[0]; + realm.write(() => { + sub._updatedAt = data._updatedAt; + }); + } } }); + RocketChat.getSettings(); }); }) .catch(e => console.error(e)); }, - login(params, callback) { return new Promise((resolve, reject) => { Meteor._startLoggingIn(); @@ -114,6 +114,11 @@ const RocketChat = { Meteor._endLoggingIn(); Meteor._handleLoginCallback(err, result); if (err) { + if (/user not found/i.test(err.reason)) { + err.error = 1; + err.reason = 'User or Password incorrect'; + err.message = 'User or Password incorrect'; + } reject(err); } else { resolve(result); @@ -137,36 +142,15 @@ const RocketChat = { }, register({ credentials }) { - return new Promise((resolve, reject) => { - Meteor.call('registerUser', credentials, (err, userId) => { - if (err) { - reject(err); - } - resolve(userId); - }); - }); + return call('registerUser', credentials); }, setUsername({ credentials }) { - return new Promise((resolve, reject) => { - Meteor.call('setUsername', credentials.username, (err, result) => { - if (err) { - reject(err); - } - resolve(result); - }); - }); + return call('setUsername', credentials.username); }, forgotPassword(email) { - return new Promise((resolve, reject) => { - Meteor.call('sendForgotPasswordEmail', email, (err, result) => { - if (err) { - reject(err); - } - resolve(result); - }); - }); + return call('sendForgotPasswordEmail', email); }, loginWithPassword({ username, password, code }, callback) { @@ -211,26 +195,6 @@ const RocketChat = { return this.login(params, callback); }, - // loadRooms(cb) { - // console.warn('a'); - // Meteor.call('rooms/get', (err, data) => { - // if (err) { - // console.error(err); - // } - // console.warn(`rooms ${ data.length }`); - // if (data.length) { - // realm.write(() => { - // data.forEach((room) => { - // room._server = { id: reduxStore.getState().server.server }; - // realm.create('rooms', room, true); - // }); - // }); - // } - - // return cb && cb(); - // }); - // }, - loadSubscriptions(cb) { Meteor.call('subscriptions/get', (err, data) => { if (err) { @@ -285,6 +249,7 @@ const RocketChat = { data.messages.forEach((message) => { message.temp = false; message._server = { id: reduxStore.getState().server.server }; + message.attachments = message.attachments || []; // write('messages', message); message.starred = !!message.starred; realm.create('messages', message, true); @@ -334,38 +299,17 @@ const RocketChat = { }, spotlight(search, usernames) { - return new Promise((resolve, reject) => { - Meteor.call('spotlight', search, usernames, (error, result) => { - if (error) { - return reject(error); - } - return resolve(result); - }); - }); + return call('spotlight', search, usernames); }, createDirectMessage(username) { - return new Promise((resolve, reject) => { - Meteor.call('createDirectMessage', username, (error, result) => { - if (error) { - return reject(error); - } - return resolve(result); - }); - }); + return call('createDirectMessage', username); }, readMessages(rid) { return call('readMessages', rid); }, joinRoom(rid) { - return new Promise((resolve, reject) => { - Meteor.call('joinRoom', rid, (error, result) => { - if (error) { - return reject(error); - } - return resolve(result); - }); - }); + return call('joinRoom', rid); }, @@ -379,26 +323,12 @@ const RocketChat = { */ _ufsCreate(fileInfo) { // return call('ufsCreate', fileInfo); - return new Promise((resolve, reject) => { - Meteor.call('ufsCreate', fileInfo, (error, result) => { - if (error) { - return reject(error); - } - return resolve(result); - }); - }); + return call('ufsCreate', fileInfo); }, // ["ZTE8CKHJt7LATv7Me","fileSystem","e8E96b2819" _ufsComplete(fileId, store, token) { - return new Promise((resolve, reject) => { - Meteor.call('ufsComplete', fileId, store, token, (error, result) => { - if (error) { - return reject(error); - } - return resolve(result); - }); - }); + return call('ufsComplete', fileId, store, token); }, /* @@ -412,14 +342,7 @@ const RocketChat = { } */ _sendFileMessage(rid, data, msg = {}) { - return new Promise((resolve, reject) => { - Meteor.call('sendFileMessage', rid, null, data, msg, (error, result) => { - if (error) { - return reject(error); - } - return resolve(result); - }); - }); + return call('sendFileMessage', rid, null, data, msg); }, async sendFileMessage(rid, fileInfo, data) { const placeholder = RocketChat.getMessage(rid, 'Sending an image'); @@ -448,23 +371,36 @@ const RocketChat = { }); } }, - getRooms() { + async getRooms() { const { server, login } = reduxStore.getState(); - return Promise.all([call('subscriptions/get'), call('rooms/get')]).then(([subscriptions, rooms]) => { - const data = subscriptions.map((subscription) => { - subscription._updatedAt = (rooms.find(room => room._id === subscription.rid) || {})._updatedAt; - subscription._server = { id: server.server }; - return subscription; - }); + let lastMessage = realm + .objects('subscriptions') + .filtered('_server.id = $0', server.server) + .sorted('_updatedAt', true)[0]; + lastMessage = lastMessage && new Date(lastMessage._updatedAt); + let [subscriptions, rooms] = await Promise.all([call('subscriptions/get', lastMessage), call('rooms/get', lastMessage)]); - realm.write(() => { - data.forEach((subscription) => { - realm.create('subscriptions', subscription, true); - }); - }); - Meteor.subscribe('stream-notify-user', `${ login.user.id }/subscriptions-changed`, false); - return data; + if (lastMessage) { + subscriptions = subscriptions.update; + rooms = rooms.update; + } + const data = subscriptions.map((subscription) => { + const room = rooms.find(({ _id }) => _id === subscription.rid); + delete subscription._updatedAt; + if (room) { + subscription._updatedAt = room._updatedAt; + } + subscription._server = { id: server.server }; + return subscription; }); + + realm.write(() => { + data.forEach(subscription => + realm.create('subscriptions', subscription, true)); + }); + Meteor.subscribe('stream-notify-user', `${ login.user.id }/subscriptions-changed`, false); + Meteor.subscribe('stream-notify-user', `${ login.user.id }/rooms-changed`, false); + return data; }, logout({ server }) { Meteor.logout(); @@ -472,6 +408,27 @@ const RocketChat = { AsyncStorage.removeItem(TOKEN_KEY); AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`); }, + async getSettings() { + const temp = realm.objects('settings').sorted('_updatedAt', true)[0]; + const result = await (!temp ? call('public-settings/get') : call('public-settings/get', new Date(temp._updatedAt))); + const settings = temp ? result.update : result; + const filteredSettings = RocketChat._prepareSettings(RocketChat._filterSettings(settings)); + realm.write(() => { + filteredSettings.forEach(setting => realm.create('settings', setting, true)); + }); + reduxStore.dispatch(actions.setAllSettings(RocketChat.parseSettings(filteredSettings))); + }, + parseSettings: settings => settings.reduce((ret, item) => { + ret[item._id] = item[settingsType[item.type]] || item.valueAsString || item.value; + return ret; + }, {}), + _prepareSettings(settings) { + return settings.map((setting) => { + setting[settingsType[setting.type]] = setting.value; + return setting; + }); + }, + _filterSettings: settings => settings.filter(setting => settingsType[setting.type] && setting.value), deleteMessage(message) { return call('deleteMessage', { _id: message._id }); }, diff --git a/app/presentation/RoomItem.js b/app/presentation/RoomItem.js index 6f143076..3d97e491 100644 --- a/app/presentation/RoomItem.js +++ b/app/presentation/RoomItem.js @@ -87,13 +87,12 @@ export default class RoomItem extends React.PureComponent { return null; } - const { color } = avatarInitialsAndColor(name); - if (type === 'd') { return ( ); } + const { color } = avatarInitialsAndColor(name); return ( @@ -120,24 +119,13 @@ export default class RoomItem extends React.PureComponent { render() { const { unread, name, _updatedAt } = this.props; - if (_updatedAt) { - return ( - - {this.icon} - - { name } - { moment(_updatedAt).format(this.props.dateFormat) } - - {this.renderNumber(unread)} - - ); - } return ( {this.icon} { name } + {_updatedAt ? { moment(_updatedAt).format(this.props.dateFormat) } : null} {this.renderNumber(unread)} diff --git a/app/sagas/init.js b/app/sagas/init.js index 26255156..78938dd8 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -4,6 +4,8 @@ import * as actions from '../actions'; import { setServer } from '../actions/server'; import { restoreToken } from '../actions/login'; import { APP } from '../actions/actionsTypes'; +import realm from '../lib/realm'; +import RocketChat from '../lib/rocketchat'; const restore = function* restore() { try { @@ -16,6 +18,8 @@ const restore = function* restore() { const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer'); if (currentServer) { yield put(setServer(currentServer)); + const tmp = realm.objects('settings'); + yield put(actions.setAllSettings(RocketChat.parseSettings(tmp.slice(0, tmp.length)))); } yield put(actions.appReady({})); } catch (e) { diff --git a/app/sagas/login.js b/app/sagas/login.js index b02ff994..0838aea0 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -1,5 +1,5 @@ import { AsyncStorage } from 'react-native'; -import { take, put, call, takeEvery, takeLatest, select, all } from 'redux-saga/effects'; +import { take, put, call, takeLatest, select, all } from 'redux-saga/effects'; import * as types from '../actions/actionsTypes'; import { loginRequest, @@ -9,7 +9,6 @@ import { loginSuccess, loginFailure, setToken, - logout, registerSuccess, setUsernameRequest, setUsernameSuccess, @@ -83,11 +82,7 @@ const handleLoginRequest = function* handleLoginRequest({ credentials }) { yield put(loginSuccess(user)); } catch (err) { - if (err.error === 403) { - yield put(logout()); - } else { - yield put(loginFailure(err)); - } + yield put(loginFailure(err)); } }; @@ -147,7 +142,7 @@ const handleForgotPasswordRequest = function* handleForgotPasswordRequest({ emai }; const root = function* root() { - yield takeEvery(types.SERVER.CHANGED, handleLoginWhenServerChanges); + yield takeLatest(types.SERVER.CHANGED, handleLoginWhenServerChanges); yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest); yield takeLatest(types.LOGIN.SUCCESS, saveToken); yield takeLatest(types.LOGIN.SUBMIT, handleLoginSubmit); diff --git a/app/views/LoginView.js b/app/views/LoginView.js index 9af008fb..5928a448 100644 --- a/app/views/LoginView.js +++ b/app/views/LoginView.js @@ -1,6 +1,6 @@ import React from 'react'; -// import Spinner from 'react-native-loading-spinner-overlay'; +import Spinner from 'react-native-loading-spinner-overlay'; import PropTypes from 'prop-types'; import { Keyboard, Text, TextInput, View, TouchableOpacity, SafeAreaView } from 'react-native'; @@ -143,6 +143,7 @@ class LoginView extends React.Component { {this.props.login.failure && {this.props.login.error.reason}} + diff --git a/app/views/RoomView.js b/app/views/RoomView.js index 1c68d2ab..41b02dcf 100644 --- a/app/views/RoomView.js +++ b/app/views/RoomView.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Text, View, StyleSheet, Button, SafeAreaView } from 'react-native'; +import { Text, View, StyleSheet, Button, SafeAreaView, Dimensions } from 'react-native'; import { ListView } from 'realm/react-native'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; @@ -38,7 +38,7 @@ const styles = StyleSheet.create({ textAlign: 'center', color: '#a00' }, - header: { + loadingMore: { transform: [{ scaleY: -1 }], textAlign: 'center', padding: 5, @@ -86,7 +86,7 @@ export default class RoomView extends React.Component { .sorted('ts', true); this.state = { slow: false, - dataSource: [], + dataSource: ds.cloneWithRows([]), loaded: true, joined: typeof props.rid === 'undefined' }; @@ -102,10 +102,9 @@ export default class RoomView extends React.Component { this.timer = setTimeout(() => this.setState({ slow: true }), 5000); this.props.getMessages(this.rid); this.data.addListener(this.updateState); - this.state.dataSource = ds.cloneWithRows(this.data); } componentDidMount() { - + this.updateState(); } componentDidUpdate() { return !this.props.loading && clearTimeout(this.timer); @@ -124,14 +123,12 @@ export default class RoomView extends React.Component { this.state.end !== true ) { this.setState({ - // ...this.state, loadingMore: true }); const lastRowData = this.data[rowCount - 1]; RocketChat.loadMessagesForRoom(this.rid, lastRowData.ts, ({ end }) => { this.setState({ - // ...this.state, loadingMore: false, end }); @@ -186,15 +183,16 @@ export default class RoomView extends React.Component { renderHeader = () => { if (this.state.loadingMore) { - return Loading more messages...; + return Loading more messages...; } if (this.state.end) { - return Start of conversation; + return Start of conversation; } }; render() { + const { height } = Dimensions.get('window'); return ( {this.renderBanner()} @@ -202,7 +200,7 @@ export default class RoomView extends React.Component {