Newer
Older
import { AsyncStorage, InteractionManager } from 'react-native';
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
import RNUserDefaults from 'rn-user-defaults';
import { Q } from '@nozbe/watermelondb';
Djorkaeff Alexandre
committed
import * as FileSystem from 'expo-file-system';
import reduxStore from './createStore';
import defaultSettings from '../constants/settings';
import messagesStatus from '../constants/messagesStatus';
import { isIOS, getBundleId } from '../utils/deviceInfo';
Djorkaeff Alexandre
committed
import { extractHostname } from '../utils/server';
import fetch, { BASIC_AUTH_KEY } from '../utils/fetch';
import { setUser, setLoginServices, loginRequest } from '../actions/login';
import { disconnect, connectSuccess, connectRequest } from '../actions/connect';
import {
shareSelectServer, shareSetUser
} from '../actions/share';
import subscribeRooms from './methods/subscriptions/rooms';
import getUsersPresence, { getUserPresence, subscribeUsersPresence } from './methods/getUsersPresence';
import protectedFunction from './methods/helpers/protectedFunction';
import readMessages from './methods/readMessages';
import getSettings, { getLoginSettings, setSettings } from './methods/getSettings';
import getRooms from './methods/getRooms';
import getPermissions from './methods/getPermissions';
import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis';
import getSlashCommands from './methods/getSlashCommands';
import getRoles from './methods/getRoles';
import triggerBlockAction, { triggerSubmitView, triggerCancel } from './methods/actions';
import loadMessagesForRoom from './methods/loadMessagesForRoom';
import loadMissedMessages from './methods/loadMissedMessages';
import loadThreadMessages from './methods/loadThreadMessages';
import sendMessage, { sendMessageCall } from './methods/sendMessage';
import { sendFileMessage, cancelUpload, isUploadActive } from './methods/sendFileMessage';
import callJitsi from './methods/callJitsi';
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';
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
const STATUSES = ['offline', 'online', 'away', 'busy'];
async subscribeRooms() {
if (!this.roomsSub) {
try {
this.roomsSub = await subscribeRooms.call(this);
} catch (e) {
log(e);
}
createChannel({
name, users, type, readOnly, broadcast
}) {
return this.methodCall(type ? 'createPrivateGroup' : 'createChannel', name, users, readOnly, {}, { broadcast });
return await RNUserDefaults.get(TOKEN_KEY);
console.warn(`RNUserDefaults error: ${ error.message }`);
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 });
try {
await sdk.connect();
} catch (err) {
if (err.message && err.message.includes('400')) {
return {
success: false,
message: 'Websocket_disabled',
messageOptions: {
contact: I18n.t('Contact_your_server_admin')
}
};
}
}
sdk.disconnect();
return {
success: true
};
},
const notRCServer = {
success: false,
message: 'Not_RC_Server',
messageOptions: {
contact: I18n.t('Contact_your_server_admin')
}
};
const result = await fetch(`${ server }/api/info`).then(async(response) => {
let res = notRCServer;
try {
res = await response.json();
if (!(res && res.success)) {
return notRCServer;
}
} catch (e) {
// do nothing
}
return res;
});
if (result.success) {
if (semver.lt(result.version, MIN_ROCKETCHAT_VERSION)) {
return {
success: false,
message: 'Invalid_server_version',
messageOptions: {
minVersion: MIN_ROCKETCHAT_VERSION
}
};
}
message: 'The_URL_is_invalid',
messageOptions: {
contact: I18n.t('Contact_your_server_admin')
}
stopListener(listener) {
return listener && listener.stop();
connect({ server, user, logoutOnError = false }) {
if (!this.sdk || this.sdk.client.host !== server) {
database.setActiveDB(server);
}
if (this.connectTimeout) {
clearTimeout(this.connectTimeout);
}
if (this.connectedListener) {
this.connectedListener.then(this.stopListener);
}
if (this.closeListener) {
this.closeListener.then(this.stopListener);
}
if (this.usersListener) {
this.usersListener.then(this.stopListener);
}
if (this.notifyLoggedListener) {
this.notifyLoggedListener.then(this.stopListener);
}
if (this.roomsSub) {
this.roomsSub.stop();
this.roomsSub = null;
if (this.sdk) {
this.sdk.disconnect();
this.sdk = null;
}
if (this.code) {
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.getSettings();
const sdkConnect = () => this.sdk.connect()
.then(() => {
if (user && user.token) {
reduxStore.dispatch(loginRequest({ resume: user.token }, logoutOnError));
}
})
.catch((err) => {
console.log('connect error', err);
// when `connect` raises an error, we try again in 10 seconds
this.connectTimeout = setTimeout(() => {
sdkConnect();
sdkConnect();
this.connectedListener = this.sdk.onStreamData('connected', () => {
this.closeListener = this.sdk.onStreamData('close', () => {
reduxStore.dispatch(disconnect());
});
this.usersListener = this.sdk.onStreamData('users', protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage)));
this.notifyLoggedListener = this.sdk.onStreamData('stream-notify-logged', protectedFunction((ddpMessage) => {
const { eventName } = ddpMessage.fields;
if (eventName === 'user-status') {
this.activeUsers = this.activeUsers || {};
if (!this._setUserTimer) {
this._setUserTimer = setTimeout(() => {
const activeUsersBatch = this.activeUsers;
InteractionManager.runAfterInteractions(() => {
reduxStore.dispatch(setActiveUsers(activeUsersBatch));
});
this._setUserTimer = null;
return this.activeUsers = {};
}, 10000);
}
const userStatus = ddpMessage.fields.args[0];
const [id,, status, statusText] = userStatus;
this.activeUsers[id] = { status: STATUSES[status], statusText };
const { user: loggedUser } = reduxStore.getState().login;
if (loggedUser && loggedUser.id === id) {
reduxStore.dispatch(setUser({ status: STATUSES[status], statusText }));
async shareExtensionInit(server) {
Diego Mello
committed
database.setShareDB(server);
Diego Mello
committed
if (this.shareSDK) {
this.shareSDK.disconnect();
this.shareSDK = null;
}
// Use useSsl: false only if server url starts with http://
const useSsl = !/http:\/\//.test(server);
Diego Mello
committed
this.shareSDK = new RocketchatClient({ host: server, protocol: 'ddp', useSsl });
reduxStore.dispatch(shareSelectServer(server));
// set User info
try {
const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`);
const userCollections = serversDB.collections.get('users');
let user = null;
if (userId) {
const userRecord = await userCollections.find(userId);
id: userRecord.id,
token: userRecord.token,
username: userRecord.username,
roles: userRecord.roles
reduxStore.dispatch(shareSetUser(user));
await RocketChat.login({ resume: user.token });
} catch (e) {
log(e);
}
Diego Mello
committed
closeShareExtension() {
if (this.shareSDK) {
this.shareSDK.disconnect();
this.shareSDK = null;
}
database.share = null;
reduxStore.dispatch(shareSetUser(null));
Diego Mello
committed
},
return this.methodCall('jitsi:updateTimeout', rid);
return this.post('users.register', credentials, false);
return this.methodCall('setUsername', username);
return this.post('users.forgotPassword', { email }, false);
},
loginTOTP(params) {
return new Promise(async(resolve, reject) => {
try {
const result = await this.login(params);
return resolve(result);
} catch (e) {
if (e.data?.error && (e.data.error === 'totp-required' || e.data.error === 'totp-invalid')) {
const { details } = e.data;
try {
const code = await twoFactor({ method: details?.method || 'totp', invalid: e.data.error === 'totp-invalid' });
return resolve(this.loginTOTP({ ...params, code: code?.twoFactorCode }));
} catch {
// twoFactor was canceled
return reject();
}
} else {
reject(e);
}
}
});
loginWithPassword({ user, password }) {
const state = reduxStore.getState();
if (state.settings.LDAP_Enable) {
params = {
ldap: true,
ldapOptions: {}
};
} else if (state.settings.CROWD_Enable) {
params = {
};
}
return this.loginTOTP(params);
async loginOAuthOrSso(params) {
reduxStore.dispatch(loginRequest({ resume: result.token }));
} catch (error) {
throw error;
}
},
Diego Mello
committed
const sdk = this.shareSDK || this.sdk;
Diego Mello
committed
await sdk.login(params);
const { result } = sdk.currentLogin;
const user = {
id: result.userId,
token: result.authToken,
username: result.me.username,
name: result.me.name,
language: result.me.language,
status: result.me.status,
emails: result.me.emails,
roles: result.me.roles
if (this.roomsSub) {
this.roomsSub.stop();
this.roomsSub = null;
if (this.activeUsersSubTimeout) {
clearTimeout(this.activeUsersSubTimeout);
this.activeUsersSubTimeout = false;
}
console.log('logout -> removePushToken -> catch -> error', error);
} catch (error) {
console.log('logout -> api logout -> catch -> error', error);
}
try {
const servers = await RNUserDefaults.objectForKey(SERVERS);
await RNUserDefaults.setObjectForKey(SERVERS, servers && servers.filter(srv => srv[SERVER_URL] !== server));
Djorkaeff Alexandre
committed
// 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 }`);
const db = database.active;
await db.action(() => db.unsafeResetDatabase());
async clearCache({ server }) {
try {
const serversDB = database.servers;
await serversDB.action(async() => {
const serverCollection = serversDB.collections.get('servers');
const serverRecord = await serverCollection.find(server);
await serverRecord.update((s) => {
s.roomsUpdatedAt = null;
});
});
} catch (e) {
// Do nothing
}
try {
const db = database.active;
await db.action(() => db.unsafeResetDatabase());
} catch (e) {
// Do nothing
}
},
return new Promise(async(resolve) => {
const token = getDeviceToken();
if (token) {
await this.post('push.token', data);
} catch (error) {
console.log(error);
}
}
return resolve();
});
},
removePushToken() {
const token = getDeviceToken();
if (token) {
return this.sdk.del('push.token', { token });
await db.action(async() => {
await message.update((m) => {
m.status = messagesStatus.TEMP;
});
let m = {
id: message.id,
msg: message.msg,
subscription: { id: message.subscription.id }
};
if (tmid) {
m = {
...m,
tmid
};
await sendMessageCall.call(this, m);
} catch (e) {
log(e);
async search({ text, filterUsers = true, filterRooms = true }) {
const searchText = text.trim();
if (this.oldPromise) {
this.oldPromise('cancel');
}
if (searchText === '') {
delete this.oldPromise;
return [];
}
const db = database.active;
let data = await db.collections.get('subscriptions').query(
Q.where('name', Q.like(`%${ Q.sanitizeLikeString(searchText) }%`))
).fetch();
data = data.filter(item => item.t === 'd' && !RocketChat.isGroupChat(item));
} else if (!filterUsers && filterRooms) {
data = data.filter(item => item.t !== 'd' || RocketChat.isGroupChat(item));
data = data.map((sub) => {
if (sub.t !== 'd') {
return ({
rid: sub.rid,
name: sub.name,
fname: sub.fname,
t: sub.t,
search: true
});
}
return sub;
});
const usernames = data.map(sub => sub.name);
try {
if (data.length < 7) {
const { users, rooms } = await Promise.race([
RocketChat.spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }),
new Promise((resolve, reject) => this.oldPromise = reject)
]);
if (filterUsers) {
data = data.concat(users.map(user => ({
...user,
rid: user.username,
name: user.username,
t: 'd',
search: true
})));
}
if (filterRooms) {
rooms.forEach((room) => {
// Check if it exists on local database
const index = data.findIndex(item => item.rid === room._id);
if (index === -1) {
data.push({
rid: room._id,
...room,
search: true
});
}
});
}
return data;
} catch (e) {
console.warn(e);
return this.methodCall('spotlight', search, usernames, type);
return this.post('im.create', { username });
createGroupChat() {
const { users } = reduxStore.getState().selectedUsers;
const usernames = users.map(u => u.name).join(',');
// RC 3.1.0
return this.post('im.create', { usernames });
createDiscussion({
prid, pmid, t_name, reply, users
}) {
// RC 1.0.0
return this.post('rooms.createDiscussion', {
prid, pmid, t_name, reply, users
});
},
return this.methodCall('joinRoom', roomId);
return this.post('channels.join', { roomId });
triggerBlockAction,
triggerSubmitView,
triggerCancel,
sendFileMessage,
cancelUpload,
isUploadActive,
parseSettings: settings => settings.reduce((ret, item) => {
ret[item._id] = defaultSettings[item._id] && item[defaultSettings[item._id].type];
if (item._id === 'Hide_System_Messages') {
ret[item._id] = ret[item._id]
.reduce((array, value) => [...array, ...value === 'mute_unmute' ? ['user-muted', 'user-unmuted'] : [value]], []);
}
Guilherme Gazzo
committed
_prepareSettings(settings) {
return settings.map((setting) => {
setting[defaultSettings[setting._id].type] = setting.value;
Guilherme Gazzo
committed
return setting;
});
},
return this.post('chat.delete', { msgId: messageId, roomId: rid });
return this.post('chat.update', { roomId: rid, msgId: id, text: msg });
return this.post('subscriptions.unread', { firstUnreadMessage: { _id: messageId } });
toggleStarMessage(messageId, starred) {
if (starred) {
return this.post('chat.unStarMessage', { messageId });
return this.post('chat.starMessage', { messageId });
togglePinMessage(messageId, pinned) {
if (pinned) {
return this.post('chat.unPinMessage', { messageId });
return this.post('chat.pinMessage', { messageId });
return this.post('chat.reportMessage', { messageId, description: 'Message reported by user' });
async getRoom(rid) {
try {
const db = database.active;
const room = await db.collections.get('subscriptions').find(rid);
return Promise.resolve(room);
} catch (error) {
Guilherme Gazzo
committed
return Promise.reject(new Error('Room not found'));
}
async getPermalinkMessage(message) {
room = await RocketChat.getRoom(message.subscription.id);
Guilherme Gazzo
committed
const roomType = {
p: 'group',
c: 'channel',
d: 'direct'
}[room.t];
return `${ server }/${ roomType }/${ room.name }?msg=${ message.id }`;
Guilherme Gazzo
committed
},
getPermalinkChannel(channel) {
const { server } = reduxStore.getState().server;
const roomType = {
p: 'group',
c: 'channel',
d: 'direct'
}[channel.t];
return `${ server }/${ roomType }/${ channel.name }`;
},
subscribeRoom(...args) {
return this.sdk.subscribeRoom(...args);
},
onStreamData(...args) {
return this.sdk.onStreamData(...args);
},
emitTyping(room, t = true) {
const { login } = reduxStore.getState();
return this.methodCall('stream-notify-room', `${ room }/typing`, login.user.username, t);
return this.methodCall('UserPresence:away');
return this.methodCall('UserPresence:online');
setUserStatus(status, message) {
return this.post('users.setStatus', { status, message });
return this.post('chat.react', { emoji, messageId });
return this.post('rooms.favorite', { roomId, favorite });
toggleRead(read, roomId) {
if (read) {
return this.post('subscriptions.unread', { roomId });
}
return this.post('subscriptions.read', { rid: roomId });
},
getRoomMembers(rid, allUsers, skip = 0, limit = 10) {
return this.methodCall('getUsersOfRoom', rid, allUsers, { skip, limit });
Diego Mello
committed
},
return this.methodCall('getUserRoles');
return this.sdk.get(`${ this.roomTypeToApiType(t) }.counters`, { roomId });
getChannelInfo(roomId) {
// RC 0.48.0
return this.sdk.get('channels.info', { roomId });
},
getUserInfo(userId) {
// RC 0.48.0
return this.sdk.get('users.info', { userId });
},
pranavpandey1998official
committed
getRoomInfo(roomId) {
// RC 0.72.0
return this.sdk.get('rooms.info', { roomId });
},
getUidDirectMessage(room) {
const { id: userId } = reduxStore.getState().login.user;
// legacy method
if (!room.uids && room.rid && room.t === 'd') {
return room.rid.replace(userId, '').trim();
}
if (RocketChat.isGroupChat(room)) {
return false;
const me = room && room.uids && room.uids.find(uid => uid === userId);
const other = room && room.uids && room.uids.filter(uid => uid !== userId);
return other && other.length ? other[0] : me;
},
isGroupChat(room) {
return (room.uids && room.uids.length > 2) || (room.usernames && room.usernames.length > 2);
Diego Mello
committed
toggleBlockUser(rid, blocked, block) {
if (block) {
return this.methodCall('blockUser', { rid, blocked });
Diego Mello
committed
}
return this.methodCall('unblockUser', { rid, blocked });
Diego Mello
committed
},
return this.post(`${ this.roomTypeToApiType(t) }.leave`, { roomId });
return this.post(`${ this.roomTypeToApiType(t) }.delete`, { roomId });
toggleMuteUserInRoom(rid, username, mute) {
if (mute) {
return this.methodCall('muteUserInRoom', { rid, username });
return this.methodCall('unmuteUserInRoom', { rid, username });
return this.post(`${ this.roomTypeToApiType(t) }.archive`, { roomId });
return this.post(`${ this.roomTypeToApiType(t) }.unarchive`, { roomId });
hideRoom(roomId, t) {
return this.post(`${ this.roomTypeToApiType(t) }.close`, { roomId });
},
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
return this.methodCall('saveRoomSettings', rid, params);
},
post(...args) {
return new Promise(async(resolve, reject) => {
try {
const result = await this.sdk.post(...args);
return resolve(result);
} catch (e) {
if (e.data && (e.data.errorType === 'totp-required' || e.data.errorType === 'totp-invalid')) {
const { details } = e.data;
try {
await twoFactor({ method: details?.method, invalid: e.data.errorType === 'totp-invalid' });
return resolve(this.post(...args));
} catch {
// twoFactor was canceled
return resolve({});
}
} else {
reject(e);
}
}
});
},
methodCall(...args) {
return new Promise(async(resolve, reject) => {
try {
const result = await this.sdk.methodCall(...args, this.code || '');
return resolve(result);
} catch (e) {
if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) {
const { details } = e;
try {
this.code = await twoFactor({ method: details?.method, invalid: e.error === 'totp-invalid' });
return resolve(this.methodCall(...args));
} catch {
// twoFactor was canceled
return resolve({});
}
} else {
reject(e);
}
}
});
},
sendEmailCode() {
const { username } = reduxStore.getState().login.user;
// RC 3.1.0
return this.post('users.2fa.sendEmailCode', { emailOrUsername: username });
return this.post('users.updateOwnBasicInfo', { data, customFields });
return this.methodCall('saveUserPreferences', params);
saveNotificationSettings(roomId, notifications) {
return this.post('rooms.saveNotification', { roomId, notifications });
},
addUsersToRoom(rid) {
let { users } = reduxStore.getState().selectedUsers;
users = users.map(u => u.name);
return this.methodCall('addUsersToRoom', { rid, users });
return this.methodCall('getSingleMessage', msgId);
async hasPermission(permissions, rid) {
const db = database.active;
const subsCollection = db.collections.get('subscriptions');
const permissionsCollection = db.collections.get('permissions');
// get the room from database
const room = await subsCollection.find(rid);
roomRoles = room.roles || [];
console.log('hasPermission -> Room not found');
return permissions.reduce((result, permission) => {
result[permission] = false;
return result;
}, {});
}
// get permissions from database
try {
const permissionsFiltered = await permissionsCollection.query(Q.where('id', Q.oneOf(permissions))).fetch();
const shareUser = reduxStore.getState().share.user;
const loginUser = reduxStore.getState().login.user;
// get user roles on the server from redux
const userRoles = (shareUser.roles || loginUser.roles) || [];
// merge both roles
const mergedRoles = [...new Set([...roomRoles, ...userRoles])];
// return permissions in object format
// e.g. { 'edit-room': true, 'set-readonly': false }
return permissions.reduce((result, permission) => {
result[permission] = false;
const permissionFound = permissionsFiltered.find(p => p.id === permission);
if (permissionFound) {
result[permission] = returnAnArray(permissionFound.roles).some(r => mergedRoles.includes(r));
}
return result;
}, {});
} catch (e) {
log(e);
}
return this.methodCall('getAvatarSuggestion');