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');
} else if (!filterUsers && filterRooms) {
data = data.filter(item => item.t !== 'd');
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
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
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');