From a2821af95b43d75404d4fb59d7ac32483ec2e40e Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 5 Dec 2018 18:52:08 -0200 Subject: [PATCH] Use Rest API calls (#558) --- .../__snapshots__/Storyshots.test.js.snap | 8 +- app/ReactotronConfig.js | 1 + app/actions/actionsTypes.js | 14 +- app/actions/connect.js | 12 - app/actions/login.js | 91 +-- app/actions/room.js | 10 +- app/constants/settings.js | 4 +- app/containers/Avatar.js | 9 +- app/containers/MessageActions.js | 5 +- app/containers/MessageBox/Recording.js | 1 - app/containers/Sidebar.js | 4 +- app/containers/message/Image.js | 2 +- app/i18n/locales/en.js | 4 +- app/i18n/locales/pt-BR.js | 5 +- app/i18n/locales/ru.js | 2 - app/i18n/locales/zh-CN.js | 2 - app/index.js | 2 - app/lib/methods/canOpenRoom.js | 38 +- app/lib/methods/getCustomEmojis.js | 3 +- app/lib/methods/getPermissions.js | 16 +- app/lib/methods/getRooms.js | 26 +- app/lib/methods/getSettings.js | 26 +- app/lib/methods/loadMessagesForRoom.js | 31 +- app/lib/methods/loadMissedMessages.js | 15 +- app/lib/methods/readMessages.js | 19 +- app/lib/methods/sendMessage.js | 23 +- app/lib/methods/subscriptions/room.js | 38 +- app/lib/methods/subscriptions/rooms.js | 140 ++-- app/lib/rocketchat.js | 697 +++++++++--------- app/reducers/app.js | 7 +- app/reducers/connect.js | 24 +- app/reducers/login.js | 77 +- app/reducers/sortPreferences.js | 6 +- app/sagas/connect.js | 50 -- app/sagas/index.js | 2 - app/sagas/init.js | 41 +- app/sagas/login.js | 123 ++-- app/sagas/messages.js | 3 +- app/sagas/rooms.js | 59 +- app/sagas/selectServer.js | 20 +- app/sagas/state.js | 15 +- app/utils/isValidEmail.js | 5 + app/utils/log.js | 2 +- app/views/ForgotPasswordView.js | 52 +- app/views/LoginSignupView.js | 14 +- app/views/LoginView.js | 50 +- app/views/OAuthView.js | 15 +- app/views/RegisterView.js | 62 +- app/views/RoomActionsView/index.js | 95 +-- app/views/RoomInfoView/index.js | 2 +- app/views/RoomMembersView/index.js | 19 +- app/views/RoomView/ListView.js | 7 +- app/views/RoomView/index.js | 90 ++- app/views/RoomsListView/index.js | 15 +- app/views/SetUsernameView.js | 50 +- e2e/03-forgotpassword.spec.js | 2 +- e2e/04-createuser.spec.js | 51 +- package-lock.json | 148 +--- package.json | 7 +- 59 files changed, 925 insertions(+), 1436 deletions(-) delete mode 100644 app/sagas/connect.js create mode 100644 app/utils/isValidEmail.js diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap index 326454c3..b9ce3568 100644 --- a/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/__tests__/__snapshots__/Storyshots.test.js.snap @@ -34,7 +34,7 @@ exports[`Storyshots Avatar avatar 1`] = ` source={ Object { "priority": "high", - "uri": "baseUrl/avatar/test?format=png&size=50", + "uri": "baseUrl/avatar/test?format=png&width=50&height=50", } } style={ @@ -80,7 +80,7 @@ exports[`Storyshots Avatar avatar 1`] = ` source={ Object { "priority": "high", - "uri": "baseUrl/avatar/aa?format=png&size=50", + "uri": "baseUrl/avatar/aa?format=png&width=50&height=50", } } style={ @@ -126,7 +126,7 @@ exports[`Storyshots Avatar avatar 1`] = ` source={ Object { "priority": "high", - "uri": "baseUrl/avatar/bb?format=png&size=50", + "uri": "baseUrl/avatar/bb?format=png&width=50&height=50", } } style={ @@ -172,7 +172,7 @@ exports[`Storyshots Avatar avatar 1`] = ` source={ Object { "priority": "high", - "uri": "baseUrl/avatar/test?format=png&size=50", + "uri": "baseUrl/avatar/test?format=png&width=50&height=50", } } style={ diff --git a/app/ReactotronConfig.js b/app/ReactotronConfig.js index 4f2db614..d274cb6f 100644 --- a/app/ReactotronConfig.js +++ b/app/ReactotronConfig.js @@ -17,4 +17,5 @@ if (__DEV__) { // $ adb reverse tcp:9090 tcp:9090 Reactotron.clear(); console.warn = Reactotron.log; + console.log = Reactotron.log; } diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index 74780887..5238b533 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -11,19 +11,10 @@ function createRequestTypes(base, types = defaultTypes) { // Login events export const LOGIN = createRequestTypes('LOGIN', [ ...defaultTypes, - 'SET_TOKEN', - 'RESTORE_TOKEN', - 'SUBMIT', - 'REGISTER_SUBMIT', - 'REGISTER_REQUEST', - 'SET_USERNAME_SUBMIT', - 'SET_USERNAME_REQUEST', - 'SET_USERNAME_SUCCESS', 'SET_SERVICES', 'SET_PREFERENCE', 'SET_SORT_PREFERENCE' ]); -export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD'); export const USER = createRequestTypes('USER', ['SET']); export const ROOMS = createRequestTypes('ROOMS', [ ...defaultTypes, @@ -82,7 +73,7 @@ export const SERVER = createRequestTypes('SERVER', [ 'INIT_ADD', 'FINISH_ADD' ]); -export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT', 'DISCONNECT_BY_USER']); +export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']); export const LOGOUT = 'LOGOUT'; // logout is always success export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET']); export const ROLES = createRequestTypes('ROLES', ['SET']); @@ -93,6 +84,3 @@ export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPE export const ROOM_FILES = createRequestTypes('ROOM_FILES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']); export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']); export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']); - -export const INCREMENT = 'INCREMENT'; -export const DECREMENT = 'DECREMENT'; diff --git a/app/actions/connect.js b/app/actions/connect.js index 40049f28..57f46c71 100644 --- a/app/actions/connect.js +++ b/app/actions/connect.js @@ -12,21 +12,9 @@ export function connectSuccess() { }; } -export function connectFailure(err) { - return { - type: types.METEOR.FAILURE, - err - }; -} - export function disconnect(err) { return { type: types.METEOR.DISCONNECT, err }; } -export function disconnect_by_user() { - return { - type: types.METEOR.DISCONNECT_BY_USER - }; -} diff --git a/app/actions/login.js b/app/actions/login.js index cc5c1715..c406d8e8 100644 --- a/app/actions/login.js +++ b/app/actions/login.js @@ -1,11 +1,5 @@ import * as types from './actionsTypes'; -export function loginSubmit(credentials) { - return { - type: types.LOGIN.SUBMIT, - credentials - }; -} export function loginRequest(credentials) { return { type: types.LOGIN.REQUEST, @@ -13,45 +7,10 @@ export function loginRequest(credentials) { }; } -export function registerSubmit(credentials) { - return { - type: types.LOGIN.REGISTER_SUBMIT, - credentials - }; -} - -export function registerRequest(credentials) { - return { - type: types.LOGIN.REGISTER_REQUEST, - credentials - }; -} - -export function setUsernameSubmit(credentials) { - return { - type: types.LOGIN.SET_USERNAME_SUBMIT, - credentials - }; -} - -export function setUsernameRequest(credentials) { - return { - type: types.LOGIN.SET_USERNAME_REQUEST, - credentials - }; -} - -export function setUsernameSuccess() { - return { - type: types.LOGIN.SET_USERNAME_SUCCESS - }; -} - export function loginSuccess(user) { return { type: types.LOGIN.SUCCESS, - user, - token: user.token + user }; } @@ -62,58 +21,16 @@ export function loginFailure(err) { }; } -export function setToken(user = {}) { - return { - type: types.LOGIN.SET_TOKEN, - ...user - }; -} - -export function restoreToken(token) { - return { - type: types.LOGIN.RESTORE_TOKEN, - token - }; -} - export function logout() { return { type: types.LOGOUT }; } -export function forgotPasswordInit() { +export function setUser(user) { return { - type: types.FORGOT_PASSWORD.INIT - }; -} - -export function forgotPasswordRequest(email) { - return { - type: types.FORGOT_PASSWORD.REQUEST, - email - }; -} - -export function forgotPasswordSuccess() { - return { - type: types.FORGOT_PASSWORD.SUCCESS - }; -} - -export function forgotPasswordFailure(err) { - return { - type: types.FORGOT_PASSWORD.FAILURE, - err - }; -} - -export function setUser(action) { - return { - // do not change this params order - // since we use spread operator, sometimes `type` is overriden - ...action, - type: types.USER.SET + type: types.USER.SET, + user }; } diff --git a/app/actions/room.js b/app/actions/room.js index 2c2e603a..ab8b1b1e 100644 --- a/app/actions/room.js +++ b/app/actions/room.js @@ -35,17 +35,19 @@ export function closeRoom() { }; } -export function leaveRoom(rid) { +export function leaveRoom(rid, t) { return { type: types.ROOM.LEAVE, - rid + rid, + t }; } -export function eraseRoom(rid) { +export function eraseRoom(rid, t) { return { type: types.ROOM.ERASE, - rid + rid, + t }; } diff --git a/app/constants/settings.js b/app/constants/settings.js index 12277bf5..fe00972b 100644 --- a/app/constants/settings.js +++ b/app/constants/settings.js @@ -58,6 +58,8 @@ export default { }, UI_Use_Real_Name: { type: 'valueAsBoolean' + }, + Assets_favicon_512: { + type: null } }; -export const settingsUpdatedAt = new Date('2018-11-14'); diff --git a/app/containers/Avatar.js b/app/containers/Avatar.js index 29381de5..fc531de1 100644 --- a/app/containers/Avatar.js +++ b/app/containers/Avatar.js @@ -33,8 +33,15 @@ export default class Avatar extends React.PureComponent { borderRadius }; + if (!text && !avatar) { + return null; + } + const room = type === 'd' ? text : `@${ text }`; - const uri = avatar || `${ baseUrl }/avatar/${ room }?format=png&size=${ size === 100 ? 100 : 50 }`; + // Avoid requesting several sizes by having only two sizes on cache + const uriSize = size === 100 ? 100 : 50; + const uri = avatar || `${ baseUrl }/avatar/${ room }?format=png&width=${ uriSize }&height=${ uriSize }`; + const image = ( { + handleShare = async() => { const { actionMessage } = this.props; + const permalink = await this.getPermalink(actionMessage); Share.share({ - message: actionMessage.msg.content.replace(/<(?:.|\n)*?>/gm, '') + message: permalink }); }; diff --git a/app/containers/MessageBox/Recording.js b/app/containers/MessageBox/Recording.js index ebf8d087..8652f27a 100644 --- a/app/containers/MessageBox/Recording.js +++ b/app/containers/MessageBox/Recording.js @@ -101,7 +101,6 @@ export default class extends React.PureComponent { } } catch (err) { this.finishRecording(false); - console.error(err); } } diff --git a/app/containers/Sidebar.js b/app/containers/Sidebar.js index 4dce2487..a9ff4af7 100644 --- a/app/containers/Sidebar.js +++ b/app/containers/Sidebar.js @@ -7,7 +7,7 @@ import { connect } from 'react-redux'; import Icon from 'react-native-vector-icons/MaterialIcons'; import { Navigation } from 'react-native-navigation'; -import { appStart as appStartAction, setStackRoot as setStackRootAction } from '../actions'; +import { setStackRoot as setStackRootAction } from '../actions'; import { logout as logoutAction } from '../actions/login'; import Avatar from './Avatar'; import Status from './status'; @@ -95,7 +95,6 @@ const keyExtractor = item => item.id; baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' }), dispatch => ({ logout: () => dispatch(logoutAction()), - appStart: () => dispatch(appStartAction('outside')), setStackRoot: stackRoot => dispatch(setStackRootAction(stackRoot)) })) export default class Sidebar extends Component { @@ -106,7 +105,6 @@ export default class Sidebar extends Component { stackRoot: PropTypes.string.isRequired, user: PropTypes.object, logout: PropTypes.func.isRequired, - appStart: PropTypes.func, setStackRoot: PropTypes.func } diff --git a/app/containers/message/Image.js b/app/containers/message/Image.js index bef94875..6b81127d 100644 --- a/app/containers/message/Image.js +++ b/app/containers/message/Image.js @@ -44,7 +44,7 @@ export default class extends React.PureComponent { const { baseUrl, file, user } = this.props; const img = `${ baseUrl }${ file.image_url }?rc_uid=${ user.id }&rc_token=${ user.token }`; - if (!baseUrl) { + if (!img) { return null; } diff --git a/app/i18n/locales/en.js b/app/i18n/locales/en.js index 41582163..425cacae 100644 --- a/app/i18n/locales/en.js +++ b/app/i18n/locales/en.js @@ -1,5 +1,4 @@ export default { - '1_online_member': '1 online member', '1_person_reacted': '1 person reacted', '1_user': '1 user', 'error-action-not-allowed': '{{action}} is not allowed', @@ -184,6 +183,7 @@ export default { Login_error: 'Your credentials were rejected! Please try again.', Login_with: 'Login with', Logout: 'Logout', + members: 'members', Members: 'Members', Mentioned_Messages: 'Mentioned Messages', mentioned: 'mentioned', @@ -198,7 +198,6 @@ export default { Mute: 'Mute', muted: 'muted', My_servers: 'My servers', - N_online_members: '{{n}} online members', N_people_reacted: '{{n}} people reacted', N_users: '{{n}} users', name: 'name', @@ -254,6 +253,7 @@ export default { Reply: 'Reply', Resend: 'Resend', Reset_password: 'Reset password', + resetting_password: 'resetting password', RESET: 'RESET', Roles: 'Roles', Room_actions: 'Room actions', diff --git a/app/i18n/locales/pt-BR.js b/app/i18n/locales/pt-BR.js index 3f627b56..76d51ae2 100644 --- a/app/i18n/locales/pt-BR.js +++ b/app/i18n/locales/pt-BR.js @@ -1,5 +1,4 @@ export default { - '1_online_member': '1 membro online', '1_person_reacted': '1 pessoa reagiu', '1_user': '1 usuário', 'error-action-not-allowed': '{{action}} não é permitido', @@ -202,7 +201,6 @@ export default { Microphone_Permission: 'Acesso ao Microfone', Mute: 'Mudo', muted: 'mudo', - N_online_members: '{{n}} membros online', N_people_reacted: '{{n}} pessoas reagiram', N_users: '{{n}} usuários', name: 'nome', @@ -257,6 +255,7 @@ export default { Reply: 'Responder', Resend: 'Reenviar', Reset_password: 'Resetar senha', + resetting_password: 'redefinindo senha', RESET: 'RESETAR', Roles: 'Papéis', Room_actions: 'Ações', @@ -305,7 +304,7 @@ export default { Take_a_photo: 'Tirar uma foto', Terms_of_Service: ' Termos de Serviço ', The_URL_is_invalid: 'A URL fornecida é inválida ou não acessível. Por favor tente novamente, mas com uma url diferente.', - There_was_an_error_while_action: 'Acontece um erro {{action}}!', + There_was_an_error_while_action: 'Aconteceu um erro {{action}}!', This_room_is_blocked: 'Este quarto está bloqueado', This_room_is_read_only: 'Este quarto é apenas de leitura', Timezone: 'Fuso horário', diff --git a/app/i18n/locales/ru.js b/app/i18n/locales/ru.js index 7fb8a0cc..4cc484d4 100644 --- a/app/i18n/locales/ru.js +++ b/app/i18n/locales/ru.js @@ -1,5 +1,4 @@ export default { - '1_online_member': '1 участник онлайн', '1_person_reacted': '1 человек отреагировал', 'error-action-not-allowed': '{{action}} не допускается', 'error-application-not-found': 'Приложение не найдено', @@ -175,7 +174,6 @@ export default { Mute: 'Заглушить', muted: 'Заглушен', My_servers: 'Мои серверы', - N_online_members: '{{n}} пользователей онлайн', N_person_reacted: '{{n}} людей отреагировало', Name: 'Имя', New_in_RocketChat_question_mark: 'Новичок в Rocket.Chat?', diff --git a/app/i18n/locales/zh-CN.js b/app/i18n/locales/zh-CN.js index 958058b4..b5c5e152 100644 --- a/app/i18n/locales/zh-CN.js +++ b/app/i18n/locales/zh-CN.js @@ -1,5 +1,4 @@ export default { - '1_online_member': '1 人在线', '1_person_reacted': '1 人回复了', '1_user': '1 位用户', 'error-action-not-allowed': '不允许 {{action}}', @@ -199,7 +198,6 @@ export default { Mute: '静音', muted: '被静音', My_servers: '我的服务器', - N_online_members: '{{n}} 位会员在线', N_people_reacted: '{{n}} 人回复', N_users: '{{n}} 位用户', name: '名字', diff --git a/app/index.js b/app/index.js index c6cb8f6f..579a8404 100644 --- a/app/index.js +++ b/app/index.js @@ -99,8 +99,6 @@ iconsLoaded(); export default class App extends Component { constructor(props) { super(props); - store.dispatch(appInit()); - store.subscribe(this.onStoreUpdate.bind(this)); initializePushNotifications(); Navigation.events().registerAppLaunchedListener(() => { diff --git a/app/lib/methods/canOpenRoom.js b/app/lib/methods/canOpenRoom.js index d37e8e93..f4a16e8d 100644 --- a/app/lib/methods/canOpenRoom.js +++ b/app/lib/methods/canOpenRoom.js @@ -1,57 +1,37 @@ import * as SDK from '@rocket.chat/sdk'; import database from '../realm'; -import log from '../../utils/log'; -// TODO: api fix -const ddpTypes = { - channel: 'c', direct: 'd', group: 'p' -}; const restTypes = { channel: 'channels', direct: 'im', group: 'groups' }; -async function canOpenRoomREST({ type, rid }) { +async function open({ type, rid }) { try { await SDK.api.post(`${ restTypes[type] }.open`, { roomId: rid }); return true; - } catch (error) { - // TODO: workround for 'already open for the sender' error - if (!error.errorType) { + } catch (e) { + if (e.data && /is already open/.test(e.data.error)) { return true; } return false; } } -async function canOpenRoomDDP(...args) { - try { - const [{ type, name }] = args; - await SDK.driver.asyncCall('getRoomByTypeAndName', ddpTypes[type], name); - return true; - } catch (error) { - if (error.isClientSafe) { - return false; - } - return canOpenRoomREST.call(this, ...args); - } -} - export default async function canOpenRoom({ rid, path }) { - const { database: db } = database; + const [type] = path.split('/'); + if (type === 'channel') { + return true; + } - const room = db.objects('subscriptions').filtered('rid == $0', rid); + const room = database.objects('subscriptions').filtered('rid == $0', rid); if (room.length) { return true; } - const [type, name] = path.split('/'); - try { - const data = await (this.connected() ? canOpenRoomDDP.call(this, { rid, type, name }) : canOpenRoomREST.call(this, { type, rid })); - return data; + return await open.call(this, { type, rid }); } catch (e) { - log('canOpenRoom', e); return false; } } diff --git a/app/lib/methods/getCustomEmojis.js b/app/lib/methods/getCustomEmojis.js index cded086d..45d5994f 100644 --- a/app/lib/methods/getCustomEmojis.js +++ b/app/lib/methods/getCustomEmojis.js @@ -16,7 +16,8 @@ const getLastMessage = () => { export default async function() { try { const lastMessage = getLastMessage(); - let emojis = await SDK.driver.asyncCall('listEmojiCustom'); + const result = await SDK.api.get('emoji-custom'); + let { emojis } = result; emojis = emojis.filter(emoji => !lastMessage || emoji._updatedAt > lastMessage); emojis = this._prepareEmojis(emojis); InteractionManager.runAfterInteractions(() => database.write(() => { diff --git a/app/lib/methods/getPermissions.js b/app/lib/methods/getPermissions.js index e0398c48..0c1e40e9 100644 --- a/app/lib/methods/getPermissions.js +++ b/app/lib/methods/getPermissions.js @@ -5,18 +5,14 @@ import database from '../realm'; import log from '../../utils/log'; import defaultPermissions from '../../constants/permissions'; -const getLastUpdate = () => { - const setting = database.objects('permissions').sorted('_updatedAt', true)[0]; - return setting && setting._updatedAt; -}; - export default async function() { try { - const lastUpdate = getLastUpdate(); - const result = await (!lastUpdate - ? SDK.driver.asyncCall('permissions/get') - : SDK.driver.asyncCall('permissions/get', new Date(lastUpdate))); - const permissions = (result.update || result).filter(permission => defaultPermissions.includes(permission._id)); + const result = await SDK.api.get('permissions.list'); + + if (!result.success) { + return; + } + const permissions = result.permissions.filter(permission => defaultPermissions.includes(permission._id)); permissions .map((permission) => { permission._updatedAt = new Date(); diff --git a/app/lib/methods/getRooms.js b/app/lib/methods/getRooms.js index acf7ed01..f43316a5 100644 --- a/app/lib/methods/getRooms.js +++ b/app/lib/methods/getRooms.js @@ -12,31 +12,17 @@ const lastMessage = () => { return message && new Date(message.roomUpdatedAt).toISOString(); }; -const getRoomRest = async function() { - const updatedSince = lastMessage(); - const [subscriptions, rooms] = await (updatedSince - ? Promise.all([SDK.api.get('subscriptions.get', { updatedSince }), SDK.api.get('rooms.get', { updatedSince })]) - : Promise.all([SDK.api.get('subscriptions.get'), SDK.api.get('rooms.get')]) - ); - return mergeSubscriptionsRooms(subscriptions, rooms); -}; - -const getRoomDpp = async function() { - try { - const updatedSince = lastMessage(); - const [subscriptions, rooms] = await Promise.all([SDK.driver.asyncCall('subscriptions/get', updatedSince), SDK.driver.asyncCall('rooms/get', updatedSince)]); - return mergeSubscriptionsRooms(subscriptions, rooms); - } catch (e) { - return getRoomRest.apply(this); - } -}; - export default function() { const { database: db } = database; return new Promise(async(resolve, reject) => { try { - const { subscriptions, rooms } = await (this.connected() ? getRoomDpp.apply(this) : getRoomRest.apply(this)); + const updatedSince = lastMessage(); + const [subscriptionsResult, roomsResult] = await (updatedSince + ? Promise.all([SDK.api.get('subscriptions.get', { updatedSince }), SDK.api.get('rooms.get', { updatedSince })]) + : Promise.all([SDK.api.get('subscriptions.get'), SDK.api.get('rooms.get')]) + ); + const { subscriptions, rooms } = mergeSubscriptionsRooms(subscriptionsResult, roomsResult); const data = rooms.map(room => ({ room, sub: database.objects('subscriptions').filtered('rid == $0', room._id) })); diff --git a/app/lib/methods/getSettings.js b/app/lib/methods/getSettings.js index ace90685..38a79b09 100644 --- a/app/lib/methods/getSettings.js +++ b/app/lib/methods/getSettings.js @@ -5,12 +5,7 @@ import reduxStore from '../createStore'; import database from '../realm'; import * as actions from '../../actions'; import log from '../../utils/log'; -import { settingsUpdatedAt } from '../../constants/settings'; - -const getLastUpdate = () => { - const [setting] = database.objects('settings').sorted('_updatedAt', true); - return setting && setting._updatedAt; -}; +import settings from '../../constants/settings'; function updateServer(param) { database.databases.serversDB.write(() => { @@ -20,19 +15,14 @@ function updateServer(param) { export default async function() { try { - // if (!SDK.driver.dd) { - // // TODO: should implement loop or get from rest? - // return; - // } + const settingsParams = JSON.stringify(Object.keys(settings)); + const result = await fetch(`${ SDK.api.url }settings.public?query={"_id":{"$in":${ settingsParams }}}`).then(response => response.json()); - const lastUpdate = getLastUpdate(); - const fetchNewSettings = lastUpdate < settingsUpdatedAt; - const result = await ((!lastUpdate || fetchNewSettings) - ? SDK.driver.asyncCall('public-settings/get') - : SDK.driver.asyncCall('public-settings/get', new Date(lastUpdate))); - const data = result.update || result || []; - - const filteredSettings = this._prepareSettings(this._filterSettings(data)); + if (!result.success) { + return; + } + const data = result.settings || []; + const filteredSettings = this._prepareSettings(data.filter(item => item._id !== 'Assets_favicon_512')); InteractionManager.runAfterInteractions( () => database.write( diff --git a/app/lib/methods/loadMessagesForRoom.js b/app/lib/methods/loadMessagesForRoom.js index e53c73e5..f20b57c6 100644 --- a/app/lib/methods/loadMessagesForRoom.js +++ b/app/lib/methods/loadMessagesForRoom.js @@ -5,47 +5,28 @@ import buildMessage from './helpers/buildMessage'; import database from '../realm'; import log from '../../utils/log'; -// TODO: api fix -const types = { - c: 'channels', d: 'im', p: 'groups' -}; - -async function loadMessagesForRoomRest({ rid: roomId, latest, t }) { +async function load({ rid: roomId, latest, t }) { + let params = { roomId, count: 50 }; if (latest) { - latest = new Date(latest).toISOString(); + params = { ...params, latest: new Date(latest).toISOString() }; } - const data = await SDK.api.get(`${ types[t] }.history`, { roomId, latest, count: 50 }); + const data = await SDK.api.get(`${ this.roomTypeToApiType(t) }.history`, params); if (!data || data.status === 'error') { return []; } return data.messages; } -async function loadMessagesForRoomDDP(...args) { - const [{ rid: roomId, latest }] = args; - try { - const data = await SDK.driver.asyncCall('loadHistory', roomId, latest, 50); - if (!data || !data.messages.length) { - return []; - } - return data.messages; - } catch (e) { - return loadMessagesForRoomRest.call(this, ...args); - } -} - export default function loadMessagesForRoom(...args) { const { database: db } = database; return new Promise(async(resolve, reject) => { try { - const data = (await (this.connected() - ? loadMessagesForRoomDDP.call(this, ...args) - : loadMessagesForRoomRest.call(this, ...args))).map(buildMessage); + const data = await load.call(this, ...args); if (data && data.length) { InteractionManager.runAfterInteractions(() => { db.write(() => data.forEach((message) => { - db.create('messages', message, true); + db.create('messages', buildMessage(message), true); })); return resolve(data); }); diff --git a/app/lib/methods/loadMissedMessages.js b/app/lib/methods/loadMissedMessages.js index 69ae0f18..0ad952cd 100644 --- a/app/lib/methods/loadMissedMessages.js +++ b/app/lib/methods/loadMissedMessages.js @@ -5,7 +5,7 @@ import buildMessage from './helpers/buildMessage'; import database from '../realm'; import log from '../../utils/log'; -async function loadMissedMessagesRest({ rid: roomId, lastOpen }) { +async function load({ rid: roomId, lastOpen }) { let lastUpdate; if (lastOpen) { lastUpdate = new Date(lastOpen).toISOString(); @@ -16,22 +16,11 @@ async function loadMissedMessagesRest({ rid: roomId, lastOpen }) { return result; } -async function loadMissedMessagesDDP(...args) { - const [{ rid, lastOpen: lastUpdate }] = args; - - try { - const result = await SDK.driver.asyncCall('messages/get', rid, { lastUpdate: new Date(lastUpdate), count: 50 }); - return result; - } catch (e) { - return loadMissedMessagesRest.call(this, ...args); - } -} - export default function loadMissedMessages(...args) { const { database: db } = database; return new Promise(async(resolve, reject) => { try { - const data = (await (this.connected() ? loadMissedMessagesDDP.call(this, ...args) : loadMissedMessagesRest.call(this, ...args))); + const data = (await load.call(this, ...args)); if (data) { if (data.updated && data.updated.length) { diff --git a/app/lib/methods/readMessages.js b/app/lib/methods/readMessages.js index 7b298d7c..1f4562b8 100644 --- a/app/lib/methods/readMessages.js +++ b/app/lib/methods/readMessages.js @@ -3,25 +3,12 @@ import * as SDK from '@rocket.chat/sdk'; import database from '../realm'; import log from '../../utils/log'; -const readMessagesREST = function readMessagesREST(rid) { - return SDK.api.post('subscriptions.read', { rid }); -}; - -const readMessagesDDP = function readMessagesDDP(rid) { - try { - return SDK.driver.asyncCall('readMessages', rid); - } catch (e) { - return readMessagesREST.call(this, rid); - } -}; - export default async function readMessages(rid) { const ls = new Date(); - const { database: db } = database; try { - const data = await (this.connected() ? readMessagesDDP.call(this, rid) : readMessagesREST.call(this, rid)); - const [subscription] = db.objects('subscriptions').filtered('rid = $0', rid); - db.write(() => { + const data = await SDK.api.post('subscriptions.read', { rid }); + const [subscription] = database.objects('subscriptions').filtered('rid = $0', rid); + database.write(() => { subscription.open = true; subscription.alert = false; subscription.unread = 0; diff --git a/app/lib/methods/sendMessage.js b/app/lib/methods/sendMessage.js index a2121112..21b620d9 100644 --- a/app/lib/methods/sendMessage.js +++ b/app/lib/methods/sendMessage.js @@ -1,4 +1,3 @@ -import Random from 'react-native-meteor/lib/Random'; import * as SDK from '@rocket.chat/sdk'; import messagesStatus from '../../constants/messagesStatus'; @@ -6,9 +5,10 @@ import buildMessage from './helpers/buildMessage'; import database from '../realm'; import reduxStore from '../createStore'; import log from '../../utils/log'; +import random from '../../utils/random'; export const getMessage = (rid, msg = {}) => { - const _id = Random.id(); + const _id = random(17); const message = { _id, rid, @@ -31,21 +31,9 @@ export const getMessage = (rid, msg = {}) => { return message; }; -function sendMessageByRest(args) { - return SDK.api.post('chat.sendMessage', { message: args }); -} - -function sendMessageByDDP(...args) { - try { - return SDK.driver.asyncCall('sendMessage', ...args); - } catch (error) { - return sendMessageByRest.call(this, ...args); - } -} - -export async function _sendMessageCall(message) { +export async function sendMessageCall(message) { const { _id, rid, msg } = message; - const data = await (this.connected() ? sendMessageByDDP.call(this, { _id, rid, msg }) : sendMessageByRest.call(this, { _id, rid, msg })); + const data = await SDK.api.post('chat.sendMessage', { message: { _id, rid, msg } }); return data; } @@ -55,12 +43,13 @@ export default async function(rid, msg) { const message = getMessage(rid, msg); const room = db.objects('subscriptions').filtered('rid == $0', rid); + // TODO: do we need this? db.write(() => { room.lastMessage = message; }); try { - const ret = await _sendMessageCall.call(this, message); + const ret = await sendMessageCall.call(this, message); db.write(() => { db.create('messages', buildMessage({ ...message, ...ret }), true); }); diff --git a/app/lib/methods/subscriptions/room.js b/app/lib/methods/subscriptions/room.js index c718cdcf..3f619ac7 100644 --- a/app/lib/methods/subscriptions/room.js +++ b/app/lib/methods/subscriptions/room.js @@ -7,9 +7,7 @@ const subscribe = rid => Promise.all([ SDK.driver.subscribe('stream-notify-room', `${ rid }/typing`, false), SDK.driver.subscribe('stream-notify-room', `${ rid }/deleteMessage`, false) ]); -const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch((e) => { - log('unsubscribeRoom', e); -})); +const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch(() => console.log('unsubscribeRoom'))); let timer = null; let promises; @@ -43,26 +41,26 @@ export default function subscribeRoom({ rid, t }) { }, 5000); }; - if (!this.connected()) { - loop(); - } else { - SDK.driver.on('logged', () => { - clearTimeout(timer); - timer = false; - }); + // if (!this.connected()) { + // loop(); + // } else { + SDK.driver.on('logged', () => { + clearTimeout(timer); + timer = false; + }); - SDK.driver.on('disconnected', () => { - if (SDK.driver.userId) { - loop(); - } - }); - - try { - promises = subscribe(rid); - } catch (e) { - log('subscribeRoom', e); + SDK.driver.on('disconnected', () => { + if (SDK.driver.userId) { + loop(); } + }); + + try { + promises = subscribe(rid); + } catch (e) { + log('subscribeRoom', e); } + // } return { stop: () => stop() diff --git a/app/lib/methods/subscriptions/rooms.js b/app/lib/methods/subscriptions/rooms.js index e12cdc37..65862c8a 100644 --- a/app/lib/methods/subscriptions/rooms.js +++ b/app/lib/methods/subscriptions/rooms.js @@ -1,4 +1,3 @@ -import Random from 'react-native-meteor/lib/Random'; import * as SDK from '@rocket.chat/sdk'; import database from '../../realm'; @@ -6,6 +5,7 @@ import { merge } from '../helpers/mergeSubscriptionsRooms'; import protectedFunction from '../helpers/protectedFunction'; import messagesStatus from '../../../constants/messagesStatus'; import log from '../../../utils/log'; +import random from '../../../utils/random'; export default async function subscribeRooms(id) { const promises = Promise.all([ @@ -30,85 +30,81 @@ export default async function subscribeRooms(id) { }, 5000); }; - if (!this.connected()) { - loop(); - } else { - SDK.driver.on('logged', () => { - clearTimeout(timer); - timer = false; - }); + SDK.driver.on('logged', () => { + clearTimeout(timer); + timer = false; + }); - SDK.driver.on('logout', () => { - clearTimeout(timer); - timer = true; - }); + SDK.driver.on('logout', () => { + clearTimeout(timer); + timer = true; + }); - SDK.driver.on('disconnected', () => { - if (SDK.driver.userId) { - loop(); - } - }); + SDK.driver.on('disconnected', () => { + if (SDK.driver.userId) { + loop(); + } + }); - SDK.driver.on('stream-notify-user', protectedFunction((e, ddpMessage) => { - if (!this.ddp || ddpMessage.msg === 'added') { - return; - } - const [type, data] = ddpMessage.fields.args; - const [, ev] = ddpMessage.fields.eventName.split('/'); - if (/subscriptions/.test(ev)) { - if (type === 'removed') { - let messages = []; - const [subscription] = database.objects('subscriptions').filtered('_id == $0', data._id); + SDK.driver.on('stream-notify-user', protectedFunction((e, ddpMessage) => { + if (ddpMessage.msg === 'added') { + return; + } + const [type, data] = ddpMessage.fields.args; + const [, ev] = ddpMessage.fields.eventName.split('/'); + if (/subscriptions/.test(ev)) { + if (type === 'removed') { + let messages = []; + const [subscription] = database.objects('subscriptions').filtered('_id == $0', data._id); - if (subscription) { - messages = database.objects('messages').filtered('rid == $0', subscription.rid); - } - database.write(() => { - database.delete(messages); - database.delete(subscription); - }); - } else { - const rooms = database.objects('rooms').filtered('_id == $0', data.rid); - const tpm = merge(data, rooms[0]); - database.write(() => { - database.create('subscriptions', tpm, true); - database.delete(rooms); - }); + if (subscription) { + messages = database.objects('messages').filtered('rid == $0', subscription.rid); } + database.write(() => { + database.delete(messages); + database.delete(subscription); + }); + } else { + const rooms = database.objects('rooms').filtered('_id == $0', data.rid); + const tpm = merge(data, rooms[0]); + database.write(() => { + database.create('subscriptions', tpm, true); + database.delete(rooms); + }); } - if (/rooms/.test(ev)) { - if (type === 'updated') { - const [sub] = database.objects('subscriptions').filtered('rid == $0', data._id); - database.write(() => { - merge(sub, data); - }); - } else if (type === 'inserted') { - database.write(() => { - database.create('rooms', data, true); - }); - } + } + if (/rooms/.test(ev)) { + if (type === 'updated') { + const [sub] = database.objects('subscriptions').filtered('rid == $0', data._id); + database.write(() => { + merge(sub, data); + }); + } else if (type === 'inserted') { + database.write(() => { + database.create('rooms', data, true); + }); } - if (/message/.test(ev)) { - const [args] = ddpMessage.fields.args; - const _id = Random.id(); - const message = { + } + if (/message/.test(ev)) { + const [args] = ddpMessage.fields.args; + const _id = random(17); + const message = { + _id, + rid: args.rid, + msg: args.msg, + ts: new Date(), + _updatedAt: new Date(), + status: messagesStatus.SENT, + u: { _id, - rid: args.rid, - msg: args.msg, - ts: new Date(), - _updatedAt: new Date(), - status: messagesStatus.SENT, - u: { - _id, - username: 'rocket.cat' - } - }; - requestAnimationFrame(() => database.write(() => { - database.create('messages', message, true); - })); - } - })); - } + username: 'rocket.cat' + } + }; + requestAnimationFrame(() => database.write(() => { + database.create('messages', message, true); + })); + } + })); try { await promises; diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index df48e275..85bfb584 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -8,12 +8,11 @@ import defaultSettings from '../constants/settings'; import messagesStatus from '../constants/messagesStatus'; import database from './realm'; import log from '../utils/log'; -// import * as actions from '../actions'; import { - setUser, setLoginServices, loginRequest, loginSuccess, loginFailure, logout + setUser, setLoginServices, loginRequest, loginFailure, logout } from '../actions/login'; -import { disconnect, connectSuccess } from '../actions/connect'; +import { disconnect, connectSuccess, connectRequest } from '../actions/connect'; import { setActiveUser } from '../actions/activeUsers'; import { starredMessagesReceived, starredMessageUnstarred } from '../actions/starredMessages'; import { pinnedMessagesReceived, pinnedMessageUnpinned } from '../actions/pinnedMessages'; @@ -39,7 +38,7 @@ import _buildMessage from './methods/helpers/buildMessage'; import loadMessagesForRoom from './methods/loadMessagesForRoom'; import loadMissedMessages from './methods/loadMissedMessages'; -import sendMessage, { getMessage, _sendMessageCall } from './methods/sendMessage'; +import sendMessage, { getMessage, sendMessageCall } from './methods/sendMessage'; import { sendFileMessage, cancelUpload, isUploadActive } from './methods/sendFileMessage'; import { getDeviceToken } from '../push'; @@ -124,320 +123,257 @@ const RocketChat = { this.activeUsers[ddpMessage.id] = { ...this.activeUsers[ddpMessage.id], ...activeUser, ...ddpMessage.fields }; } }, - async loginSuccess(user) { - if (!user) { - const { user: u } = reduxStore.getState().login; - user = Object.assign({}, u); - } - - // TODO: one api call - // call /me only one time - try { - if (!user.username) { - // get me from api - let me = await SDK.api.get('me'); - // if server didn't found username - if (!me.username) { - // search username from credentials (sent during registerSubmit) - const { username } = reduxStore.getState().login.credentials; - if (username) { - // set username - await RocketChat.setUsername({ username }); - me = { ...me, username }; - } - } - user = { ...user, ...me }; - } - } catch (e) { - log('SDK.loginSuccess set username', e); - } - - try { - if (user.username) { - const userInfo = await SDK.api.get('users.info', { userId: user.id }); - user = { ...user, ...userInfo.user }; - } - - RocketChat.registerPushToken(user.id); - reduxStore.dispatch(setUser(user)); - reduxStore.dispatch(loginSuccess(user)); - this.ddp.subscribe('userData'); - } catch (e) { - log('SDK.loginSuccess', e); - } + loginSuccess({ user }) { + SDK.driver.login({ resume: user.token }); + reduxStore.dispatch(setUser(user)); + this.getRooms().catch(e => console.log(e)); + this.getPermissions(); + this.getCustomEmoji(); + this.registerPushToken().then(result => console.log(result)).catch(e => alert(e)); }, - connect(url, login) { - return new Promise(() => { - if (this.ddp) { - RocketChat.disconnect(); - this.ddp = null; + connect({ server, user }) { + database.setActiveDB(server); + + if (this.ddp) { + RocketChat.disconnect(); + this.ddp = null; + } + + SDK.api.setBaseUrl(server); + this.getSettings(); + + if (user && user.token) { + reduxStore.dispatch(loginRequest({ resume: user.token })); + } + + // Use useSsl: false only if server url starts with http:// + const useSsl = !/http:\/\//.test(server); + + reduxStore.dispatch(connectRequest()); + SDK.driver.connect({ host: server, useSsl }, (err, ddp) => { + if (err) { + return console.warn(err); } - - SDK.api.setBaseUrl(url); - - if (login) { - SDK.api.setAuth({ authToken: login.token, userId: login.id }); - RocketChat.setApiUser({ userId: login.id, authToken: login.token }); + this.ddp = ddp; + if (user && user.token) { + SDK.driver.login({ resume: user.token }); } - - SDK.driver.connect({ host: url, useSsl: true }, (err, ddp) => { - if (err) { - return console.warn(err); - } - this.ddp = ddp; - if (login) { - SDK.driver.login({ resume: login.resume }); - } - }); - - SDK.driver.on('connected', () => { - reduxStore.dispatch(connectSuccess()); - SDK.driver.subscribe('activeUsers'); - SDK.driver.subscribe('roles'); - RocketChat.getSettings(); - RocketChat.getPermissions(); - RocketChat.getCustomEmoji(); - }); - - SDK.driver.on('login', protectedFunction(() => reduxStore.dispatch(loginRequest()))); - - SDK.driver.on('forbidden', protectedFunction(() => reduxStore.dispatch(logout()))); - - SDK.driver.on('users', protectedFunction((error, ddpMessage) => RocketChat._setUser(ddpMessage))); - - // SDK.driver.on('background', () => this.getRooms().catch(e => log('background getRooms', e))); - - SDK.driver.on('logged', protectedFunction((error, user) => { - RocketChat.setApiUser({ userId: user.id, authToken: user.token }); - this.loginSuccess(user); - this.getRooms().catch(e => log('logged getRooms', e)); - this.subscribeRooms(user.id); - })); - - SDK.driver.on('disconnected', protectedFunction(() => { - reduxStore.dispatch(disconnect()); - })); - - SDK.driver.on('stream-room-messages', (error, ddpMessage) => { - // TODO: debounce - const message = _buildMessage(ddpMessage.fields.args[0]); - requestAnimationFrame(() => reduxStore.dispatch(roomMessageReceived(message))); - }); - - SDK.driver.on('stream-notify-room', protectedFunction((error, ddpMessage) => { - const [_rid, ev] = ddpMessage.fields.eventName.split('/'); - if (ev === 'typing') { - reduxStore.dispatch(someoneTyping({ _rid, username: ddpMessage.fields.args[0], typing: ddpMessage.fields.args[1] })); - } else if (ev === 'deleteMessage') { - database.write(() => { - if (ddpMessage && ddpMessage.fields && ddpMessage.fields.args.length > 0) { - const { _id } = ddpMessage.fields.args[0]; - const message = database.objects('messages').filtered('_id = $0', _id); - database.delete(message); - } - }); - } - })); - - SDK.driver.on('rocketchat_starred_message', protectedFunction((error, ddpMessage) => { - if (ddpMessage.msg === 'added') { - this.starredMessages = this.starredMessages || []; - - if (this.starredMessagesTimer) { - clearTimeout(this.starredMessagesTimer); - this.starredMessagesTimer = null; - } - - this.starredMessagesTimer = setTimeout(protectedFunction(() => { - reduxStore.dispatch(starredMessagesReceived(this.starredMessages)); - this.starredMessagesTimer = null; - return this.starredMessages = []; - }), 1000); - const message = ddpMessage.fields; - message._id = ddpMessage.id; - const starredMessage = _buildMessage(message); - this.starredMessages = [...this.starredMessages, starredMessage]; - } - if (ddpMessage.msg === 'removed') { - if (reduxStore.getState().starredMessages.isOpen) { - return reduxStore.dispatch(starredMessageUnstarred(ddpMessage.id)); - } - } - })); - - SDK.driver.on('rocketchat_pinned_message', protectedFunction((error, ddpMessage) => { - if (ddpMessage.msg === 'added') { - this.pinnedMessages = this.pinnedMessages || []; - - if (this.pinnedMessagesTimer) { - clearTimeout(this.pinnedMessagesTimer); - this.pinnedMessagesTimer = null; - } - - this.pinnedMessagesTimer = setTimeout(() => { - reduxStore.dispatch(pinnedMessagesReceived(this.pinnedMessages)); - this.pinnedMessagesTimer = null; - return this.pinnedMessages = []; - }, 1000); - const message = ddpMessage.fields; - message._id = ddpMessage.id; - const pinnedMessage = _buildMessage(message); - this.pinnedMessages = [...this.pinnedMessages, pinnedMessage]; - } - if (ddpMessage.msg === 'removed') { - if (reduxStore.getState().pinnedMessages.isOpen) { - return reduxStore.dispatch(pinnedMessageUnpinned(ddpMessage.id)); - } - } - })); - - SDK.driver.on('rocketchat_mentioned_message', protectedFunction((error, ddpMessage) => { - if (ddpMessage.msg === 'added') { - this.mentionedMessages = this.mentionedMessages || []; - - if (this.mentionedMessagesTimer) { - clearTimeout(this.mentionedMessagesTimer); - this.mentionedMessagesTimer = null; - } - - this.mentionedMessagesTimer = setTimeout(() => { - reduxStore.dispatch(mentionedMessagesReceived(this.mentionedMessages)); - this.mentionedMessagesTimer = null; - return this.mentionedMessages = []; - }, 1000); - const message = ddpMessage.fields; - message._id = ddpMessage.id; - const mentionedMessage = _buildMessage(message); - this.mentionedMessages = [...this.mentionedMessages, mentionedMessage]; - } - })); - - SDK.driver.on('rocketchat_snippeted_message', protectedFunction((error, ddpMessage) => { - if (ddpMessage.msg === 'added') { - this.snippetedMessages = this.snippetedMessages || []; - - if (this.snippetedMessagesTimer) { - clearTimeout(this.snippetedMessagesTimer); - this.snippetedMessagesTimer = null; - } - - this.snippetedMessagesTimer = setTimeout(() => { - reduxStore.dispatch(snippetedMessagesReceived(this.snippetedMessages)); - this.snippetedMessagesTimer = null; - return this.snippetedMessages = []; - }, 1000); - const message = ddpMessage.fields; - message._id = ddpMessage.id; - const snippetedMessage = _buildMessage(message); - this.snippetedMessages = [...this.snippetedMessages, snippetedMessage]; - } - })); - - SDK.driver.on('room_files', protectedFunction((error, ddpMessage) => { - if (ddpMessage.msg === 'added') { - this.roomFiles = this.roomFiles || []; - - if (this.roomFilesTimer) { - clearTimeout(this.roomFilesTimer); - this.roomFilesTimer = null; - } - - this.roomFilesTimer = setTimeout(() => { - reduxStore.dispatch(roomFilesReceived(this.roomFiles)); - this.roomFilesTimer = null; - return this.roomFiles = []; - }, 1000); - const { fields } = ddpMessage; - const message = { - _id: ddpMessage.id, - ts: fields.uploadedAt, - msg: fields.description, - status: 0, - attachments: [{ - title: fields.name - }], - urls: [], - reactions: [], - u: { - username: fields.user.username - } - }; - const fileUrl = `/file-upload/${ ddpMessage.id }/${ fields.name }`; - if (/image/.test(fields.type)) { - message.attachments[0].image_type = fields.type; - message.attachments[0].image_url = fileUrl; - } else if (/audio/.test(fields.type)) { - message.attachments[0].audio_type = fields.type; - message.attachments[0].audio_url = fileUrl; - } else if (/video/.test(fields.type)) { - message.attachments[0].video_type = fields.type; - message.attachments[0].video_url = fileUrl; - } - this.roomFiles = [...this.roomFiles, message]; - } - })); - - SDK.driver.on('rocketchat_roles', protectedFunction((error, ddpMessage) => { - this.roles = this.roles || {}; - - if (this.roleTimer) { - clearTimeout(this.roleTimer); - this.roleTimer = null; - } - this.roleTimer = setTimeout(() => { - reduxStore.dispatch(setRoles(this.roles)); - - database.write(() => { - foreach(this.roles, (description, _id) => { - database.create('roles', { _id, description }, true); - }); - }); - - this.roleTimer = null; - return this.roles = {}; - }, 1000); - this.roles[ddpMessage.id] = (ddpMessage.fields && ddpMessage.fields.description) || undefined; - })); - - // SDK.driver.on('error', (err) => { - // log('SDK.onerror', err); - // reduxStore.dispatch(connectFailure()); - // }); - - // SDK.driver.on('open', protectedFunction(() => { - // RocketChat.getSettings(); - // RocketChat.getPermissions(); - // reduxStore.dispatch(connectSuccess()); - // resolve(); - // })); - - // this.ddp.once('open', protectedFunction(() => { - // this.ddp.subscribe('activeUsers'); - // this.ddp.subscribe('roles'); - // RocketChat.getCustomEmoji(); - // })); - }).catch((e) => { - log('SDK.connect catch', e); }); - }, - connected() { - return SDK.driver.ddp && SDK.driver.ddp._logged; + + SDK.driver.on('connected', () => { + reduxStore.dispatch(connectSuccess()); + }); + + SDK.driver.on('disconnected', protectedFunction(() => { + reduxStore.dispatch(disconnect()); + })); + + SDK.driver.on('logged', protectedFunction((error, u) => { + this.subscribeRooms(u.id); + SDK.driver.subscribe('activeUsers'); + SDK.driver.subscribe('roles'); + })); + + SDK.driver.on('forbidden', protectedFunction(() => reduxStore.dispatch(logout()))); + + SDK.driver.on('users', protectedFunction((error, ddpMessage) => RocketChat._setUser(ddpMessage))); + + SDK.driver.on('stream-room-messages', (error, ddpMessage) => { + // TODO: debounce + const message = _buildMessage(ddpMessage.fields.args[0]); + requestAnimationFrame(() => reduxStore.dispatch(roomMessageReceived(message))); + }); + + SDK.driver.on('stream-notify-room', protectedFunction((error, ddpMessage) => { + const [_rid, ev] = ddpMessage.fields.eventName.split('/'); + if (ev === 'typing') { + reduxStore.dispatch(someoneTyping({ _rid, username: ddpMessage.fields.args[0], typing: ddpMessage.fields.args[1] })); + } else if (ev === 'deleteMessage') { + database.write(() => { + if (ddpMessage && ddpMessage.fields && ddpMessage.fields.args.length > 0) { + const { _id } = ddpMessage.fields.args[0]; + const message = database.objects('messages').filtered('_id = $0', _id); + database.delete(message); + } + }); + } + })); + + SDK.driver.on('rocketchat_starred_message', protectedFunction((error, ddpMessage) => { + if (ddpMessage.msg === 'added') { + this.starredMessages = this.starredMessages || []; + + if (this.starredMessagesTimer) { + clearTimeout(this.starredMessagesTimer); + this.starredMessagesTimer = null; + } + + this.starredMessagesTimer = setTimeout(protectedFunction(() => { + reduxStore.dispatch(starredMessagesReceived(this.starredMessages)); + this.starredMessagesTimer = null; + return this.starredMessages = []; + }), 1000); + const message = ddpMessage.fields; + message._id = ddpMessage.id; + const starredMessage = _buildMessage(message); + this.starredMessages = [...this.starredMessages, starredMessage]; + } + if (ddpMessage.msg === 'removed') { + if (reduxStore.getState().starredMessages.isOpen) { + return reduxStore.dispatch(starredMessageUnstarred(ddpMessage.id)); + } + } + })); + + SDK.driver.on('rocketchat_pinned_message', protectedFunction((error, ddpMessage) => { + if (ddpMessage.msg === 'added') { + this.pinnedMessages = this.pinnedMessages || []; + + if (this.pinnedMessagesTimer) { + clearTimeout(this.pinnedMessagesTimer); + this.pinnedMessagesTimer = null; + } + + this.pinnedMessagesTimer = setTimeout(() => { + reduxStore.dispatch(pinnedMessagesReceived(this.pinnedMessages)); + this.pinnedMessagesTimer = null; + return this.pinnedMessages = []; + }, 1000); + const message = ddpMessage.fields; + message._id = ddpMessage.id; + const pinnedMessage = _buildMessage(message); + this.pinnedMessages = [...this.pinnedMessages, pinnedMessage]; + } + if (ddpMessage.msg === 'removed') { + if (reduxStore.getState().pinnedMessages.isOpen) { + return reduxStore.dispatch(pinnedMessageUnpinned(ddpMessage.id)); + } + } + })); + + SDK.driver.on('rocketchat_mentioned_message', protectedFunction((error, ddpMessage) => { + if (ddpMessage.msg === 'added') { + this.mentionedMessages = this.mentionedMessages || []; + + if (this.mentionedMessagesTimer) { + clearTimeout(this.mentionedMessagesTimer); + this.mentionedMessagesTimer = null; + } + + this.mentionedMessagesTimer = setTimeout(() => { + reduxStore.dispatch(mentionedMessagesReceived(this.mentionedMessages)); + this.mentionedMessagesTimer = null; + return this.mentionedMessages = []; + }, 1000); + const message = ddpMessage.fields; + message._id = ddpMessage.id; + const mentionedMessage = _buildMessage(message); + this.mentionedMessages = [...this.mentionedMessages, mentionedMessage]; + } + })); + + SDK.driver.on('rocketchat_snippeted_message', protectedFunction((error, ddpMessage) => { + if (ddpMessage.msg === 'added') { + this.snippetedMessages = this.snippetedMessages || []; + + if (this.snippetedMessagesTimer) { + clearTimeout(this.snippetedMessagesTimer); + this.snippetedMessagesTimer = null; + } + + this.snippetedMessagesTimer = setTimeout(() => { + reduxStore.dispatch(snippetedMessagesReceived(this.snippetedMessages)); + this.snippetedMessagesTimer = null; + return this.snippetedMessages = []; + }, 1000); + const message = ddpMessage.fields; + message._id = ddpMessage.id; + const snippetedMessage = _buildMessage(message); + this.snippetedMessages = [...this.snippetedMessages, snippetedMessage]; + } + })); + + SDK.driver.on('room_files', protectedFunction((error, ddpMessage) => { + if (ddpMessage.msg === 'added') { + this.roomFiles = this.roomFiles || []; + + if (this.roomFilesTimer) { + clearTimeout(this.roomFilesTimer); + this.roomFilesTimer = null; + } + + this.roomFilesTimer = setTimeout(() => { + reduxStore.dispatch(roomFilesReceived(this.roomFiles)); + this.roomFilesTimer = null; + return this.roomFiles = []; + }, 1000); + const { fields } = ddpMessage; + const message = { + _id: ddpMessage.id, + ts: fields.uploadedAt, + msg: fields.description, + status: 0, + attachments: [{ + title: fields.name + }], + urls: [], + reactions: [], + u: { + username: fields.user.username + } + }; + const fileUrl = `/file-upload/${ ddpMessage.id }/${ fields.name }`; + if (/image/.test(fields.type)) { + message.attachments[0].image_type = fields.type; + message.attachments[0].image_url = fileUrl; + } else if (/audio/.test(fields.type)) { + message.attachments[0].audio_type = fields.type; + message.attachments[0].audio_url = fileUrl; + } else if (/video/.test(fields.type)) { + message.attachments[0].video_type = fields.type; + message.attachments[0].video_url = fileUrl; + } + this.roomFiles = [...this.roomFiles, message]; + } + })); + + SDK.driver.on('rocketchat_roles', protectedFunction((error, ddpMessage) => { + this.roles = this.roles || {}; + + if (this.roleTimer) { + clearTimeout(this.roleTimer); + this.roleTimer = null; + } + this.roleTimer = setTimeout(() => { + reduxStore.dispatch(setRoles(this.roles)); + + database.write(() => { + foreach(this.roles, (description, _id) => { + database.create('roles', { _id, description }, true); + }); + }); + + this.roleTimer = null; + return this.roles = {}; + }, 1000); + this.roles[ddpMessage.id] = (ddpMessage.fields && ddpMessage.fields.description) || undefined; + })); }, - register({ credentials }) { - return call('registerUser', credentials); + register(credentials) { + return SDK.api.post('users.register', credentials, false); }, - setUsername({ username }) { + setUsername(username) { return call('setUsername', username); }, forgotPassword(email) { - return call('sendForgotPasswordEmail', email); + return SDK.api.post('users.forgotPassword', { email }, false); }, - async loginWithPassword({ username, password, code }) { - let params = { username, password }; + async loginWithPassword({ user, password, code }) { + let params = { user, password }; const state = reduxStore.getState(); if (state.settings.LDAP_Enable) { @@ -451,16 +387,12 @@ const RocketChat = { ...params, crowd: true }; - } else if (typeof username === 'string' && username.indexOf('@') !== -1) { - params.email = username; - delete params.username; } if (code) { params = { ...params, - code, - totp: true + code }; } @@ -471,59 +403,84 @@ const RocketChat = { } }, + async loginOAuth(params) { + try { + const result = await SDK.driver.login(params); + reduxStore.dispatch(loginRequest({ resume: result.token })); + } catch (error) { + throw error; + } + }, + async login(params) { try { - await SDK.driver.login(params); + return await SDK.api.login(params); } catch (e) { reduxStore.dispatch(loginFailure(e)); throw e; } }, - logout({ server }) { + async logout({ server }) { + // this.removePushToken().catch(error => console.log(error)); try { - RocketChat.disconnect(); - SDK.driver.logout(); + await this.removePushToken(); } catch (error) { - console.warn(error); + console.log('logout -> removePushToken -> catch -> error', error); } - AsyncStorage.removeItem(TOKEN_KEY); - AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`); + try { + await SDK.api.logout(); + } catch (error) { + console.log('​logout -> api logout -> catch -> error', error); + } + SDK.driver.ddp.disconnect(); + this.ddp = null; + + Promise.all([ + AsyncStorage.removeItem('currentServer'), + AsyncStorage.removeItem(TOKEN_KEY), + AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`) + ]).catch(error => console.log(error)); + try { database.deleteAll(); } catch (error) { - console.warn(error); + console.log(error); } }, disconnect() { try { SDK.driver.unsubscribeAll(); } catch (error) { - console.warn(error); + console.log(error); } RocketChat.setApiUser({ userId: null, authToken: null }); }, setApiUser({ userId, authToken }) { SDK.api.setAuth({ userId, authToken }); - SDK.api.currentLogin = { userId, authToken }; + SDK.api.currentLogin = null; }, - registerPushToken(userId) { - const deviceToken = getDeviceToken(); - if (deviceToken) { - const key = Platform.OS === 'ios' ? 'apn' : 'gcm'; - const data = { - id: `RocketChatRN${ userId }`, - token: { [key]: deviceToken }, - appName: 'chat.rocket.reactnative', // TODO: try to get from config file - userId, - metadata: {} - }; - return call('raix:push-update', data); + registerPushToken() { + return new Promise((resolve) => { + const token = getDeviceToken(); + if (token) { + const type = Platform.OS === 'ios' ? 'apn' : 'gcm'; + const data = { + value: token, + type, + appName: 'chat.rocket.reactnative' // TODO: try to get from config file + }; + return SDK.api.post('push.token', data); + } + return resolve(); + }); + }, + removePushToken() { + const token = getDeviceToken(); + if (token) { + return SDK.api.del('push.token', { token }); } + return Promise.resolve(); }, - - // updatePushToken(pushId) { - // return call('raix:push-setuser', pushId); - // }, loadMissedMessages, loadMessagesForRoom, getMessage, @@ -532,11 +489,18 @@ const RocketChat = { readMessages, async resendMessage(messageId) { const message = await database.objects('messages').filtered('_id = $0', messageId)[0]; - database.write(() => { - message.status = messagesStatus.TEMP; - database.create('messages', message, true); - }); - return _sendMessageCall.call(this, JSON.parse(JSON.stringify(message))); + try { + database.write(() => { + message.status = messagesStatus.TEMP; + database.create('messages', message, true); + }); + await sendMessageCall.call(this, JSON.parse(JSON.stringify(message))); + } catch (error) { + database.write(() => { + message.status = messagesStatus.ERROR; + database.create('messages', message, true); + }); + } }, async search({ text, filterUsers = true, filterRooms = true }) { @@ -596,10 +560,11 @@ const RocketChat = { }, createDirectMessage(username) { - return call('createDirectMessage', username); + return SDK.api.post('im.create', { username }); }, - joinRoom(rid) { - return call('joinRoom', rid); + joinRoom(roomId) { + // TODO: join code + return SDK.api.post('channels.join', { roomId }); }, sendFileMessage, cancelUpload, @@ -608,8 +573,7 @@ const RocketChat = { getPermissions, getCustomEmoji, parseSettings: settings => settings.reduce((ret, item) => { - ret[item._id] = item[defaultSettings[item._id].type] || item.valueAsString || item.valueAsNumber - || item.valueAsBoolean || item.value; + ret[item._id] = item[defaultSettings[item._id].type]; return ret; }, {}), _prepareSettings(settings) { @@ -618,7 +582,6 @@ const RocketChat = { return setting; }); }, - _filterSettings: settings => settings.filter(setting => defaultSettings[setting._id] && (setting.value || setting.valueAsString || setting.valueAsNumber || setting.valueAsBoolean)), parseEmojis: emojis => emojis.reduce((ret, item) => { ret[item.name] = item.extension; item.aliases.forEach((alias) => { @@ -633,20 +596,24 @@ const RocketChat = { return emojis; }, deleteMessage(message) { - return call('deleteMessage', { _id: message._id }); + const { _id, rid } = message; + return SDK.api.post('chat.delete', { roomId: rid, msgId: _id }); }, editMessage(message) { const { _id, msg, rid } = message; - return call('updateMessage', { _id, msg, rid }); + return SDK.api.post('chat.update', { roomId: rid, msgId: _id, text: msg }); }, toggleStarMessage(message) { - return call('starMessage', { _id: message._id, rid: message.rid, starred: !message.starred }); + if (message.starred) { + return SDK.api.post('chat.unStarMessage', { messageId: message._id }); + } + return SDK.api.post('chat.starMessage', { messageId: message._id }); }, togglePinMessage(message) { if (message.pinned) { - return call('unpinMessage', message); + return SDK.api.post('chat.unPinMessage', { messageId: message._id }); } - return call('pinMessage', message); + return SDK.api.post('chat.pinMessage', { messageId: message._id }); }, getRoom(rid) { const [result] = database.objects('subscriptions').filtered('rid = $0', rid); @@ -655,6 +622,9 @@ const RocketChat = { } return Promise.resolve(result); }, + getRoomInfo(roomId) { + return SDK.api.get('rooms.info', { roomId }); + }, async getPermalink(message) { let room; try { @@ -691,10 +661,10 @@ const RocketChat = { return call('UserPresence:setDefaultStatus', status); }, setReaction(emoji, messageId) { - return call('setReaction', emoji, messageId); + return SDK.api.post('chat.react', { emoji, messageId }); }, - toggleFavorite(rid, f) { - return call('toggleFavorite', rid, !f); + toggleFavorite(roomId, favorite) { + return SDK.api.post('rooms.favorite', { roomId, favorite }); }, getRoomMembers(rid, allUsers) { return call('getUsersOfRoom', rid, allUsers); @@ -702,6 +672,9 @@ const RocketChat = { getUserRoles() { return call('getUserRoles'); }, + getRoomCounters(roomId, t) { + return SDK.api.get(`${ this.roomTypeToApiType(t) }.counters`, { roomId }); + }, async getRoomMember(rid, currentUserId) { try { const membersResult = await RocketChat.getRoomMembers(rid, true); @@ -716,8 +689,8 @@ const RocketChat = { } return call('unblockUser', { rid, blocked }); }, - leaveRoom(rid) { - return call('leaveRoom', rid); + leaveRoom(roomId, t) { + return SDK.api.post(`${ this.roomTypeToApiType(t) }.leave`, { roomId }); }, eraseRoom(rid) { return call('eraseRoom', rid); @@ -743,8 +716,8 @@ const RocketChat = { saveUserPreferences(params) { return call('saveUserPreferences', params); }, - saveNotificationSettings(rid, param, value) { - return call('saveNotificationSettings', rid, param, value); + saveNotificationSettings(roomId, notifications) { + return SDK.api.post('rooms.saveNotification', { roomId, notifications }); }, messageSearch(text, rid, limit) { return call('messageSearch', text, rid, limit); @@ -824,7 +797,13 @@ const RocketChat = { } }, getUsernameSuggestion() { - return SDK.driver.asyncCall('getUsernameSuggestion'); + return SDK.api.get('users.getUsernameSuggestion'); + }, + roomTypeToApiType(t) { + const types = { + c: 'channels', d: 'im', p: 'groups' + }; + return types[t]; } }; diff --git a/app/reducers/app.js b/app/reducers/app.js index 7e0dec2a..2e06cb67 100644 --- a/app/reducers/app.js +++ b/app/reducers/app.js @@ -4,7 +4,6 @@ import { APP } from '../actions/actionsTypes'; const initialState = { root: null, stackRoot: 'RoomsListView', - starting: true, ready: false, inactive: false, background: false @@ -46,14 +45,12 @@ export default function app(state = initialState, action) { case APP.INIT: return { ...state, - ready: false, - starting: true + ready: false }; case APP.READY: return { ...state, - ready: true, - starting: false + ready: true }; default: return state; diff --git a/app/reducers/connect.js b/app/reducers/connect.js index abfd142c..324cb710 100644 --- a/app/reducers/connect.js +++ b/app/reducers/connect.js @@ -2,10 +2,7 @@ import { METEOR } from '../actions/actionsTypes'; const initialState = { connecting: false, - connected: false, - errorMessage: '', - disconnected_by_user: false, - failure: false + connected: false }; export default function connect(state = initialState, action) { @@ -13,28 +10,13 @@ export default function connect(state = initialState, action) { case METEOR.REQUEST: return { ...state, - connecting: true, - disconnected_by_user: false + connecting: true }; case METEOR.SUCCESS: return { ...state, connecting: false, - connected: true, - failure: false - }; - case METEOR.FAILURE: - return { - ...state, - connecting: false, - connected: false, - failure: true, - errorMessage: action.err - }; - case METEOR.DISCONNECT_BY_USER: - return { - ...state, - disconnected_by_user: true + connected: true }; case METEOR.DISCONNECT: return initialState; diff --git a/app/reducers/login.js b/app/reducers/login.js index ef222d06..77c3c6f6 100644 --- a/app/reducers/login.js +++ b/app/reducers/login.js @@ -3,11 +3,9 @@ import * as types from '../actions/actionsTypes'; const initialState = { isAuthenticated: false, isFetching: false, - token: '', user: {}, - error: '', - services: {}, - credentials: {} + error: {}, + services: {} }; export default function login(state = initialState, action) { @@ -20,21 +18,16 @@ export default function login(state = initialState, action) { isFetching: true, isAuthenticated: false, failure: false, - error: '' + error: {} }; case types.LOGIN.SUCCESS: return { ...state, isFetching: false, isAuthenticated: true, - user: { - ...state.user, - ...action.user - }, - token: action.user.token, + user: action.user, failure: false, - error: '', - credentials: {} + error: {} }; case types.LOGIN.FAILURE: return { @@ -46,70 +39,12 @@ export default function login(state = initialState, action) { }; case types.LOGOUT: return initialState; - case types.LOGIN.SET_TOKEN: - return { - ...state, - token: action.token, - user: action.user - }; - case types.LOGIN.RESTORE_TOKEN: - return { - ...state, - token: action.token - }; - case types.LOGIN.REGISTER_SUBMIT: - return { - ...state, - isFetching: true, - failure: false, - error: '', - credentials: action.credentials - }; - case types.LOGIN.REGISTER_SUCCESS: - return { - ...state, - isFetching: false, - failure: false, - error: '', - credentials: {} - }; - case types.LOGIN.SET_USERNAME_SUBMIT: - return { - ...state, - isFetching: true, - credentials: action.credentials - }; - case types.LOGIN.SET_USERNAME_SUCCESS: - return { - ...state, - isFetching: false - }; - case types.FORGOT_PASSWORD.REQUEST: - return { - ...state, - isFetching: true, - failure: false, - success: false - }; - case types.FORGOT_PASSWORD.SUCCESS: - return { - ...state, - isFetching: false, - success: true - }; - case types.FORGOT_PASSWORD.FAILURE: - return { - ...state, - isFetching: false, - failure: true, - error: action.err - }; case types.USER.SET: return { ...state, user: { ...state.user, - ...action + ...action.user } }; case types.LOGIN.SET_SERVICES: diff --git a/app/reducers/sortPreferences.js b/app/reducers/sortPreferences.js index d5cd2ba3..eee86ea6 100644 --- a/app/reducers/sortPreferences.js +++ b/app/reducers/sortPreferences.js @@ -2,9 +2,9 @@ import { SORT_PREFERENCES } from '../actions/actionsTypes'; const initialState = { sortBy: 'activity', - groupByType: true, - showFavorites: true, - showUnread: true + groupByType: false, + showFavorites: false, + showUnread: false }; diff --git a/app/sagas/connect.js b/app/sagas/connect.js deleted file mode 100644 index bac42bba..00000000 --- a/app/sagas/connect.js +++ /dev/null @@ -1,50 +0,0 @@ -import { - call, takeLatest, select, put -} from 'redux-saga/effects'; -import { AsyncStorage } from 'react-native'; -import { METEOR } from '../actions/actionsTypes'; -import RocketChat from '../lib/rocketchat'; -import { setToken } from '../actions/login'; - -const getServer = ({ server }) => server.server; -const getToken = function* getToken() { - const currentServer = yield select(getServer); - const user = yield call([AsyncStorage, 'getItem'], `${ RocketChat.TOKEN_KEY }-${ currentServer }`); - if (user) { - yield put(setToken(JSON.parse(user))); - try { - yield call([AsyncStorage, 'setItem'], RocketChat.TOKEN_KEY, JSON.parse(user).token || ''); - } catch (error) { - console.warn('getToken', error); - } - return JSON.parse(user); - } - - yield AsyncStorage.removeItem(RocketChat.TOKEN_KEY); - yield put(setToken()); - return null; -}; - - -const connect = (...args) => RocketChat.connect(...args); - -const test = function* test() { - try { - const server = yield select(getServer); - const user = yield call(getToken); - // const response = - // yield all([call(connect, server, user && user.token ? { resume: user.token, ...user.user } : undefined)]);// , put(loginRequest({ resume: user.token }))]); - yield call(connect, server, user && user.token ? { resume: user.token, ...user } : null); - // yield put(connectSuccess(response)); - } catch (err) { - console.warn('test', err); - // yield put(connectFailure(err.status)); - } -}; - -const root = function* root() { - yield takeLatest(METEOR.REQUEST, test); - // yield take(METEOR.SUCCESS, watchConnect); - // yield takeLatest(METEOR.SUCCESS, watchConnect); -}; -export default root; diff --git a/app/sagas/index.js b/app/sagas/index.js index 7547ed38..d0e0d675 100644 --- a/app/sagas/index.js +++ b/app/sagas/index.js @@ -1,6 +1,5 @@ import { all } from 'redux-saga/effects'; import login from './login'; -import connect from './connect'; import rooms from './rooms'; import messages from './messages'; import selectServer from './selectServer'; @@ -20,7 +19,6 @@ const root = function* root() { createChannel(), rooms(), login(), - connect(), messages(), selectServer(), state(), diff --git a/app/sagas/init.js b/app/sagas/init.js index 022cba0b..6807ad03 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -1,44 +1,33 @@ import { AsyncStorage } from 'react-native'; -import { call, put, takeLatest } from 'redux-saga/effects'; +import { put, takeLatest, all } from 'redux-saga/effects'; import * as actions from '../actions'; import { selectServerRequest } from '../actions/server'; -import { restoreToken, setUser } from '../actions/login'; import { setAllPreferences } from '../actions/sortPreferences'; import { APP } from '../actions/actionsTypes'; import RocketChat from '../lib/rocketchat'; import log from '../utils/log'; -import I18n from '../i18n'; const restore = function* restore() { try { - const token = yield call([AsyncStorage, 'getItem'], RocketChat.TOKEN_KEY); - if (token) { - yield put(restoreToken(token)); - } else { - yield put(actions.appStart('outside')); - } - - const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer'); - if (currentServer) { - const user = yield call([AsyncStorage, 'getItem'], `${ RocketChat.TOKEN_KEY }-${ currentServer }`); - if (user) { - const userParsed = JSON.parse(user); - if (userParsed.language) { - I18n.locale = userParsed.language; - } - yield put(selectServerRequest(currentServer)); - yield put(setUser(userParsed)); - } else { - yield put(actions.appStart('outside')); - } - } else { - yield put(actions.appStart('outside')); - } + const { token, server } = yield all({ + token: AsyncStorage.getItem(RocketChat.TOKEN_KEY), + server: AsyncStorage.getItem('currentServer') + }); const sortPreferences = yield RocketChat.getSortPreferences(); yield put(setAllPreferences(sortPreferences)); + if (!token || !server) { + yield all([ + AsyncStorage.removeItem(RocketChat.TOKEN_KEY), + AsyncStorage.removeItem('currentServer') + ]); + yield put(actions.appStart('outside')); + } else if (server) { + yield put(selectServerRequest(server)); + } + yield put(actions.appReady({})); } catch (e) { log('restore', e); diff --git a/app/sagas/login.js b/app/sagas/login.js index 48048264..6dad0566 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -1,83 +1,68 @@ import { AsyncStorage } from 'react-native'; -import { delay } from 'redux-saga'; import { - put, call, takeLatest, select, all + put, call, takeLatest, select } from 'redux-saga/effects'; import { Navigation } from 'react-native-navigation'; import * as types from '../actions/actionsTypes'; import { appStart } from '../actions'; import { serverFinishAdd } from '../actions/server'; -import { - registerRequest, - loginFailure, - setUsernameRequest, - setUsernameSuccess, - forgotPasswordSuccess, - forgotPasswordFailure -} from '../actions/login'; +import { loginFailure, loginSuccess } from '../actions/login'; import RocketChat from '../lib/rocketchat'; import log from '../utils/log'; import I18n from '../i18n'; -const getUser = state => state.login.user; const getServer = state => state.server.server; - -const loginCall = args => RocketChat.loginWithPassword(args); -const registerCall = args => RocketChat.register(args); -const setUsernameCall = args => RocketChat.setUsername(args); -const loginSuccessCall = () => RocketChat.loginSuccess(); +const loginWithPasswordCall = args => RocketChat.loginWithPassword(args); +const loginCall = args => RocketChat.login(args); const logoutCall = args => RocketChat.logout(args); -const forgotPasswordCall = args => RocketChat.forgotPassword(args); -const handleLoginSuccess = function* handleLoginSuccess() { +const handleLoginRequest = function* handleLoginRequest({ credentials }) { try { - const user = yield select(getUser); - const adding = yield select(state => state.server.adding); - yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token); - - if (!user.username) { - return yield put(appStart('setUsername')); - } - - if (adding) { - yield put(serverFinishAdd()); - yield Navigation.dismissAllModals(); + let result; + if (credentials.resume) { + result = yield call(loginCall, credentials); } else { - yield put(appStart('inside')); + result = yield call(loginWithPasswordCall, credentials); } - } catch (e) { - log('handleLoginSuccess', e); + if (result.status === 'success') { + const { data } = result; + const user = { + id: data.userId, + token: data.authToken, + username: data.me.username, + name: data.me.name, + language: data.me.language, + status: data.me.status + }; + return yield put(loginSuccess(user)); + } + } catch (error) { + yield put(loginFailure(error)); } }; -const handleRegisterSubmit = function* handleRegisterSubmit({ credentials }) { - yield put(registerRequest(credentials)); -}; +const handleLoginSuccess = function* handleLoginSuccess({ user }) { + const adding = yield select(state => state.server.adding); + yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token); -const handleRegisterRequest = function* handleRegisterRequest({ credentials }) { + const server = yield select(getServer); try { - yield call(registerCall, { credentials }); - yield call(loginCall, { - username: credentials.email, - password: credentials.pass - }); - } catch (err) { - yield put(loginFailure(err)); + RocketChat.loginSuccess({ user }); + I18n.locale = user.language; + yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user)); + } catch (error) { + console.log('loginSuccess saga -> error', error); } -}; -const handleSetUsernameSubmit = function* handleSetUsernameSubmit({ credentials }) { - yield put(setUsernameRequest(credentials)); -}; - -const handleSetUsernameRequest = function* handleSetUsernameRequest({ credentials }) { - try { - yield call(setUsernameCall, credentials); - yield put(setUsernameSuccess()); - yield call(loginSuccessCall); - } catch (err) { - yield put(loginFailure(err)); + if (!user.username) { + RocketChat.loginSuccess({ user }); + yield put(appStart('setUsername')); + } else if (adding) { + yield put(serverFinishAdd()); + yield Navigation.dismissAllModals(); + } else { + yield put(appStart('inside')); } }; @@ -85,42 +70,24 @@ const handleLogout = function* handleLogout() { const server = yield select(getServer); if (server) { try { - yield put(appStart('outside')); yield call(logoutCall, { server }); + yield put(appStart('outside')); } catch (e) { log('handleLogout', e); } } }; -const handleForgotPasswordRequest = function* handleForgotPasswordRequest({ email }) { - try { - yield call(forgotPasswordCall, email); - yield put(forgotPasswordSuccess()); - } catch (err) { - yield put(forgotPasswordFailure(err)); - } -}; - -const handleSetUser = function* handleSetUser() { - yield delay(2000); - const [server, user] = yield all([select(getServer), select(getUser)]); - if (user && user.id) { - if (user.language) { - I18n.locale = user.language; - } - yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user)); +const handleSetUser = function handleSetUser({ user }) { + if (user && user.language) { + I18n.locale = user.language; } }; const root = function* root() { + yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest); yield takeLatest(types.LOGIN.SUCCESS, handleLoginSuccess); - yield takeLatest(types.LOGIN.REGISTER_REQUEST, handleRegisterRequest); - yield takeLatest(types.LOGIN.REGISTER_SUBMIT, handleRegisterSubmit); - yield takeLatest(types.LOGIN.SET_USERNAME_SUBMIT, handleSetUsernameSubmit); - yield takeLatest(types.LOGIN.SET_USERNAME_REQUEST, handleSetUsernameRequest); yield takeLatest(types.LOGOUT, handleLogout); - yield takeLatest(types.FORGOT_PASSWORD.REQUEST, handleForgotPasswordRequest); yield takeLatest(types.USER.SET, handleSetUser); }; export default root; diff --git a/app/sagas/messages.js b/app/sagas/messages.js index 01bda80c..1cdafc66 100644 --- a/app/sagas/messages.js +++ b/app/sagas/messages.js @@ -34,8 +34,7 @@ const get = function* get({ room }) { } yield put(messagesSuccess()); } catch (err) { - console.warn('messagesFailure', err); - yield put(messagesFailure(err.status)); + yield put(messagesFailure(err)); } }; diff --git a/app/sagas/rooms.js b/app/sagas/rooms.js index 7032b408..1296fb3b 100644 --- a/app/sagas/rooms.js +++ b/app/sagas/rooms.js @@ -3,37 +3,21 @@ import { put, call, takeLatest, take, select, race, fork, cancel, takeEvery } from 'redux-saga/effects'; import { delay } from 'redux-saga'; -import { BACKGROUND } from 'redux-enhancer-react-native-appstate'; import { Navigation } from 'react-native-navigation'; import * as types from '../actions/actionsTypes'; -// import { roomsSuccess, roomsFailure } from '../actions/rooms'; -import { addUserTyping, removeUserTyping, setLastOpen } from '../actions/room'; +import { addUserTyping, removeUserTyping } from '../actions/room'; import { messagesRequest, editCancel, replyCancel } from '../actions/messages'; import RocketChat from '../lib/rocketchat'; import database from '../lib/realm'; import log from '../utils/log'; import I18n from '../i18n'; -const leaveRoom = rid => RocketChat.leaveRoom(rid); const eraseRoom = rid => RocketChat.eraseRoom(rid); let sub; let thread; -// const getRooms = function* getRooms() { -// return yield RocketChat.getRooms(); -// }; - -// const watchRoomsRequest = function* watchRoomsRequest() { -// try { -// yield call(getRooms); -// yield put(roomsSuccess()); -// } catch (err) { -// yield put(roomsFailure(err.status)); -// } -// }; - const cancelTyping = function* cancelTyping(username) { while (true) { const { typing, timeout } = yield race({ @@ -89,7 +73,13 @@ const watchRoomOpen = function* watchRoomOpen({ room }) { if (room._id) { RocketChat.readMessages(room.rid); } + + const auth = yield select(state => state.login.isAuthenticated); + if (!auth) { + yield take(types.LOGIN.SUCCESS); + } sub = yield RocketChat.subscribeRoom(room); + thread = yield fork(usersTyping, { rid: room.rid }); yield race({ open: take(types.ROOM.OPEN), @@ -129,20 +119,8 @@ const watchuserTyping = function* watchuserTyping({ status }) { } }; -// const updateRoom = function* updateRoom() { -// const room = yield select(state => state.room); -// if (!room || !room.rid) { -// return; -// } -// yield put(messagesRequest({ rid: room.rid })); -// }; - -const updateLastOpen = function* updateLastOpen() { - yield put(setLastOpen()); -}; - -const goRoomsListAndDelete = function* goRoomsListAndDelete(rid, type) { - yield Navigation.popToRoot(type === 'erase' ? 'RoomActionsView' : 'RoomInfoEditView'); +const goRoomsListAndDelete = function* goRoomsListAndDelete(rid) { + yield Navigation.popToRoot('RoomsListView'); try { database.write(() => { const messages = database.objects('messages').filtered('rid = $0', rid); @@ -155,16 +133,16 @@ const goRoomsListAndDelete = function* goRoomsListAndDelete(rid, type) { } }; -const handleLeaveRoom = function* handleLeaveRoom({ rid }) { +const handleLeaveRoom = function* handleLeaveRoom({ rid, t }) { try { sub.stop(); - yield call(leaveRoom, rid); - yield goRoomsListAndDelete(rid, 'delete'); + yield RocketChat.leaveRoom(rid, t); + yield goRoomsListAndDelete(rid); } catch (e) { - if (e.error === 'error-you-are-last-owner') { - Alert.alert(I18n.t(e.error)); + if (e.data && e.data.errorType === 'error-you-are-last-owner') { + Alert.alert(I18n.t('Oops'), I18n.t(e.data.errorType)); } else { - Alert.alert(I18n.t('There_was_an_error_while_action', { action: I18n.t('leaving_room') })); + Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('leaving_room') })); } } }; @@ -172,10 +150,10 @@ const handleLeaveRoom = function* handleLeaveRoom({ rid }) { const handleEraseRoom = function* handleEraseRoom({ rid }) { try { sub.stop(); - yield call(eraseRoom, rid); + yield eraseRoom(rid); yield goRoomsListAndDelete(rid, 'erase'); } catch (e) { - Alert.alert(I18n.t('There_was_an_error_while_action', { action: I18n.t('erasing_room') })); + Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('erasing_room') })); } }; @@ -183,9 +161,6 @@ const root = function* root() { yield takeLatest(types.ROOM.USER_TYPING, watchuserTyping); yield takeLatest(types.ROOM.OPEN, watchRoomOpen); yield takeEvery(types.ROOM.MESSAGE_RECEIVED, handleMessageReceived); - // yield takeLatest(FOREGROUND, updateRoom); - // yield takeLatest(FOREGROUND, watchRoomsRequest); - yield takeLatest(BACKGROUND, updateLastOpen); yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom); yield takeLatest(types.ROOM.ERASE, handleEraseRoom); }; diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index 82f8d94c..f3e9fd1d 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -1,4 +1,4 @@ -import { put, call, takeLatest } from 'redux-saga/effects'; +import { put, takeLatest } from 'redux-saga/effects'; import { AsyncStorage } from 'react-native'; import { Navigation } from 'react-native-navigation'; import { Provider } from 'react-redux'; @@ -6,9 +6,9 @@ import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; import { SERVER } from '../actions/actionsTypes'; import * as actions from '../actions'; -import { connectRequest } from '../actions/connect'; import { serverFailure, selectServerRequest, selectServerSuccess } from '../actions/server'; import { setRoles } from '../actions/roles'; +import { setUser } from '../actions/login'; import RocketChat from '../lib/rocketchat'; import database from '../lib/realm'; import log from '../utils/log'; @@ -19,16 +19,20 @@ let LoginView = null; const handleSelectServer = function* handleSelectServer({ server }) { try { - yield database.setActiveDB(server); - yield put(connectRequest()); - yield call([AsyncStorage, 'setItem'], 'currentServer', server); - const token = yield AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`); - if (token) { + yield AsyncStorage.setItem('currentServer', server); + const userStringified = yield AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`); + + if (userStringified) { + const user = JSON.parse(userStringified); + yield put(setUser(user)); yield put(actions.appStart('inside')); + RocketChat.connect({ server, user }); + } else { + RocketChat.connect({ server }); } const settings = database.objects('settings'); - yield put(actions.setAllSettings(RocketChat.parseSettings(RocketChat._filterSettings(settings.slice(0, settings.length))))); + yield put(actions.setAllSettings(RocketChat.parseSettings(settings.slice(0, settings.length)))); const emojis = database.objects('customEmojis'); yield put(actions.setCustomEmojis(RocketChat.parseEmojis(emojis.slice(0, emojis.length)))); const roles = database.objects('roles'); diff --git a/app/sagas/state.js b/app/sagas/state.js index 20dc38d9..fa6adc59 100644 --- a/app/sagas/state.js +++ b/app/sagas/state.js @@ -1,8 +1,7 @@ import { takeLatest, select } from 'redux-saga/effects'; -import { FOREGROUND, BACKGROUND, INACTIVE } from 'redux-enhancer-react-native-appstate'; +import { FOREGROUND, BACKGROUND } from 'redux-enhancer-react-native-appstate'; import RocketChat from '../lib/rocketchat'; -import log from '../utils/log'; import { setBadgeCount } from '../push'; const appHasComeBackToForeground = function* appHasComeBackToForeground() { @@ -18,7 +17,7 @@ const appHasComeBackToForeground = function* appHasComeBackToForeground() { setBadgeCount(); return yield RocketChat.setUserPresenceOnline(); } catch (e) { - log('appHasComeBackToForeground', e); + console.log('appHasComeBackToForeground', e); } }; @@ -34,7 +33,7 @@ const appHasComeBackToBackground = function* appHasComeBackToBackground() { try { return yield RocketChat.setUserPresenceAway(); } catch (e) { - log('appHasComeBackToBackground', e); + console.log('appHasComeBackToBackground', e); } }; @@ -47,10 +46,10 @@ const root = function* root() { BACKGROUND, appHasComeBackToBackground ); - yield takeLatest( - INACTIVE, - appHasComeBackToBackground - ); + // yield takeLatest( + // INACTIVE, + // appHasComeBackToBackground + // ); }; export default root; diff --git a/app/utils/isValidEmail.js b/app/utils/isValidEmail.js new file mode 100644 index 00000000..58cc9077 --- /dev/null +++ b/app/utils/isValidEmail.js @@ -0,0 +1,5 @@ +export default function isValidEmail(email) { + /* eslint-disable no-useless-escape */ + const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return reg.test(email); +} diff --git a/app/utils/log.js b/app/utils/log.js index e1c98aef..d59efad0 100644 --- a/app/utils/log.js +++ b/app/utils/log.js @@ -4,7 +4,7 @@ export default (event, error) => { if (typeof error !== 'object') { error = { error }; } - Answers.logCustom(event, error); + Answers.logCustom(event); if (__DEV__) { console.warn(event, error); } diff --git a/app/views/ForgotPasswordView.js b/app/views/ForgotPasswordView.js index fc5e0178..c099539a 100644 --- a/app/views/ForgotPasswordView.js +++ b/app/views/ForgotPasswordView.js @@ -1,26 +1,21 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Text, ScrollView } from 'react-native'; -import { connect } from 'react-redux'; import { Navigation } from 'react-native-navigation'; import SafeAreaView from 'react-native-safe-area-view'; import LoggedView from './View'; -import { forgotPasswordRequest as forgotPasswordRequestAction } from '../actions/login'; import KeyboardView from '../presentation/KeyboardView'; import TextInput from '../containers/TextInput'; import Button from '../containers/Button'; import sharedStyles from './Styles'; import { showErrorAlert } from '../utils/info'; +import isValidEmail from '../utils/isValidEmail'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import I18n from '../i18n'; import { DARK_HEADER } from '../constants/headerOptions'; +import RocketChat from '../lib/rocketchat'; -@connect(state => ({ - login: state.login -}), dispatch => ({ - forgotPasswordRequest: email => dispatch(forgotPasswordRequestAction(email)) -})) /** @extends React.Component */ export default class ForgotPasswordView extends LoggedView { static options() { @@ -30,9 +25,7 @@ export default class ForgotPasswordView extends LoggedView { } static propTypes = { - componentId: PropTypes.string, - forgotPasswordRequest: PropTypes.func.isRequired, - login: PropTypes.object + componentId: PropTypes.string } constructor(props) { @@ -40,7 +33,8 @@ export default class ForgotPasswordView extends LoggedView { this.state = { email: '', - invalidEmail: true + invalidEmail: true, + isFetching: false }; } @@ -50,16 +44,6 @@ export default class ForgotPasswordView extends LoggedView { }, 600); } - componentDidUpdate() { - const { login, componentId } = this.props; - if (login.success) { - Navigation.pop(componentId); - setTimeout(() => { - showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert')); - }); - } - } - componentWillUnmount() { if (this.timeout) { clearTimeout(this.timeout); @@ -67,27 +51,35 @@ export default class ForgotPasswordView extends LoggedView { } validate = (email) => { - /* eslint-disable no-useless-escape */ - const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - if (!reg.test(email)) { + if (!isValidEmail(email)) { this.setState({ invalidEmail: true }); return; } this.setState({ email, invalidEmail: false }); } - resetPassword = () => { + resetPassword = async() => { const { email, invalidEmail } = this.state; - const { forgotPasswordRequest } = this.props; if (invalidEmail || !email) { return; } - forgotPasswordRequest(email); + try { + this.setState({ isFetching: true }); + const result = await RocketChat.forgotPassword(email); + if (result.success) { + const { componentId } = this.props; + Navigation.pop(componentId); + showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert')); + } + } catch (e) { + const msg = (e.data && e.data.error) || I18n.t('There_was_an_error_while_action', I18n.t('resetting_password')); + showErrorAlert(msg, I18n.t('Alert')); + } + this.setState({ isFetching: false }); } render() { - const { invalidEmail } = this.state; - const { login } = this.props; + const { invalidEmail, isFetching } = this.state; return ( diff --git a/app/views/LoginSignupView.js b/app/views/LoginSignupView.js index 92f1dfc1..084f9cc3 100644 --- a/app/views/LoginSignupView.js +++ b/app/views/LoginSignupView.js @@ -95,8 +95,6 @@ const SERVICES_COLLAPSED_HEIGHT = 174; @connect(state => ({ server: state.server.server, isFetching: state.login.isFetching, - Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder, - Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder, Site_Name: state.settings.Site_Name, services: state.login.services })) @@ -120,16 +118,8 @@ export default class LoginSignupView extends LoggedView { componentId: PropTypes.string, isFetching: PropTypes.bool, server: PropTypes.string, - Accounts_EmailOrUsernamePlaceholder: PropTypes.bool, - Accounts_PasswordPlaceholder: PropTypes.string, - Accounts_OAuth_Facebook: PropTypes.bool, - Accounts_OAuth_Github: PropTypes.bool, - Accounts_OAuth_Gitlab: PropTypes.bool, - Accounts_OAuth_Google: PropTypes.bool, - Accounts_OAuth_Linkedin: PropTypes.bool, - Accounts_OAuth_Meteor: PropTypes.bool, - Accounts_OAuth_Twitter: PropTypes.bool, - services: PropTypes.object + services: PropTypes.object, + Site_Name: PropTypes.string } constructor(props) { diff --git a/app/views/LoginView.js b/app/views/LoginView.js index 013e9571..1f6947f0 100644 --- a/app/views/LoginView.js +++ b/app/views/LoginView.js @@ -8,8 +8,8 @@ import { Navigation } from 'react-native-navigation'; import { Answers } from 'react-native-fabric'; import SafeAreaView from 'react-native-safe-area-view'; import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; +import equal from 'deep-equal'; -import RocketChat from '../lib/rocketchat'; import KeyboardView from '../presentation/KeyboardView'; import TextInput from '../containers/TextInput'; import Button from '../containers/Button'; @@ -19,6 +19,7 @@ import LoggedView from './View'; import I18n from '../i18n'; import store from '../lib/createStore'; import { DARK_HEADER } from '../constants/headerOptions'; +import { loginRequest as loginRequestAction } from '../actions/login'; let RegisterView = null; let ForgotPasswordView = null; @@ -52,11 +53,13 @@ const styles = StyleSheet.create({ @connect(state => ({ isFetching: state.login.isFetching, + failure: state.login.failure, + error: state.login.error, Site_Name: state.settings.Site_Name, Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder -}), () => ({ - loginSubmit: params => RocketChat.loginWithPassword(params) +}), dispatch => ({ + loginRequest: params => dispatch(loginRequestAction(params)) })) /** @extends React.Component */ export default class LoginView extends LoggedView { @@ -76,18 +79,19 @@ export default class LoginView extends LoggedView { static propTypes = { componentId: PropTypes.string, - loginSubmit: PropTypes.func.isRequired, - login: PropTypes.object, + loginRequest: PropTypes.func.isRequired, + error: PropTypes.object, Site_Name: PropTypes.string, Accounts_EmailOrUsernamePlaceholder: PropTypes.string, Accounts_PasswordPlaceholder: PropTypes.string, - isFetching: PropTypes.bool + isFetching: PropTypes.bool, + failure: PropTypes.bool } constructor(props) { super('LoginView', props); this.state = { - username: '', + user: '', password: '', code: '', showTOTP: false @@ -103,10 +107,22 @@ export default class LoginView extends LoggedView { }, 600); } - componentDidUpdate(prevProps) { - const { componentId, Site_Name } = this.props; - if (Site_Name && prevProps.Site_Name !== Site_Name) { - this.setTitle(componentId, Site_Name); + componentWillReceiveProps(nextProps) { + const { componentId, Site_Name, error } = this.props; + if (Site_Name && nextProps.Site_Name !== Site_Name) { + this.setTitle(componentId, nextProps.Site_Name); + } else if (nextProps.failure && !equal(error, nextProps.error)) { + if (nextProps.error && nextProps.error.error === 'totp-required') { + LayoutAnimation.easeInEaseOut(); + this.setState({ showTOTP: true }); + setTimeout(() => { + if (this.codeInput && this.codeInput.focus) { + this.codeInput.focus(); + } + }, 300); + return; + } + Alert.alert(I18n.t('Oops'), I18n.t('Login_error')); } } @@ -147,12 +163,12 @@ export default class LoginView extends LoggedView { valid = () => { const { - username, password, code, showTOTP + user, password, code, showTOTP } = this.state; if (showTOTP) { return code.trim(); } - return username.trim() && password.trim(); + return user.trim() && password.trim(); } submit = async() => { @@ -160,12 +176,12 @@ export default class LoginView extends LoggedView { return; } - const { username, password, code } = this.state; - const { loginSubmit } = this.props; + const { user, password, code } = this.state; + const { loginRequest } = this.props; Keyboard.dismiss(); try { - await loginSubmit({ username, password, code }); + await loginRequest({ user, password, code }); Answers.logLogin('Email', true); } catch (e) { if (e && e.error === 'totp-required') { @@ -265,7 +281,7 @@ export default class LoginView extends LoggedView { keyboardType='email-address' returnKeyType='next' iconLeft='mention' - onChangeText={value => this.setState({ username: value })} + onChangeText={value => this.setState({ user: value })} onSubmitEditing={() => { this.passwordInput.focus(); }} testID='login-view-email' /> diff --git a/app/views/OAuthView.js b/app/views/OAuthView.js index 16497f05..c5af7cd1 100644 --- a/app/views/OAuthView.js +++ b/app/views/OAuthView.js @@ -37,6 +37,9 @@ export default class OAuthView extends React.PureComponent { constructor(props) { super(props); + this.state = { + logging: false + }; this.redirectRegex = new RegExp(`(?=.*(${ props.server }))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g'); Navigation.events().bindComponent(this); } @@ -53,11 +56,20 @@ export default class OAuthView extends React.PureComponent { } login = async(params) => { + const { logging } = this.state; + if (logging) { + return; + } + + this.setState({ logging: true }); + try { - await RocketChat.login(params); + await RocketChat.loginOAuth(params); } catch (e) { console.warn(e); } + this.setState({ logging: false }); + this.dismiss(); } render() { @@ -72,7 +84,6 @@ export default class OAuthView extends React.PureComponent { const parts = url.split('#'); const credentials = JSON.parse(parts[1]); this.login({ oauth: { ...credentials } }); - this.dismiss(); } }} /> diff --git a/app/views/RegisterView.js b/app/views/RegisterView.js index 1c623cf0..debd84e9 100644 --- a/app/views/RegisterView.js +++ b/app/views/RegisterView.js @@ -7,9 +7,7 @@ import { connect, Provider } from 'react-redux'; import { Navigation } from 'react-native-navigation'; import SafeAreaView from 'react-native-safe-area-view'; import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; -import equal from 'deep-equal'; -import { registerSubmit as registerSubmitAction } from '../actions/login'; import TextInput from '../containers/TextInput'; import Button from '../containers/Button'; import KeyboardView from '../presentation/KeyboardView'; @@ -19,16 +17,16 @@ import LoggedView from './View'; import I18n from '../i18n'; import store from '../lib/createStore'; import { DARK_HEADER } from '../constants/headerOptions'; +import RocketChat from '../lib/rocketchat'; +import { loginRequest as loginRequestAction } from '../actions/login'; +import isValidEmail from '../utils/isValidEmail'; let TermsServiceView = null; let PrivacyPolicyView = null; let LegalView = null; -@connect(state => ({ - server: state.server.server, - login: state.login -}), dispatch => ({ - registerSubmit: params => dispatch(registerSubmitAction(params)) +@connect(null, dispatch => ({ + loginRequest: params => dispatch(loginRequestAction(params)) })) /** @extends React.Component */ export default class RegisterView extends LoggedView { @@ -48,13 +46,7 @@ export default class RegisterView extends LoggedView { static propTypes = { componentId: PropTypes.string, - server: PropTypes.string, - registerSubmit: PropTypes.func.isRequired, - Accounts_UsernamePlaceholder: PropTypes.string, - Accounts_NamePlaceholder: PropTypes.string, - Accounts_EmailOrUsernamePlaceholder: PropTypes.string, - Accounts_PasswordPlaceholder: PropTypes.string, - login: PropTypes.object + loginRequest: PropTypes.func } constructor(props) { @@ -63,7 +55,8 @@ export default class RegisterView extends LoggedView { name: '', email: '', password: '', - username: '' + username: '', + saving: false }; Navigation.events().bindComponent(this); } @@ -75,10 +68,8 @@ export default class RegisterView extends LoggedView { } componentDidUpdate(prevProps) { - const { login, componentId, Site_Name } = this.props; - if (login && login.failure && login.error && !equal(login.error, prevProps.login.error)) { - Alert.alert(I18n.t('Oops'), login.error.reason); - } else if (Site_Name && prevProps.Site_Name !== Site_Name) { + const { componentId, Site_Name } = this.props; + if (Site_Name && prevProps.Site_Name !== Site_Name) { this.setTitle(componentId, Site_Name); } } @@ -122,26 +113,30 @@ export default class RegisterView extends LoggedView { const { name, email, password, username } = this.state; - return name.trim() && email.trim() && password.trim() && username.trim(); + return name.trim() && email.trim() && password.trim() && username.trim() && isValidEmail(email); } - invalidEmail = () => { - const { login } = this.props; - return login.failure && /Email/.test(login.error && login.error.reason) ? login.error : {}; - } - - submit = () => { + submit = async() => { if (!this.valid()) { return; } + this.setState({ saving: true }); + Keyboard.dismiss(); + const { name, email, password, username } = this.state; - const { registerSubmit } = this.props; - registerSubmit({ - name, email, pass: password, username - }); - Keyboard.dismiss(); + const { loginRequest } = this.props; + + try { + await RocketChat.register({ + name, email, pass: password, username + }); + await loginRequest({ user: email, password }); + } catch (e) { + Alert.alert(I18n.t('Oops'), e.data.error); + } + this.setState({ saving: false }); } termsService = () => { @@ -187,7 +182,7 @@ export default class RegisterView extends LoggedView { } render() { - const { login } = this.props; + const { saving } = this.state; return ( @@ -219,7 +214,6 @@ export default class RegisterView extends LoggedView { iconLeft='mail' onChangeText={email => this.setState({ email })} onSubmitEditing={() => { this.passwordInput.focus(); }} - error={this.invalidEmail()} testID='register-view-email' /> diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 7a059d54..329a9189 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -35,7 +35,7 @@ const modules = {}; username: state.login.user && state.login.user.username, baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' }), dispatch => ({ - leaveRoom: rid => dispatch(leaveRoomAction(rid)) + leaveRoom: (rid, t) => dispatch(leaveRoomAction(rid, t)) })) /** @extends React.Component */ export default class RoomActionsView extends LoggedView { @@ -67,16 +67,26 @@ export default class RoomActionsView extends LoggedView { this.rooms = database.objects('subscriptions').filtered('rid = $0', rid); this.state = { room: this.rooms[0] || {}, - onlineMembers: [], - allMembers: [], + membersCount: 0, member: {} }; } async componentDidMount() { + const { room } = this.state; + if (room && room.t !== 'd' && this.canViewMembers) { + const { rid } = this.props; + try { + const counters = await RocketChat.getRoomCounters(rid, room.t); + if (counters.success) { + this.setState({ membersCount: counters.members, joined: counters.joined }); + } + } catch (error) { + console.log('RoomActionsView -> getRoomCounters -> error', error); + } + } + this.rooms.addListener(this.updateRoom); - const [members, member] = await Promise.all([this.updateRoomMembers(), this.updateRoomMember()]); - this.setState({ ...members, ...member }); } componentWillUnmount() { @@ -96,10 +106,6 @@ export default class RoomActionsView extends LoggedView { name: item.route, passProps: item.params } - // screen: item.route, - // title: item.name, - // passProps: item.params, - // backButtonTitle: '' }); } if (item.event) { @@ -108,12 +114,10 @@ export default class RoomActionsView extends LoggedView { } get canAddUser() { - const { allMembers, room } = this.state; - const { username } = this.props; + const { room, joined } = this.state; const { rid, t } = room; - // TODO: same test joined - const userInRoom = !!allMembers.find(m => m.username === username); + const userInRoom = joined; const permissions = RocketChat.hasPermission(['add-user-to-joined-room', 'add-user-to-any-c-room', 'add-user-to-any-p-room'], rid); if (userInRoom && permissions['add-user-to-joined-room']) { @@ -138,11 +142,16 @@ export default class RoomActionsView extends LoggedView { return false; } } - return (t === 'c' || t === 'p'); + + // This method is executed only in componentDidMount and returns a value + // We save the state to read in render + const result = (t === 'c' || t === 'p'); + this.setState({ canViewMembers: result }); + return result; } get sections() { - const { onlineMembers, room } = this.state; + const { room, membersCount, canViewMembers } = this.state; const { rid, t, blocker, notifications } = room; @@ -255,15 +264,13 @@ export default class RoomActionsView extends LoggedView { } else if (t === 'c' || t === 'p') { const actions = []; - if (this.canViewMembers) { + if (canViewMembers) { actions.push({ icon: 'ios-people', name: I18n.t('Members'), - description: (onlineMembers.length === 1 - ? I18n.t('1_online_member') - : I18n.t('N_online_members', { n: onlineMembers.length })), + description: `${ membersCount } ${ I18n.t('members') }`, route: 'RoomMembersView', - params: { rid, members: onlineMembers }, + params: { rid }, testID: 'room-actions-members', require: () => require('../RoomMembersView').default }); @@ -299,47 +306,6 @@ export default class RoomActionsView extends LoggedView { return sections; } - updateRoomMembers = async() => { - const { room } = this.state; - const { rid, t } = room; - - if (!this.canViewMembers) { - return {}; - } - - if (t === 'c' || t === 'p') { - let onlineMembers = []; - let allMembers = []; - try { - const onlineMembersCall = RocketChat.getRoomMembers(rid, false); - const allMembersCall = RocketChat.getRoomMembers(rid, true); - const [onlineMembersResult, allMembersResult] = await Promise.all([onlineMembersCall, allMembersCall]); - onlineMembers = onlineMembersResult.records; - allMembers = allMembersResult.records; - return { onlineMembers, allMembers }; - } catch (error) { - return {}; - } - } - } - - updateRoomMember = async() => { - const { room } = this.state; - const { rid, t } = room; - const { userId } = this.props; - - if (t !== 'd') { - return {}; - } - try { - const member = await RocketChat.getRoomMember(rid, userId); - return { member }; - } catch (e) { - log('RoomActions updateRoomMember', e); - return {}; - } - } - updateRoom = () => { this.setState({ room: this.rooms[0] || {} }); } @@ -370,7 +336,7 @@ export default class RoomActionsView extends LoggedView { { text: I18n.t('Yes_action_it', { action: I18n.t('leave') }), style: 'destructive', - onPress: () => leaveRoom(room.rid) + onPress: () => leaveRoom(room.rid, room.t) } ] ); @@ -379,7 +345,10 @@ export default class RoomActionsView extends LoggedView { toggleNotifications = () => { const { room } = this.state; try { - RocketChat.saveNotificationSettings(room.rid, 'mobilePushNotifications', room.notifications ? 'default' : 'nothing'); + const notifications = { + mobilePushNotifications: room.notifications ? 'default' : 'nothing' + }; + RocketChat.saveNotificationSettings(room.rid, notifications); } catch (e) { log('toggleNotifications', e); } diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.js index 66a0fae0..00d41e96 100644 --- a/app/views/RoomInfoView/index.js +++ b/app/views/RoomInfoView/index.js @@ -154,7 +154,7 @@ export default class RoomInfoView extends LoggedView { if (room.t === 'd') { try { const roomUser = await RocketChat.getRoomMember(room.rid, userId); - this.setState({ roomUser }); + this.setState({ roomUser: roomUser || {} }); const username = room.name; const activeUser = activeUsers[roomUser._id]; diff --git a/app/views/RoomMembersView/index.js b/app/views/RoomMembersView/index.js index e9b42173..744be654 100644 --- a/app/views/RoomMembersView/index.js +++ b/app/views/RoomMembersView/index.js @@ -67,12 +67,13 @@ export default class RoomMembersView extends LoggedView { members, membersFiltered: [], userLongPressed: {}, - room: {} + room: this.rooms[0] || {} }; Navigation.events().bindComponent(this); } componentDidMount() { + this.fetchMembers(); this.rooms.addListener(this.updateRoom); } @@ -91,7 +92,8 @@ export default class RoomMembersView extends LoggedView { rightButtons: [{ id: 'toggleOnline', text: allUsers ? I18n.t('Online') : I18n.t('All'), - testID: 'room-members-view-toggle-status' + testID: 'room-members-view-toggle-status', + color: Platform.OS === 'android' ? '#FFF' : undefined }] } }); @@ -121,8 +123,10 @@ export default class RoomMembersView extends LoggedView { if (subscriptions.length) { this.goRoom({ rid: subscriptions[0].rid }); } else { - const room = await RocketChat.createDirectMessage(item.username); - this.goRoom({ rid: room.rid }); + const result = await RocketChat.createDirectMessage(item.username); + if (result.success) { + this.goRoom({ rid: result.room._id }); + } } } catch (e) { log('onPressUser', e); @@ -151,6 +155,13 @@ export default class RoomMembersView extends LoggedView { } } + fetchMembers = async(status) => { + const { rid } = this.state; + const membersResult = await RocketChat.getRoomMembers(rid, status); + const members = membersResult.records; + this.setState({ allUsers: status, members }); + } + updateRoom = async() => { const [room] = this.rooms; await this.setState({ room }); diff --git a/app/views/RoomView/ListView.js b/app/views/RoomView/ListView.js index a30fc01a..6f11d236 100644 --- a/app/views/RoomView/ListView.js +++ b/app/views/RoomView/ListView.js @@ -32,7 +32,8 @@ export class List extends React.Component { renderFooter: PropTypes.func, renderRow: PropTypes.func, room: PropTypes.string, - end: PropTypes.bool + end: PropTypes.bool, + loadingMore: PropTypes.bool }; constructor(props) { @@ -49,8 +50,8 @@ export class List extends React.Component { } shouldComponentUpdate(nextProps) { - const { end } = this.props; - return end !== nextProps.end; + const { end, loadingMore } = this.props; + return end !== nextProps.end || loadingMore !== nextProps.loadingMore; } componentWillUnmount() { diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index f893df2a..748a6d4d 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -23,7 +23,6 @@ import UploadProgress from './UploadProgress'; import styles from './styles'; import log from '../../utils/log'; import I18n from '../../i18n'; -import debounce from '../../utils/debounce'; import { iconsMap } from '../../Icons'; import store from '../../lib/createStore'; import ConnectionBadge from '../../containers/ConnectionBadge'; @@ -39,7 +38,8 @@ let RoomActionsView = null; }, actionMessage: state.messages.actionMessage, showActions: state.messages.showActions, - showErrorActions: state.messages.showErrorActions + showErrorActions: state.messages.showErrorActions, + appState: state.app.ready && state.app.foreground ? 'foreground' : 'background' }), dispatch => ({ openRoom: room => dispatch(openRoomAction(room)), setLastOpen: date => dispatch(setLastOpenAction(date)), @@ -87,6 +87,7 @@ export default class RoomView extends LoggedView { showActions: PropTypes.bool, showErrorActions: PropTypes.bool, actionMessage: PropTypes.object, + appState: PropTypes.string, toggleReactionPicker: PropTypes.func.isRequired, actionsShow: PropTypes.func, closeRoom: PropTypes.func @@ -100,23 +101,33 @@ export default class RoomView extends LoggedView { loaded: false, joined: this.rooms.length > 0, room: {}, - end: false + end: false, + loadingMore: false }; this.onReactionPress = this.onReactionPress.bind(this); Navigation.events().bindComponent(this); } - componentDidMount() { - this.updateRoom(); + async componentDidMount() { + if (this.rooms.length === 0 && this.rid) { + const result = await RocketChat.getRoomInfo(this.rid); + if (result.success) { + const { room } = result; + this.setState( + { room: { rid: room._id, t: room.t, name: room.name } }, + () => this.updateRoom() + ); + } + } this.rooms.addListener(this.updateRoom); this.internalSetState({ loaded: true }); } shouldComponentUpdate(nextProps, nextState) { const { - room, loaded, joined, end + room, loaded, joined, end, loadingMore } = this.state; - const { showActions, showErrorActions } = this.props; + const { showActions, showErrorActions, appState } = this.props; if (room.ro !== nextState.room.ro) { return true; @@ -128,17 +139,21 @@ export default class RoomView extends LoggedView { return true; } else if (end !== nextState.end) { return true; + } else if (loadingMore !== nextState.loadingMore) { + return true; } else if (showActions !== nextProps.showActions) { return true; } else if (showErrorActions !== nextProps.showErrorActions) { return true; + } else if (appState !== nextProps.appState) { + return true; } return false; } componentDidUpdate(prevProps, prevState) { const { room } = this.state; - const { componentId } = this.props; + const { componentId, appState } = this.props; if (prevState.room.f !== room.f) { Navigation.mergeOptions(componentId, { @@ -154,32 +169,41 @@ export default class RoomView extends LoggedView { }] } }); + } else if (appState === 'foreground' && appState !== prevProps.appState) { + RocketChat.loadMissedMessages(room).catch(e => console.log(e)); + RocketChat.readMessages(room.rid).catch(e => console.log(e)); } } componentWillUnmount() { const { closeRoom } = this.props; this.rooms.removeAllListeners(); - this.onEndReached.stop(); + if (this.onEndReached && this.onEndReached.stop) { + this.onEndReached.stop(); + } closeRoom(); } - onEndReached = debounce((lastRowData) => { + onEndReached = async(lastRowData) => { if (!lastRowData) { - this.internalSetState({ end: true }); return; } - requestAnimationFrame(async() => { - const { room } = this.state; - try { - const result = await RocketChat.loadMessagesForRoom({ rid: this.rid, t: room.t, latest: lastRowData.ts }); - this.internalSetState({ end: result < 50 }); - } catch (e) { - log('RoomView.onEndReached', e); - } - }); - }) + const { loadingMore, end } = this.state; + if (loadingMore || end) { + return; + } + + this.setState({ loadingMore: true }); + const { room } = this.state; + try { + const result = await RocketChat.loadMessagesForRoom({ rid: this.rid, t: room.t, latest: lastRowData.ts }); + this.internalSetState({ end: result.length < 50, loadingMore: false }); + } catch (e) { + this.internalSetState({ loadingMore: false }); + log('RoomView.onEndReached', e); + } + } onMessageLongPress = (message) => { const { actionsShow } = this.props; @@ -228,7 +252,7 @@ export default class RoomView extends LoggedView { }); } else if (buttonId === 'star') { try { - RocketChat.toggleFavorite(rid, f); + RocketChat.toggleFavorite(rid, !f); } catch (e) { log('toggleFavorite', e); } @@ -254,7 +278,8 @@ export default class RoomView extends LoggedView { } } } else { - openRoom({ rid: this.rid }); + const { room } = this.state; + openRoom(room); this.internalSetState({ joined: false }); } } @@ -270,10 +295,12 @@ export default class RoomView extends LoggedView { joinRoom = async() => { const { rid } = this.props; try { - await RocketChat.joinRoom(rid); - this.internalSetState({ - joined: true - }); + const result = await RocketChat.joinRoom(rid); + if (result.success) { + this.internalSetState({ + joined: true + }); + } } catch (e) { log('joinRoom', e); } @@ -364,15 +391,15 @@ export default class RoomView extends LoggedView { }; renderHeader = () => { - const { end } = this.state; - if (!end) { - return ; + const { loadingMore } = this.state; + if (loadingMore) { + return ; } return null; } renderList = () => { - const { loaded, end } = this.state; + const { loaded, end, loadingMore } = this.state; if (!loaded) { return ; } @@ -381,6 +408,7 @@ export default class RoomView extends LoggedView { ({ toggleSortDropdown: () => dispatch(toggleSortDropdownAction()), openSearchHeader: () => dispatch(openSearchHeaderAction()), @@ -114,6 +115,7 @@ export default class RoomsListView extends LoggedView { showFavorites: PropTypes.bool, showUnread: PropTypes.bool, useRealName: PropTypes.bool, + appState: PropTypes.string, toggleSortDropdown: PropTypes.func, openSearchHeader: PropTypes.func, closeSearchHeader: PropTypes.func, @@ -164,7 +166,7 @@ export default class RoomsListView extends LoggedView { componentDidUpdate(prevProps) { const { - sortBy, groupByType, showFavorites, showUnread + sortBy, groupByType, showFavorites, showUnread, appState } = this.props; if (!( @@ -174,6 +176,8 @@ export default class RoomsListView extends LoggedView { && (prevProps.showUnread === showUnread) )) { this.getSubscriptions(); + } else if (appState === 'foreground' && appState !== prevProps.appState) { + RocketChat.getRooms().catch(e => console.log(e)); } } @@ -416,9 +420,10 @@ export default class RoomsListView extends LoggedView { // if user is using the search we need first to join/create room try { const { username } = item; - const sub = await RocketChat.createDirectMessage(username); - const { rid } = sub; - return this.goRoom(rid); + const result = await RocketChat.createDirectMessage(username); + if (result.success) { + return this.goRoom(result.room._id); + } } catch (e) { log('RoomsListView._onPressItem', e); } diff --git a/app/views/SetUsernameView.js b/app/views/SetUsernameView.js index 5851fff0..48636870 100644 --- a/app/views/SetUsernameView.js +++ b/app/views/SetUsernameView.js @@ -1,14 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - Text, ScrollView, Alert, StyleSheet + Text, ScrollView, StyleSheet } from 'react-native'; import { connect } from 'react-redux'; import SafeAreaView from 'react-native-safe-area-view'; -import equal from 'deep-equal'; import { Navigation } from 'react-native-navigation'; -import { setUsernameSubmit as setUsernameSubmitAction } from '../actions/login'; +import { loginRequest as loginRequestAction } from '../actions/login'; import TextInput from '../containers/TextInput'; import Button from '../containers/Button'; import KeyboardView from '../presentation/KeyboardView'; @@ -28,9 +27,9 @@ const styles = StyleSheet.create({ @connect(state => ({ server: state.server.server, - login: state.login + token: state.login.user && state.login.user.token }), dispatch => ({ - setUsernameSubmit: params => dispatch(setUsernameSubmitAction(params)) + loginRequest: params => dispatch(loginRequestAction(params)) })) /** @extends React.Component */ export default class SetUsernameView extends LoggedView { @@ -43,15 +42,15 @@ export default class SetUsernameView extends LoggedView { static propTypes = { componentId: PropTypes.string, server: PropTypes.string, - setUsernameSubmit: PropTypes.func.isRequired, - Accounts_UsernamePlaceholder: PropTypes.string, - login: PropTypes.object + userId: PropTypes.string, + loginRequest: PropTypes.func } constructor(props) { super('SetUsernameView', props); this.state = { - username: '' + username: '', + saving: false }; const { componentId, server } = this.props; Navigation.mergeOptions(componentId, { @@ -68,13 +67,8 @@ export default class SetUsernameView extends LoggedView { this.usernameInput.focus(); }, 600); const suggestion = await RocketChat.getUsernameSuggestion(); - this.setState({ username: suggestion }); - } - - componentDidUpdate(prevProps) { - const { login } = this.props; - if (login && login.failure && login.error && !equal(login.error, prevProps.login.error)) { - Alert.alert(I18n.t('Oops'), login.error.reason); + if (suggestion.success) { + this.setState({ username: suggestion.result }); } } @@ -84,15 +78,27 @@ export default class SetUsernameView extends LoggedView { } } - submit = () => { + submit = async() => { const { username } = this.state; - const { setUsernameSubmit } = this.props; - setUsernameSubmit({ username }); + const { loginRequest, token } = this.props; + + if (!username.trim()) { + return; + } + + this.setState({ saving: true }); + try { + await RocketChat.setUsername(username); + RocketChat.setApiUser({ userId: null, authToken: null }); + await loginRequest({ resume: token }); + } catch (e) { + console.log('SetUsernameView -> catch -> e', e); + } + this.setState({ saving: false }); } render() { - const { username } = this.state; - const { login } = this.props; + const { username, saving } = this.state; return ( @@ -117,7 +123,7 @@ export default class SetUsernameView extends LoggedView { onPress={this.submit} testID='set-username-view-submit' disabled={!username} - loading={login.isFetching} + loading={saving} /> diff --git a/e2e/03-forgotpassword.spec.js b/e2e/03-forgotpassword.spec.js index 3e887fc9..04391ad7 100644 --- a/e2e/03-forgotpassword.spec.js +++ b/e2e/03-forgotpassword.spec.js @@ -32,7 +32,7 @@ describe('Forgot password screen', () => { describe('Usage', async() => { it('should reset password and navigate to login', async() => { - await element(by.id('forgot-password-view-email')).replaceText(data.email); + await element(by.id('forgot-password-view-email')).replaceText('diego.mello@rocket.chat'); await element(by.id('forgot-password-view-submit')).tap(); await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(60000); await expect(element(by.id('login-view'))).toBeVisible(); diff --git a/e2e/04-createuser.spec.js b/e2e/04-createuser.spec.js index 79e33d97..5097efe9 100644 --- a/e2e/04-createuser.spec.js +++ b/e2e/04-createuser.spec.js @@ -58,17 +58,15 @@ describe('Create user screen', () => { }); describe('Usage', () => { - it('should submit invalid email and raise error', async() => { - const invalidEmail = 'invalidemail'; - await element(by.id('register-view-name')).replaceText(data.user); - await element(by.id('register-view-username')).replaceText(data.user); - await element(by.id('register-view-email')).replaceText(invalidEmail); - await element(by.id('register-view-password')).replaceText(data.password); - await element(by.id('register-view-submit')).tap(); - await waitFor(element(by.text(`Invalid email ${ invalidEmail }`)).atIndex(0)).toExist().withTimeout(10000); - await expect(element(by.text(`Invalid email ${ invalidEmail }`)).atIndex(0)).toExist(); - await element(by.text('OK')).tap(); - }); + // FIXME: Detox isn't able to check if it's tappable: https://github.com/wix/Detox/issues/246 + // it.only('should submit invalid email and do nothing', async() => { + // const invalidEmail = 'invalidemail'; + // await element(by.id('register-view-name')).replaceText(data.user); + // await element(by.id('register-view-username')).replaceText(data.user); + // await element(by.id('register-view-email')).replaceText(invalidEmail); + // await element(by.id('register-view-password')).replaceText(data.password); + // await element(by.id('register-view-submit')).tap(); + // }); it('should submit email already taken and raise error', async() => { const invalidEmail = 'invalidemail'; @@ -77,8 +75,20 @@ describe('Create user screen', () => { await element(by.id('register-view-email')).replaceText('diego.mello@rocket.chat'); await element(by.id('register-view-password')).replaceText(data.password); await element(by.id('register-view-submit')).tap(); - await waitFor(element(by.text('Email already exists.')).atIndex(0)).toExist().withTimeout(10000); - await expect(element(by.text('Email already exists.')).atIndex(0)).toExist(); + await waitFor(element(by.text('Email already exists. [403]')).atIndex(0)).toExist().withTimeout(10000); + await expect(element(by.text('Email already exists. [403]')).atIndex(0)).toExist(); + await element(by.text('OK')).tap(); + }); + + it('should submit email already taken and raise error', async() => { + const invalidEmail = 'invalidemail'; + await element(by.id('register-view-name')).replaceText(data.user); + await element(by.id('register-view-username')).replaceText('diego.mello'); + await element(by.id('register-view-email')).replaceText(data.email); + await element(by.id('register-view-password')).replaceText(data.password); + await element(by.id('register-view-submit')).tap(); + await waitFor(element(by.text('Username is already in use')).atIndex(0)).toExist().withTimeout(10000); + await expect(element(by.text('Username is already in use')).atIndex(0)).toExist(); await element(by.text('OK')).tap(); }); @@ -92,21 +102,6 @@ describe('Create user screen', () => { await expect(element(by.id('rooms-list-view'))).toBeVisible(); }); - it('should pick an existing username, suggest another and finish register', async() => { - await logout(); - await navigateToRegister(); - await element(by.id('register-view-name')).replaceText(data.user); - await element(by.id('register-view-username')).replaceText(data.user); - await element(by.id('register-view-email')).replaceText(`${ data.email }2`); - await element(by.id('register-view-password')).replaceText(data.password); - await element(by.id('register-view-submit')).tap(); - await waitFor(element(by.id('set-username-view'))).toBeVisible().withTimeout(60000); - await expect(element(by.id('set-username-view'))).toBeVisible(); - await element(by.id('set-username-view-submit')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000); - await expect(element(by.id('rooms-list-view'))).toBeVisible(); - }); - afterEach(async() => { takeScreenshot(); }); diff --git a/package-lock.json b/package-lock.json index 3c27dc22..0aebf2ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2149,8 +2149,8 @@ "integrity": "sha512-iOD1PRnTSVr9sDWQdesIpfRrwJhHfeEQe5BpalQxC5OhM9thpiE6cu2NlW1KBWl0RJG4ZiJaF1xLlCo9YxU6dA==" }, "@rocket.chat/sdk": { - "version": "git+https://github.com/RocketChat/Rocket.Chat.js.SDK.git#86d0b0f544ea700f742a66f59a21e1679aa7ff50", - "from": "git+https://github.com/RocketChat/Rocket.Chat.js.SDK.git#ddp", + "version": "git+https://github.com/RocketChat/Rocket.Chat.js.SDK.git#3257e342690eb103f3ea5eec918fb73670ddb6a8", + "from": "git+https://github.com/RocketChat/Rocket.Chat.js.SDK.git#temp-ddp", "requires": { "@types/lru-cache": "^4.1.0", "@types/node": "^9.4.6", @@ -2573,9 +2573,9 @@ "integrity": "sha512-FWR7QB7EqBRq1s9BMk0ccOSOuRLfVEWYpHQYpFPaXtCoqN6dJx2ttdsdQbUxLLnAlKpYeVjveGGhQ3583TTa7g==" }, "@types/node": { - "version": "9.6.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.36.tgz", - "integrity": "sha512-Fbw+AdRLL01vv7Rk7bYaNPecqmKoinJHGbpKnDpbUZmUj/0vj3nLqPQ4CNBzr3q2zso6Cq/4jHoCAdH78fvJrw==" + "version": "9.6.40", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.40.tgz", + "integrity": "sha512-M3HHoXXndsho/sTbQML2BJr7/uwNhMg8P0D4lb+UsM65JQZx268faiz9hKpY4FpocWqpwlLwa8vevw8hLtKjOw==" }, "@types/react": { "version": "16.4.6", @@ -4642,6 +4642,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" @@ -4650,7 +4651,8 @@ "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true } } }, @@ -6664,11 +6666,6 @@ "randomfill": "^1.0.3" } }, - "crypto-js": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.8.tgz", - "integrity": "sha1-cV8HC/YBTyrpkqmLOSkli3E/CNU=" - }, "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", @@ -8246,11 +8243,6 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-1.1.1.tgz", "integrity": "sha1-qG5e5r2qFgVEddp5fM3fDFVphJE=" }, - "eventemitter3": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", - "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=" - }, "events": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", @@ -8813,9 +8805,9 @@ } }, "follow-redirects": { - "version": "1.5.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.9.tgz", - "integrity": "sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", "requires": { "debug": "=3.1.0" }, @@ -10427,7 +10419,8 @@ "hoist-non-react-statics": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", - "integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs=" + "integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs=", + "dev": true }, "home-or-tmp": { "version": "2.0.0", @@ -14127,7 +14120,8 @@ "lodash._getnative": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true }, "lodash.assign": { "version": "4.2.0", @@ -14174,12 +14168,14 @@ "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true }, "lodash.isarray": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true }, "lodash.isequal": { "version": "4.5.0", @@ -14196,6 +14192,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, "requires": { "lodash._getnative": "^3.0.0", "lodash.isarguments": "^3.0.0", @@ -14958,23 +14955,6 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, - "minimongo-cache": { - "version": "0.0.48", - "resolved": "https://registry.npmjs.org/minimongo-cache/-/minimongo-cache-0.0.48.tgz", - "integrity": "sha1-pvu3i2YnVUJJr+78EkPPfLpr6gc=", - "requires": { - "eventemitter3": "^1.1.0", - "invariant": "^2.1.1", - "lodash": "~2.4.1" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" - } - } - }, "minipass": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", @@ -15078,11 +15058,6 @@ } } }, - "mobx": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mobx/-/mobx-2.7.0.tgz", - "integrity": "sha1-zz2C0YwMp/RY2PKiQIF7PcflSgE=" - }, "mocha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", @@ -17690,21 +17665,6 @@ } } }, - "raf": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.1.0.tgz", - "integrity": "sha1-XYS/gbV/l5+MSSvgg3jFOLtO7Pw=", - "requires": { - "performance-now": "~0.2.0" - }, - "dependencies": { - "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" - } - } - }, "ramda": { "version": "0.24.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.24.1.tgz", @@ -17970,32 +17930,11 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.3.tgz", "integrity": "sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA==" }, - "react-komposer": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/react-komposer/-/react-komposer-1.13.1.tgz", - "integrity": "sha1-S4rEvMcTI710E9yrlcgxGX9Q7tA=", - "requires": { - "babel-runtime": "6.x.x", - "hoist-non-react-statics": "1.x.x", - "invariant": "2.x.x", - "mobx": "^2.3.4", - "shallowequal": "0.2.x" - } - }, "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, - "react-mixin": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/react-mixin/-/react-mixin-3.1.1.tgz", - "integrity": "sha512-z9fZ0aCRDjlgxLdMeWkJ9TwhmVLhQ09r8RFpin/cEPA2T6jsb7YHNWcIe0Oii+hhJNyMymdy91CSya5mRkuCkg==", - "requires": { - "object-assign": "^4.0.1", - "smart-mixin": "^2.0.0" - } - }, "react-modal": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.5.1.tgz", @@ -18239,9 +18178,9 @@ "from": "github:corymsmith/react-native-fabric#523a4edab3b2bf55ea9eeea2cf0dde82c5c29dd4" }, "react-native-fast-image": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-5.1.1.tgz", - "integrity": "sha512-kEzgZxbbXYhy27u5GnhrKitn+XDBFAHSDUJdYC6llMi5cDPjgcqhOAQABj0K+ga5pn+/xPZLmD882rrUGiwVVA==" + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-5.0.11.tgz", + "integrity": "sha512-5NNQwRniOfSBAvKldyPEs1xotWxrFcplOSQiVc78dv/EhH4G0IpdrLtsQmBdB91EMtPQfvoT269sKqj5MJCgyA==" }, "react-native-fit-image": { "version": "1.5.4", @@ -18277,9 +18216,8 @@ } }, "react-native-image-crop-picker": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/react-native-image-crop-picker/-/react-native-image-crop-picker-0.21.3.tgz", - "integrity": "sha512-qzY8aSYZxH4L9XYRk4V1n8x1gfq+ykNG0Kc0a9ne+JWwAQkf2P8aTKeNd4noNFZEOSJBiD4XXE/pbX55dQ5F3g==" + "version": "git+https://github.com/RocketChat/react-native-image-crop-picker.git#6c205596b5496b207daa93408c9cef886e04bdbb", + "from": "git+https://github.com/RocketChat/react-native-image-crop-picker.git" }, "react-native-image-pan-zoom": { "version": "2.1.11", @@ -18334,23 +18272,6 @@ "react-native-fit-image": "^1.5.2" } }, - "react-native-meteor": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/react-native-meteor/-/react-native-meteor-1.4.0.tgz", - "integrity": "sha512-Bm5RGTDv7LsJqqWjaR8KCv2mpeeMMPOIeZWHSx1yeTLUnEx71jVWAbHNXmEEynyH61/NVUpwDaXZKxiw9OwhFA==", - "requires": { - "base-64": "^0.1.0", - "crypto-js": "^3.1.6", - "ejson": "^2.1.2", - "minimongo-cache": "0.0.48", - "prop-types": "^15.5.10", - "react-komposer": "^1.8.0", - "react-mixin": "^3.0.3", - "trackr": "^2.0.2", - "underscore": "^1.8.3", - "wolfy87-eventemitter": "^4.3.0" - } - }, "react-native-modal": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-7.0.0.tgz", @@ -20180,6 +20101,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-0.2.2.tgz", "integrity": "sha1-HjL9W8q2rWiKSBLLDMBO/HXHAU4=", + "dev": true, "requires": { "lodash.keys": "^3.1.2" } @@ -20301,11 +20223,6 @@ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=" }, - "smart-mixin": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/smart-mixin/-/smart-mixin-2.0.0.tgz", - "integrity": "sha1-o0oQVeMqdbMNK048oyPcmctT9Dc=" - }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -21812,14 +21729,6 @@ } } }, - "trackr": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/trackr/-/trackr-2.0.2.tgz", - "integrity": "sha1-7jixO1gLMN9ejgJw0c89AhLEdF4=", - "requires": { - "raf": "~3.1.0" - } - }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", @@ -22767,11 +22676,6 @@ "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", "dev": true }, - "wolfy87-eventemitter": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/wolfy87-eventemitter/-/wolfy87-eventemitter-4.3.0.tgz", - "integrity": "sha1-ZJc5bJXnQ1nwa241QJM5MY2Nlk8=" - }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/package.json b/package.json index fa2bce6a..c43bf40e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "dependencies": { "@remobile/react-native-toast": "^1.0.7", - "@rocket.chat/sdk": "git+https://github.com/RocketChat/Rocket.Chat.js.SDK.git#ddp", + "@rocket.chat/sdk": "git+https://github.com/RocketChat/Rocket.Chat.js.SDK.git#temp-ddp", "deep-equal": "^1.0.1", "ejson": "^2.1.2", "js-base64": "^2.4.9", @@ -41,16 +41,15 @@ "react-native-device-info": "^0.24.3", "react-native-dialog": "^5.4.0", "react-native-fabric": "github:corymsmith/react-native-fabric#523a4edab3b2bf55ea9eeea2cf0dde82c5c29dd4", - "react-native-fast-image": "^5.1.1", + "react-native-fast-image": "^5.0.11", "react-native-gesture-handler": "^1.0.9", "react-native-i18n": "^2.0.15", - "react-native-image-crop-picker": "0.21.3", + "react-native-image-crop-picker": "git+https://github.com/RocketChat/react-native-image-crop-picker.git", "react-native-image-zoom-viewer": "^2.2.23", "react-native-keyboard-aware-scroll-view": "^0.7.4", "react-native-keyboard-input": "^5.3.1", "react-native-keyboard-tracking-view": "^5.5.0", "react-native-markdown-renderer": "^3.2.8", - "react-native-meteor": "^1.4.0", "react-native-modal": "^7.0.0", "react-native-navigation": "^2.1.3", "react-native-notifications": "^1.1.21",