diff --git a/app/i18n/locales/en.js b/app/i18n/locales/en.js
index e89b9df7b..b9f723706 100644
--- a/app/i18n/locales/en.js
+++ b/app/i18n/locales/en.js
@@ -560,6 +560,7 @@ export default {
You_will_be_logged_out_of_this_application: 'You will be logged out of this application.',
Clear: 'Clear',
This_will_clear_all_your_offline_data: 'This will clear all your offline data.',
+ This_will_remove_all_data_from_this_server: 'This will remove all data from this server.',
Mark_unread: 'Mark Unread',
Wait_activation_warning: 'Before you can login, your account must be manually activated by an administrator.'
};
diff --git a/app/i18n/locales/pt-BR.js b/app/i18n/locales/pt-BR.js
index 5cd3fb3e1..c104637d7 100644
--- a/app/i18n/locales/pt-BR.js
+++ b/app/i18n/locales/pt-BR.js
@@ -499,6 +499,7 @@ export default {
You_will_be_logged_out_of_this_application: 'Você sairá deste aplicativo.',
Clear: 'Limpar',
This_will_clear_all_your_offline_data: 'Isto limpará todos os seus dados offline.',
+ This_will_remove_all_data_from_this_server: 'Isto removerá todos os dados desse servidor.',
Mark_unread: 'Marcar como não Lida',
Wait_activation_warning: 'Antes que você possa fazer o login, sua conta deve ser manualmente ativada por um administrador.'
};
diff --git a/app/lib/database/index.js b/app/lib/database/index.js
index 67f1188ae..31bf5317b 100644
--- a/app/lib/database/index.js
+++ b/app/lib/database/index.js
@@ -33,6 +33,36 @@ if (__DEV__ && isIOS) {
console.log(appGroupPath);
}
+export const getDatabase = (database = '') => {
+ const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.');
+ const dbName = `${ appGroupPath }${ path }.db`;
+
+ const adapter = new SQLiteAdapter({
+ dbName,
+ schema: appSchema,
+ migrations
+ });
+
+ return new Database({
+ adapter,
+ modelClasses: [
+ Subscription,
+ Room,
+ Message,
+ Thread,
+ ThreadMessage,
+ CustomEmoji,
+ FrequentlyUsedEmoji,
+ Upload,
+ Setting,
+ Role,
+ Permission,
+ SlashCommand
+ ],
+ actionsEnabled: true
+ });
+};
+
class DB {
databases = {
serversDB: new Database({
@@ -86,34 +116,8 @@ class DB {
});
}
- setActiveDB(database = '') {
- const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.');
- const dbName = `${ appGroupPath }${ path }.db`;
-
- const adapter = new SQLiteAdapter({
- dbName,
- schema: appSchema,
- migrations
- });
-
- this.databases.activeDB = new Database({
- adapter,
- modelClasses: [
- Subscription,
- Room,
- Message,
- Thread,
- ThreadMessage,
- CustomEmoji,
- FrequentlyUsedEmoji,
- Upload,
- Setting,
- Role,
- Permission,
- SlashCommand
- ],
- actionsEnabled: true
- });
+ setActiveDB(database) {
+ this.databases.activeDB = getDatabase(database);
}
}
diff --git a/app/lib/methods/logout.js b/app/lib/methods/logout.js
new file mode 100644
index 000000000..4064362de
--- /dev/null
+++ b/app/lib/methods/logout.js
@@ -0,0 +1,127 @@
+import RNUserDefaults from 'rn-user-defaults';
+import * as FileSystem from 'expo-file-system';
+import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
+
+import { SERVERS, SERVER_URL } from '../../constants/userDefaults';
+import { getDeviceToken } from '../../notifications/push';
+import { extractHostname } from '../../utils/server';
+import { BASIC_AUTH_KEY } from '../../utils/fetch';
+import database, { getDatabase } from '../database';
+import RocketChat from '../rocketchat';
+import { useSsl } from '../../utils/url';
+
+async function removeServerKeys({ server, userId }) {
+ await RNUserDefaults.clear(`${ RocketChat.TOKEN_KEY }-${ server }`);
+ await RNUserDefaults.clear(`${ RocketChat.TOKEN_KEY }-${ userId }`);
+ await RNUserDefaults.clear(`${ BASIC_AUTH_KEY }-${ server }`);
+}
+
+async function removeSharedCredentials({ server }) {
+ try {
+ const servers = await RNUserDefaults.objectForKey(SERVERS);
+ await RNUserDefaults.setObjectForKey(SERVERS, servers && servers.filter(srv => srv[SERVER_URL] !== server));
+
+ // clear certificate for server - SSL Pinning
+ const certificate = await RNUserDefaults.objectForKey(extractHostname(server));
+ if (certificate && certificate.path) {
+ await RNUserDefaults.clear(extractHostname(server));
+ await FileSystem.deleteAsync(certificate.path);
+ }
+ } catch (e) {
+ console.log('removeSharedCredentials', e);
+ }
+}
+
+async function removeServerData({ server }) {
+ try {
+ const batch = [];
+ const serversDB = database.servers;
+ const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`);
+
+ const usersCollection = serversDB.collections.get('users');
+ if (userId) {
+ const userRecord = await usersCollection.find(userId);
+ batch.push(userRecord.prepareDestroyPermanently());
+ }
+ const serverCollection = serversDB.collections.get('servers');
+ const serverRecord = await serverCollection.find(server);
+ batch.push(serverRecord.prepareDestroyPermanently());
+
+ await serversDB.action(() => serversDB.batch(...batch));
+ await removeSharedCredentials({ server });
+ await removeServerKeys({ server });
+ } catch (e) {
+ console.log('removeServerData', e);
+ }
+}
+
+async function removeCurrentServer() {
+ await RNUserDefaults.clear('currentServer');
+ await RNUserDefaults.clear(RocketChat.TOKEN_KEY);
+}
+
+async function removeServerDatabase({ server }) {
+ try {
+ const db = getDatabase(server);
+ await db.action(() => db.unsafeResetDatabase());
+ } catch (e) {
+ console.log(e);
+ }
+}
+
+export async function removeServer({ server }) {
+ try {
+ const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`);
+ if (userId) {
+ const resume = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ userId }`);
+
+ const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) });
+ await sdk.login({ resume });
+
+ const token = getDeviceToken();
+ if (token) {
+ await sdk.del('push.token', { token });
+ }
+
+ await sdk.logout();
+ }
+
+ await removeServerData({ server });
+ await removeServerDatabase({ server });
+ } catch (e) {
+ console.log('removePush', e);
+ }
+}
+
+export default async function logout({ server }) {
+ if (this.roomsSub) {
+ this.roomsSub.stop();
+ this.roomsSub = null;
+ }
+
+ if (this.activeUsersSubTimeout) {
+ clearTimeout(this.activeUsersSubTimeout);
+ this.activeUsersSubTimeout = false;
+ }
+
+ try {
+ await this.removePushToken();
+ } catch (e) {
+ console.log('removePushToken', e);
+ }
+
+ try {
+ // RC 0.60.0
+ await this.sdk.logout();
+ } catch (e) {
+ console.log('logout', e);
+ }
+
+ if (this.sdk) {
+ this.sdk = null;
+ }
+
+ await removeServerData({ server });
+ await removeCurrentServer();
+ await removeServerDatabase({ server });
+}
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index 098717d26..5bef3ad8a 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -3,7 +3,6 @@ import semver from 'semver';
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
import RNUserDefaults from 'rn-user-defaults';
import { Q } from '@nozbe/watermelondb';
-import * as FileSystem from 'expo-file-system';
import reduxStore from './createStore';
import defaultSettings from '../constants/settings';
@@ -11,8 +10,7 @@ import messagesStatus from '../constants/messagesStatus';
import database from './database';
import log from '../utils/log';
import { isIOS, getBundleId } from '../utils/deviceInfo';
-import { extractHostname } from '../utils/server';
-import fetch, { BASIC_AUTH_KEY } from '../utils/fetch';
+import fetch from '../utils/fetch';
import { setUser, setLoginServices, loginRequest } from '../actions/login';
import { disconnect, connectSuccess, connectRequest } from '../actions/connect';
@@ -43,12 +41,13 @@ import sendMessage, { sendMessageCall } from './methods/sendMessage';
import { sendFileMessage, cancelUpload, isUploadActive } from './methods/sendFileMessage';
import callJitsi from './methods/callJitsi';
+import logout, { removeServer } from './methods/logout';
import { getDeviceToken } from '../notifications/push';
-import { SERVERS, SERVER_URL } from '../constants/userDefaults';
import { setActiveUsers } from '../actions/activeUsers';
import I18n from '../i18n';
import { twoFactor } from '../utils/twoFactor';
+import { useSsl } from '../utils/url';
const TOKEN_KEY = 'reactnativemeteor_usertoken';
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
@@ -86,10 +85,7 @@ const RocketChat = {
}
},
async getWebsocketInfo({ server }) {
- // Use useSsl: false only if server url starts with http://
- const useSsl = !/http:\/\//.test(server);
-
- const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl });
+ const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) });
try {
await sdk.connect();
@@ -200,10 +196,7 @@ const RocketChat = {
this.code = null;
}
- // Use useSsl: false only if server url starts with http://
- const useSsl = !/http:\/\//.test(server);
-
- this.sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl });
+ this.sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) });
this.getSettings();
const sdkConnect = () => this.sdk.connect()
@@ -270,10 +263,7 @@ const RocketChat = {
this.shareSDK = null;
}
- // Use useSsl: false only if server url starts with http://
- const useSsl = !/http:\/\//.test(server);
-
- this.shareSDK = new RocketchatClient({ host: server, protocol: 'ddp', useSsl });
+ this.shareSDK = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) });
// set Server
const serversDB = database.servers;
@@ -408,73 +398,8 @@ const RocketChat = {
throw e;
}
},
- async logout({ server }) {
- if (this.roomsSub) {
- this.roomsSub.stop();
- this.roomsSub = null;
- }
-
- if (this.activeUsersSubTimeout) {
- clearTimeout(this.activeUsersSubTimeout);
- this.activeUsersSubTimeout = false;
- }
-
- try {
- await this.removePushToken();
- } catch (error) {
- console.log('logout -> removePushToken -> catch -> error', error);
- }
- try {
- // RC 0.60.0
- await this.sdk.logout();
- } catch (error) {
- console.log('logout -> api logout -> catch -> error', error);
- }
- this.sdk = null;
-
- try {
- const servers = await RNUserDefaults.objectForKey(SERVERS);
- await RNUserDefaults.setObjectForKey(SERVERS, servers && servers.filter(srv => srv[SERVER_URL] !== server));
- // clear certificate for server - SSL Pinning
- const certificate = await RNUserDefaults.objectForKey(extractHostname(server));
- if (certificate && certificate.path) {
- await RNUserDefaults.clear(extractHostname(server));
- await FileSystem.deleteAsync(certificate.path);
- }
- } catch (error) {
- console.log('logout_rn_user_defaults', error);
- }
-
- const userId = await RNUserDefaults.get(`${ TOKEN_KEY }-${ server }`);
-
- try {
- const serversDB = database.servers;
- await serversDB.action(async() => {
- const usersCollection = serversDB.collections.get('users');
- const userRecord = await usersCollection.find(userId);
- const serverCollection = serversDB.collections.get('servers');
- const serverRecord = await serverCollection.find(server);
- await serversDB.batch(
- userRecord.prepareDestroyPermanently(),
- serverRecord.prepareDestroyPermanently()
- );
- });
- } catch (error) {
- // Do nothing
- }
-
- await RNUserDefaults.clear('currentServer');
- await RNUserDefaults.clear(TOKEN_KEY);
- await RNUserDefaults.clear(`${ TOKEN_KEY }-${ server }`);
- await RNUserDefaults.clear(`${ BASIC_AUTH_KEY }-${ server }`);
-
- try {
- const db = database.active;
- await db.action(() => db.unsafeResetDatabase());
- } catch (error) {
- console.log(error);
- }
- },
+ logout,
+ removeServer,
async clearCache({ server }) {
try {
const serversDB = database.servers;
diff --git a/app/presentation/UserItem.js b/app/presentation/UserItem.js
index de8e0ef73..bb37108bf 100644
--- a/app/presentation/UserItem.js
+++ b/app/presentation/UserItem.js
@@ -1,13 +1,13 @@
import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
-import { LongPressGestureHandler, State } from 'react-native-gesture-handler';
import Avatar from '../containers/Avatar';
import { CustomIcon } from '../lib/Icons';
import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors';
import Touch from '../utils/touch';
+import LongPress from '../utils/longPress';
const styles = StyleSheet.create({
button: {
@@ -41,38 +41,25 @@ const styles = StyleSheet.create({
const UserItem = ({
name, username, onPress, testID, onLongPress, style, icon, baseUrl, user, theme
-}) => {
- const longPress = ({ nativeEvent }) => {
- if (nativeEvent.state === State.ACTIVE) {
- if (onLongPress) {
- onLongPress();
- }
- }
- };
-
- return (
- (
+
+
-
-
-
-
- {name}
- @{username}
-
- {icon ? : null}
+
+
+
+ {name}
+ @{username}
-
-
- );
-};
+ {icon ? : null}
+
+
+
+);
UserItem.propTypes = {
name: PropTypes.string.isRequired,
diff --git a/app/utils/longPress.js b/app/utils/longPress.js
new file mode 100644
index 000000000..e491efcb9
--- /dev/null
+++ b/app/utils/longPress.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { State, LongPressGestureHandler } from 'react-native-gesture-handler';
+
+class LongPress extends React.Component {
+ setNativeProps(props) {
+ this.ref.setNativeProps(props);
+ }
+
+ getRef = (ref) => {
+ this.ref = ref;
+ };
+
+ longPress = ({ nativeEvent }) => {
+ const { onLongPress } = this.props;
+ if (nativeEvent.state === State.ACTIVE) {
+ if (onLongPress) {
+ onLongPress();
+ }
+ }
+ };
+
+ render() {
+ const { children, ...props } = this.props;
+
+ return (
+
+ {children}
+
+ );
+ }
+}
+
+LongPress.propTypes = {
+ children: PropTypes.node,
+ onLongPress: PropTypes.func
+};
+
+export default LongPress;
diff --git a/app/utils/url.js b/app/utils/url.js
index 856eac771..16506ed13 100644
--- a/app/utils/url.js
+++ b/app/utils/url.js
@@ -7,3 +7,6 @@ export const isValidURL = (url) => {
+ '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator
return !!pattern.test(url);
};
+
+// Use useSsl: false only if server url starts with http://
+export const useSsl = url => !/http:\/\//.test(url);
diff --git a/app/views/RoomsListView/ServerDropdown.js b/app/views/RoomsListView/ServerDropdown.js
index f91c20bd8..3e69b956a 100644
--- a/app/views/RoomsListView/ServerDropdown.js
+++ b/app/views/RoomsListView/ServerDropdown.js
@@ -22,6 +22,8 @@ import { withTheme } from '../../theme';
import { KEY_COMMAND, handleCommandSelectServer } from '../../commands';
import { isTablet } from '../../utils/deviceInfo';
import { withSplit } from '../../split';
+import LongPress from '../../utils/longPress';
+import { showConfirmationAlert } from '../../utils/info';
const ROW_HEIGHT = 68;
const ANIMATION_DURATION = 200;
@@ -132,7 +134,6 @@ class ServerDropdown extends Component {
const {
server: currentServer, selectServerRequest, navigation, split
} = this.props;
-
this.close();
if (currentServer !== server) {
const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`);
@@ -152,6 +153,19 @@ class ServerDropdown extends Component {
}
}
+ remove = server => showConfirmationAlert({
+ message: I18n.t('This_will_remove_all_data_from_this_server'),
+ callToAction: I18n.t('Delete'),
+ onPress: async() => {
+ this.close();
+ try {
+ await RocketChat.removeServer({ server });
+ } catch {
+ // do nothing
+ }
+ }
+ });
+
handleCommands = ({ event }) => {
const { servers } = this.state;
const { navigation } = this.props;
@@ -173,35 +187,37 @@ class ServerDropdown extends Component {
const { server, theme } = this.props;
return (
- this.select(item.id)}
- testID={`rooms-list-header-server-${ item.id }`}
- theme={theme}
- >
-
- {item.iconURL
- ? (
- console.warn('error loading serverIcon')}
- />
- )
- : (
-
- )
- }
-
- {item.name || item.id}
- {item.id}
+ (item.id === server || this.remove(item.id))}>
+ this.select(item.id)}
+ testID={`rooms-list-header-server-${ item.id }`}
+ theme={theme}
+ >
+
+ {item.iconURL
+ ? (
+ console.warn('error loading serverIcon')}
+ />
+ )
+ : (
+
+ )
+ }
+
+ {item.name || item.id}
+ {item.id}
+
+ {item.id === server ? : null}
- {item.id === server ? : null}
-
-
+
+
);
}