Merge branch 'develop' into chore.dehydrate-login-methods-from-rocketchatjs

This commit is contained in:
Gerzon Z 2022-02-25 11:39:36 -04:00
commit ff44417b32
81 changed files with 1389 additions and 740 deletions

View File

@ -1,59 +0,0 @@
import * as types from './actionsTypes';
export function loginRequest(credentials, logoutOnError, isFromWebView) {
return {
type: types.LOGIN.REQUEST,
credentials,
logoutOnError,
isFromWebView
};
}
export function loginSuccess(user) {
return {
type: types.LOGIN.SUCCESS,
user
};
}
export function loginFailure(err) {
return {
type: types.LOGIN.FAILURE,
err
};
}
export function logout(forcedByServer = false) {
return {
type: types.LOGOUT,
forcedByServer
};
}
export function setUser(user) {
return {
type: types.USER.SET,
user
};
}
export function setLoginServices(data) {
return {
type: types.LOGIN.SET_SERVICES,
data
};
}
export function setPreference(preference) {
return {
type: types.LOGIN.SET_PREFERENCE,
preference
};
}
export function setLocalAuthenticated(isLocalAuthenticated) {
return {
type: types.LOGIN.SET_LOCAL_AUTHENTICATED,
isLocalAuthenticated
};
}

115
app/actions/login.ts Normal file
View File

@ -0,0 +1,115 @@
import { Action } from 'redux';
import { IUser } from '../definitions';
import * as types from './actionsTypes';
interface ICredentials {
resume: string;
user: string;
password: string;
}
interface ILoginRequest extends Action {
credentials: any;
logoutOnError?: boolean;
isFromWebView?: boolean;
}
interface ILoginSuccess extends Action {
user: Partial<IUser>;
}
interface ILoginFailure extends Action {
err: Partial<IUser>;
}
interface ILogout extends Action {
forcedByServer: boolean;
}
interface ISetUser extends Action {
user: Partial<IUser>;
}
interface ISetServices extends Action {
data: Record<string, string>;
}
interface ISetPreference extends Action {
preference: Record<string, any>;
}
interface ISetLocalAuthenticated extends Action {
isLocalAuthenticated: boolean;
}
export type TActionsLogin = ILoginRequest &
ILoginSuccess &
ILoginFailure &
ILogout &
ISetUser &
ISetServices &
ISetPreference &
ISetLocalAuthenticated;
export function loginRequest(
credentials: Partial<ICredentials>,
logoutOnError?: boolean,
isFromWebView?: boolean
): ILoginRequest {
return {
type: types.LOGIN.REQUEST,
credentials,
logoutOnError,
isFromWebView
};
}
export function loginSuccess(user: Partial<IUser>): ILoginSuccess {
return {
type: types.LOGIN.SUCCESS,
user
};
}
export function loginFailure(err: Record<string, any>): ILoginFailure {
return {
type: types.LOGIN.FAILURE,
err
};
}
export function logout(forcedByServer = false): ILogout {
return {
type: types.LOGOUT,
forcedByServer
};
}
export function setUser(user: Partial<IUser>): ISetUser {
return {
type: types.USER.SET,
user
};
}
export function setLoginServices(data: Record<string, any>): ISetServices {
return {
type: types.LOGIN.SET_SERVICES,
data
};
}
export function setPreference(preference: Record<string, any>): ISetPreference {
return {
type: types.LOGIN.SET_PREFERENCE,
preference
};
}
export function setLocalAuthenticated(isLocalAuthenticated: boolean): ISetLocalAuthenticated {
return {
type: types.LOGIN.SET_LOCAL_AUTHENTICATED,
isLocalAuthenticated
};
}

View File

@ -1,62 +0,0 @@
import * as types from './actionsTypes';
export function subscribeRoom(rid) {
return {
type: types.ROOM.SUBSCRIBE,
rid
};
}
export function unsubscribeRoom(rid) {
return {
type: types.ROOM.UNSUBSCRIBE,
rid
};
}
export function leaveRoom(roomType, room, selected) {
return {
type: types.ROOM.LEAVE,
room,
roomType,
selected
};
}
export function deleteRoom(roomType, room, selected) {
return {
type: types.ROOM.DELETE,
room,
roomType,
selected
};
}
export function closeRoom(rid) {
return {
type: types.ROOM.CLOSE,
rid
};
}
export function forwardRoom(rid, transferData) {
return {
type: types.ROOM.FORWARD,
transferData,
rid
};
}
export function removedRoom() {
return {
type: types.ROOM.REMOVED
};
}
export function userTyping(rid, status = true) {
return {
type: types.ROOM.USER_TYPING,
rid,
status
};
}

109
app/actions/room.ts Normal file
View File

@ -0,0 +1,109 @@
import { Action } from 'redux';
import { ERoomType } from '../definitions/ERoomType';
import { ROOM } from './actionsTypes';
// TYPE RETURN RELATED
type ISelected = Record<string, string>;
export interface ITransferData {
roomId: string;
userId?: string;
departmentId?: string;
}
// ACTION RETURN RELATED
interface IBaseReturn extends Action {
rid: string;
}
type TSubscribeRoom = IBaseReturn;
type TUnsubscribeRoom = IBaseReturn;
type TCloseRoom = IBaseReturn;
type TRoom = Record<string, any>;
interface ILeaveRoom extends Action {
roomType: ERoomType;
room: TRoom;
selected?: ISelected;
}
interface IDeleteRoom extends Action {
roomType: ERoomType;
room: TRoom;
selected?: ISelected;
}
interface IForwardRoom extends Action {
transferData: ITransferData;
rid: string;
}
interface IUserTyping extends Action {
rid: string;
status: boolean;
}
export type TActionsRoom = TSubscribeRoom & TUnsubscribeRoom & TCloseRoom & ILeaveRoom & IDeleteRoom & IForwardRoom & IUserTyping;
export function subscribeRoom(rid: string): TSubscribeRoom {
return {
type: ROOM.SUBSCRIBE,
rid
};
}
export function unsubscribeRoom(rid: string): TUnsubscribeRoom {
return {
type: ROOM.UNSUBSCRIBE,
rid
};
}
export function leaveRoom(roomType: ERoomType, room: TRoom, selected?: ISelected): ILeaveRoom {
return {
type: ROOM.LEAVE,
room,
roomType,
selected
};
}
export function deleteRoom(roomType: ERoomType, room: TRoom, selected?: ISelected): IDeleteRoom {
return {
type: ROOM.DELETE,
room,
roomType,
selected
};
}
export function closeRoom(rid: string): TCloseRoom {
return {
type: ROOM.CLOSE,
rid
};
}
export function forwardRoom(rid: string, transferData: ITransferData): IForwardRoom {
return {
type: ROOM.FORWARD,
transferData,
rid
};
}
export function removedRoom(): Action {
return {
type: ROOM.REMOVED
};
}
export function userTyping(rid: string, status = true): IUserTyping {
return {
type: ROOM.USER_TYPING,
rid,
status
};
}

View File

@ -82,6 +82,7 @@ interface IMessageBoxProps {
isFocused(): boolean;
user: {
id: string;
_id: string;
username: string;
token: string;
};
@ -1184,5 +1185,5 @@ const mapStateToProps = (state: any) => ({
const dispatchToProps = {
typing: (rid: any, status: any) => userTypingAction(rid, status)
};
// @ts-ignore
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(withActionSheet(MessageBox)) as any;

View File

@ -45,7 +45,14 @@ export const SYSTEM_MESSAGES = [
'message_snippeted',
'thread-created',
'room_e2e_enabled',
'room_e2e_disabled'
'room_e2e_disabled',
'removed-user-from-team',
'added-user-to-team',
'user-added-room-to-team',
'user-converted-to-team',
'user-converted-to-channel',
'user-deleted-room-from-team',
'user-removed-room-from-team'
];
export const SYSTEM_MESSAGE_TYPES = {
@ -56,7 +63,14 @@ export const SYSTEM_MESSAGE_TYPES = {
USER_JOINED_TEAM: 'ujt',
USER_JOINED_DISCUSSION: 'ut',
USER_LEFT_CHANNEL: 'ul',
USER_LEFT_TEAM: 'ult'
USER_LEFT_TEAM: 'ult',
REMOVED_USER_FROM_TEAM: 'removed-user-from-team',
ADDED_USER_TO_TEAM: 'added-user-to-team',
ADDED_ROOM_TO_TEAM: 'user-added-room-to-team',
CONVERTED_TO_TEAM: 'user-converted-to-team',
CONVERTED_TO_CHANNEL: 'user-converted-to-channel',
DELETED_ROOM_FROM_TEAM: 'user-deleted-room-from-team',
REMOVED_ROOM_FROM_TEAM: 'user-removed-room-from-team'
};
export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [
@ -67,7 +81,14 @@ export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [
SYSTEM_MESSAGE_TYPES.USER_JOINED_TEAM,
SYSTEM_MESSAGE_TYPES.USER_JOINED_DISCUSSION,
SYSTEM_MESSAGE_TYPES.USER_LEFT_CHANNEL,
SYSTEM_MESSAGE_TYPES.USER_LEFT_TEAM
SYSTEM_MESSAGE_TYPES.USER_LEFT_TEAM,
SYSTEM_MESSAGE_TYPES.REMOVED_USER_FROM_TEAM,
SYSTEM_MESSAGE_TYPES.ADDED_USER_TO_TEAM,
SYSTEM_MESSAGE_TYPES.ADDED_ROOM_TO_TEAM,
SYSTEM_MESSAGE_TYPES.CONVERTED_TO_TEAM,
SYSTEM_MESSAGE_TYPES.CONVERTED_TO_CHANNEL,
SYSTEM_MESSAGE_TYPES.DELETED_ROOM_FROM_TEAM,
SYSTEM_MESSAGE_TYPES.REMOVED_ROOM_FROM_TEAM
];
type TInfoMessage = {
@ -76,6 +97,7 @@ type TInfoMessage = {
msg: string;
author: { username: string };
};
export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage): string => {
const { username } = author;
if (type === 'rm') {
@ -147,6 +169,27 @@ export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage): strin
if (type === 'room_e2e_enabled') {
return I18n.t('This_room_encryption_has_been_enabled_by__username_', { username });
}
if (type === 'removed-user-from-team') {
return I18n.t('Removed__username__from_team', { user_removed: username });
}
if (type === 'added-user-to-team') {
return I18n.t('Added__username__to_team', { user_added: username });
}
if (type === 'user-added-room-to-team') {
return I18n.t('added__roomName__to_team', { roomName: msg });
}
if (type === 'user-converted-to-team') {
return I18n.t('Converted__roomName__to_team', { roomName: msg });
}
if (type === 'user-converted-to-channel') {
return I18n.t('Converted__roomName__to_channel', { roomName: msg });
}
if (type === 'user-deleted-room-from-team') {
return I18n.t('Deleted__roomName__', { roomName: msg });
}
if (type === 'user-removed-room-from-team') {
return I18n.t('Removed__roomName__from_this_team', { roomName: msg });
}
return '';
};

View File

@ -0,0 +1,7 @@
export enum ERoomType {
p = 'group',
c = 'channel',
d = 'direct',
t = 'team',
l = 'omnichannel'
}

View File

@ -1,5 +1,5 @@
export interface IAttachment {
ts: Date;
ts: string | Date;
title: string;
type: string;
description: string;

View File

@ -1,8 +1,9 @@
import Model from '@nozbe/watermelondb/Model';
export interface ICustomEmoji {
_id: string;
name?: string;
aliases?: string;
aliases?: string[];
extension: string;
_updatedAt: Date;
}

View File

@ -63,7 +63,7 @@ export interface IMessage {
_id: string;
rid: string;
msg?: string;
id?: string;
id: string;
t?: MessageType;
ts: string | Date;
u: IUserMessage;
@ -74,7 +74,7 @@ export interface IMessage {
emoji?: string;
attachments?: IAttachment[];
urls?: IUrl[];
_updatedAt: Date;
_updatedAt: string | Date;
status?: number;
pinned?: boolean;
starred?: boolean;
@ -83,10 +83,10 @@ export interface IMessage {
role?: string;
drid?: string;
dcount?: number;
dlm?: Date;
dlm?: string | Date;
tmid?: string;
tcount?: number;
tlm?: Date;
tlm?: string | Date;
replies?: string[];
mentions?: IUserMention[];
channels?: IUserChannel[];

View File

@ -1,6 +1,6 @@
export interface IRocketChatRecord {
_id: string;
_updatedAt: Date;
_id?: string;
_updatedAt?: Date;
}
export type RocketChatRecordDeleted<T> = T &

View File

@ -1,5 +1,7 @@
import Model from '@nozbe/watermelondb/Model';
import { MarkdownAST } from '@rocket.chat/message-parser';
import { IAttachment } from './IAttachment';
import { IMessage } from './IMessage';
import { IServedBy } from './IServedBy';
import { SubscriptionType } from './ISubscription';
@ -37,6 +39,7 @@ export interface IRoom {
tags?: string[];
e2eKeyId?: string;
avatarETag?: string;
latest?: string;
default?: true;
featured?: true;
}
@ -100,3 +103,52 @@ export interface IOmnichannelRoom extends Omit<IRoom, 'default' | 'featured' | '
}
export type TRoomModel = IRoom & Model;
export interface IServerRoomItem {
_id: string;
name: string;
fname: string;
t: SubscriptionType;
u: {
_id: string;
username: string;
};
customFields: {};
ts: string;
ro: boolean;
_updatedAt: string;
lm: string;
lastMessage: {
alias: string;
msg: string;
attachments: IAttachment[];
parseUrls: boolean;
bot: {
i: string;
};
groupable: boolean;
avatar: string;
ts: string;
u: IUser;
rid: string;
_id: string;
_updatedAt: string;
mentions: [];
channels: [];
md: MarkdownAST;
};
topic: string;
joinCodeRequired: boolean;
description: string;
jitsiTimeout: string;
usersCount: number;
e2eKeyId: string;
avatarETag: string;
encrypted: boolean;
}
export interface IServerRoom {
update: IServerRoomItem[];
remove: IServerRoomItem[];
success: boolean;
}

View File

@ -1,5 +1,7 @@
import Model from '@nozbe/watermelondb/Model';
import { IEnterpriseModules } from '../reducers/enterpriseModules';
export interface IServer {
name: string;
iconURL: string;
@ -13,7 +15,7 @@ export interface IServer {
autoLockTime?: number;
biometry?: boolean;
uniqueID: string;
enterpriseModules: string;
enterpriseModules: IEnterpriseModules;
E2E_Enable: boolean;
}

View File

@ -9,4 +9,22 @@ export interface ISettings {
_updatedAt?: Date;
}
export interface IPreparedSettings {
_id: string;
value: string;
enterprise: boolean;
valueAsString?: string;
valueAsBoolean?: boolean;
valueAsNumber?: number;
}
export interface ISettingsIcon {
_id: string;
value: {
defaultUrl: string;
url?: string;
};
enterprise: boolean;
}
export type TSettingsModel = ISettings & Model;

View File

@ -9,4 +9,8 @@ export interface ISlashCommand {
appId?: string;
}
export interface ISlashCommandResult extends ISlashCommand {
command: string;
}
export type TSlashCommandModel = ISlashCommand & Model;

View File

@ -24,12 +24,20 @@ export interface IVisitor {
lastMessageTs: Date;
}
export enum ERoomTypes {
DIRECT = 'direct',
GROUP = 'group',
CHANNEL = 'channel'
}
export interface ISubscription {
_id: string; // _id belongs watermelonDB
id: string; // id from server
_updatedAt?: string; // from server
v?: IVisitor;
f: boolean;
t: SubscriptionType;
ts: Date;
ts: string | Date;
ls: Date;
name: string;
fname?: string;
@ -38,12 +46,14 @@ export interface ISubscription {
alert: boolean;
roles?: string[];
unread: number;
lm: string;
lr: string;
userMentions: number;
groupMentions: number;
tunread?: string[];
tunreadUser?: string[];
tunreadGroup?: string[];
roomUpdatedAt: Date;
roomUpdatedAt: Date | number;
ro: boolean;
lastOpen?: Date;
description?: string;
@ -59,7 +69,7 @@ export interface ISubscription {
ignored?: string[];
broadcast?: boolean;
prid?: string;
draftMessage?: string;
draftMessage?: string | null;
lastThreadSync?: Date;
jitsiTimeout?: number;
autoTranslate?: boolean;
@ -88,3 +98,30 @@ export interface ISubscription {
}
export type TSubscriptionModel = ISubscription & Model;
export interface IServerSubscriptionItem {
_id: string;
rid: string;
u: {
_id: string;
username: string;
};
_updatedAt: string;
alert: boolean;
fname: string;
groupMentions: number;
name: string;
open: boolean;
t: string;
unread: number;
userMentions: number;
ls: string;
lr: string;
tunread: number[] | [];
}
export interface IServerSubscription {
update: IServerSubscriptionItem[];
remove: IServerSubscriptionItem[];
success: boolean;
}

View File

@ -13,6 +13,7 @@ interface IFileThread {
}
export interface IThreadResult {
id: string;
_id: string;
rid: string;
ts: string | Date;
@ -23,13 +24,13 @@ export interface IThreadResult {
attachments?: IAttachment[];
md?: MarkdownAST;
u: IUserMessage;
_updatedAt: Date;
_updatedAt: string | Date;
urls?: IUrl[];
mentions?: IUserMention[];
channels?: IUserChannel[];
replies?: string[];
tcount?: number;
tlm?: Date;
tlm?: string | Date;
}
export interface IThread {
@ -38,8 +39,8 @@ export interface IThread {
msg?: string;
t?: MessageType;
rid: string;
_updatedAt?: Date;
ts?: Date;
_updatedAt?: string | Date;
ts?: string | Date;
u?: IUserMessage;
alias?: string;
parseUrls?: boolean;
@ -56,10 +57,10 @@ export interface IThread {
role?: string;
drid?: string;
dcount?: number | string;
dlm?: number;
dlm?: string | Date;
tmid?: string;
tcount?: number | string;
tlm?: string;
tlm?: string | Date;
replies?: string[];
mentions?: IUserMention[];
channels?: IUserChannel[];
@ -67,7 +68,7 @@ export interface IThread {
autoTranslate?: boolean;
translations?: any;
e2e?: string;
subscription: { id: string };
subscription?: { id: string };
}
export type TThreadModel = IThread & Model;

View File

@ -6,6 +6,7 @@ import { IReaction } from './IReaction';
import { IUrl } from './IUrl';
export interface IThreadMessage {
id: string;
_id: string;
tmsg?: string;
msg?: string;
@ -20,7 +21,7 @@ export interface IThreadMessage {
emoji?: string;
attachments?: IAttachment[];
urls?: IUrl[];
_updatedAt?: Date;
_updatedAt?: string | Date;
status?: number;
pinned?: boolean;
starred?: boolean;
@ -29,10 +30,10 @@ export interface IThreadMessage {
role?: string;
drid?: string;
dcount?: number;
dlm?: Date;
dlm?: string | Date;
tmid?: string;
tcount?: number;
tlm?: Date;
tlm?: string | Date;
replies?: string[];
mentions?: IUserMention[];
channels?: IUserChannel[];

View File

@ -1,16 +1,17 @@
import Model from '@nozbe/watermelondb/Model';
export interface IUpload {
id: string;
path?: string;
id?: string;
rid?: string;
path: string;
name?: string;
description?: string;
size: number;
type?: string;
store?: string;
progress: number;
error: boolean;
subscription: { id: string };
progress?: number;
error?: boolean;
subscription?: { id: string };
}
export type TUploadModel = IUpload & Model;

View File

@ -2,6 +2,7 @@ import Model from '@nozbe/watermelondb/Model';
import { UserStatus } from './UserStatus';
import { IRocketChatRecord } from './IRocketChatRecord';
import { ILoggedUser } from './ILoggedUser';
export interface ILoginToken {
hashedToken: string;
@ -93,14 +94,16 @@ export interface IUserSettings {
};
}
export interface IUser extends IRocketChatRecord {
export interface IUser extends IRocketChatRecord, Omit<ILoggedUser, 'username' | 'name' | 'status'> {
_id: string;
createdAt: Date;
roles: string[];
type: string;
active: boolean;
name?: string;
id: string;
token: string;
createdAt?: Date;
roles?: string[];
type?: string;
active?: boolean;
username: string;
name?: string;
services?: IUserServices;
emails?: IUserEmail[];
status?: UserStatus;
@ -115,7 +118,6 @@ export interface IUser extends IRocketChatRecord {
oauth?: {
authorizedClients: string[];
};
_updatedAt: Date;
statusLivechat?: string;
e2e?: {
private_key: string;

View File

@ -23,7 +23,9 @@ import { ICreateChannel } from '../../reducers/createChannel';
import { ICreateDiscussion } from '../../reducers/createDiscussion';
import { IEncryption } from '../../reducers/encryption';
import { IInviteLinks } from '../../reducers/inviteLinks';
import { ILogin } from '../../reducers/login';
import { IRoles } from '../../reducers/roles';
import { IRoom } from '../../reducers/room';
import { ISelectedUsers } from '../../reducers/selectedUsers';
import { IServer } from '../../reducers/server';
import { ISettings } from '../../reducers/settings';
@ -33,13 +35,13 @@ import { IEnterpriseModules } from '../../reducers/enterpriseModules';
export interface IApplicationState {
settings: ISettings;
login: any;
meteor: IConnect;
login: ILogin;
server: IServer;
selectedUsers: ISelectedUsers;
app: IApp;
createChannel: ICreateChannel;
room: any;
room: IRoom;
rooms: any;
sortPreferences: any;
share: IShare;

View File

@ -0,0 +1,5 @@
export type E2eEndpoints = {
'e2e.setUserPublicAndPrivateKeys': {
POST: (params: { public_key: string; private_key: string }) => void;
};
};

View File

@ -9,7 +9,7 @@ export type EmojiCustomEndpoints = {
} & PaginatedResult;
};
'emoji-custom.list': {
GET: (params: { query: string }) => {
GET: (params: { updatedSince: string }) => {
emojis?: {
update: ICustomEmojiDescriptor[];
};

View File

@ -14,6 +14,7 @@ import { OauthCustomConfiguration } from './settings';
import { UserEndpoints } from './user';
import { UsersEndpoints } from './users';
import { TeamsEndpoints } from './teams';
import { E2eEndpoints } from './e2e';
export type Endpoints = ChannelsEndpoints &
ChatEndpoints &
@ -30,4 +31,5 @@ export type Endpoints = ChannelsEndpoints &
OauthCustomConfiguration &
UserEndpoints &
UsersEndpoints &
TeamsEndpoints;
TeamsEndpoints &
E2eEndpoints;

View File

@ -788,5 +788,24 @@
"Enable_Message_Parser": "Enable Message Parser",
"Unsupported_format": "Unsupported format",
"Downloaded_file": "Downloaded file",
"Error_Download_file": "Error while downloading file"
"Error_Download_file": "Error while downloading file",
"added__roomName__to_team": "added #{{roomName}} to this Team",
"Added__username__to_team": "added @{{user_added}} to this Team",
"Converted__roomName__to_team": "converted #{{roomName}} to a Team",
"Converted__roomName__to_channel": "converted #{{roomName}} to a Channel",
"Converting_team_to_channel": "Converting Team to Channel",
"Deleted__roomName__": "deleted #{{roomName}}",
"Message_HideType_added_user_to_team": "Hide \"User Added to Team\" messages",
"Message_HideType_removed_user_from_team": "Hide \"User Removed from Team\" messages",
"Message_HideType_ujt": "Hide \"User Joined Team\" messages",
"Message_HideType_ult": "Hide \"User Left Team\" messages",
"Message_HideType_user_added_room_to_team": "Hide \"User Added Room to Team\" messages",
"Message_HideType_user_converted_to_channel": "Hide \"User converted team to a Channel\" messages",
"Message_HideType_user_converted_to_team": "Hide \"User converted channel to a Team\" messages",
"Message_HideType_user_deleted_room_from_team": "Hide \"User deleted room from Team\" messages",
"Message_HideType_user_removed_room_from_team": "Hide \"User removed room from Team\" messages",
"Removed__roomName__from_this_team": "removed #{{roomName}} from this Team",
"Removed__username__from_team": "removed @{{user_removed}} from this Team",
"User_joined_team": "joined this Team",
"User_left_team": "left this Team"
}

View File

@ -443,7 +443,7 @@ class Encryption {
};
// Decrypt a message
decryptMessage = async (message: Partial<IMessage>) => {
decryptMessage = async (message: Pick<IMessage, 't' | 'e2e' | 'rid' | 'msg' | 'tmsg'>) => {
const { t, e2e } = message;
// Prevent create a new instance if this room was encrypted sometime ago

View File

@ -1,9 +1,11 @@
import { store as reduxStore } from '../auxStore';
import Navigation from '../Navigation';
import { ISubscription } from '../../definitions';
import { events, logEvent } from '../../utils/log';
import { store } from '../auxStore';
import Navigation from '../Navigation';
import sdk from '../rocketchat';
async function jitsiURL({ room }) {
const { settings } = reduxStore.getState();
async function jitsiURL({ room }: { room: ISubscription }) {
const { settings } = store.getState();
const { Jitsi_Enabled } = settings;
if (!Jitsi_Enabled) {
@ -19,7 +21,7 @@ async function jitsiURL({ room }) {
let queryString = '';
if (Jitsi_Enabled_TokenAuth) {
try {
const accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', room?.rid);
const accessToken = await sdk.methodCallWrapper('jitsi:generateAccessToken', room?.rid);
queryString = `?jwt=${accessToken}`;
} catch {
logEvent(events.RA_JITSI_F);
@ -30,23 +32,23 @@ async function jitsiURL({ room }) {
if (Jitsi_URL_Room_Hash) {
rname = uniqueID + room?.rid;
} else {
rname = encodeURIComponent(room.t === 'd' ? room?.usernames?.join?.(' x ') : room?.name);
rname = encodeURIComponent(room.t === 'd' ? (room?.usernames?.join?.(' x ') as string) : room?.name);
}
return `${protocol}${domain}${prefix}${rname}${queryString}`;
}
export function callJitsiWithoutServer(path) {
export function callJitsiWithoutServer(path: string): void {
logEvent(events.RA_JITSI_VIDEO);
const { Jitsi_SSL } = reduxStore.getState().settings;
const { Jitsi_SSL } = store.getState().settings;
const protocol = Jitsi_SSL ? 'https://' : 'http://';
const url = `${protocol}${path}`;
Navigation.navigate('JitsiMeetView', { url, onlyAudio: false });
}
async function callJitsi(room, onlyAudio = false) {
async function callJitsi(room: ISubscription, onlyAudio = false): Promise<void> {
logEvent(onlyAudio ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
const url = await jitsiURL.call(this, { room });
const url = await jitsiURL({ room });
Navigation.navigate('JitsiMeetView', { url, onlyAudio, rid: room?.rid });
}

View File

@ -1,5 +1,8 @@
import database from '../database';
import { ERoomTypes } from '../../definitions';
import { store } from '../auxStore';
import database from '../database';
import RocketChat from '../rocketchat';
import sdk from '../rocketchat/services/sdk';
const restTypes = {
channel: 'channels',
@ -7,27 +10,28 @@ const restTypes = {
group: 'groups'
};
async function open({ type, rid, name }) {
async function open({ type, rid, name }: { type: ERoomTypes; rid: string; name: string }) {
try {
const params = rid ? { roomId: rid } : { roomName: name };
// if it's a direct link without rid we'll create a new dm
// if the dm already exists it'll return the existent
if (type === 'direct' && !rid) {
const result = await this.createDirectMessage(name);
if (type === ERoomTypes.DIRECT && !rid) {
const result = await RocketChat.createDirectMessage(name);
if (result.success) {
const { room } = result;
room.rid = room._id;
room.rid = room._id as string;
return room;
}
}
// if it's a group we need to check if you can open
if (type === 'group') {
if (type === ERoomTypes.GROUP) {
try {
// RC 0.61.0
await this.sdk.post(`${restTypes[type]}.open`, params);
} catch (e) {
// @ts-ignore
await sdk.post(`${restTypes[type]}.open`, params);
} catch (e: any) {
if (!(e.data && /is already open/.test(e.data.error))) {
return false;
}
@ -36,9 +40,10 @@ async function open({ type, rid, name }) {
// if it's a channel or group and the link don't have rid
// we'll get info from the room
if ((type === 'channel' || type === 'group') && !rid) {
if ((type === ERoomTypes.CHANNEL || type === ERoomTypes.GROUP) && !rid) {
// RC 0.72.0
const result = await this.sdk.get(`${restTypes[type]}.info`, params);
// @ts-ignore
const result: any = await sdk.get(`channel.info`, params);
if (result.success) {
const room = result[type];
room.rid = room._id;
@ -56,7 +61,7 @@ async function open({ type, rid, name }) {
}
}
export default async function canOpenRoom({ rid, path, isCall }) {
export default async function canOpenRoom({ rid, path, isCall }: { rid: string; isCall: boolean; path: string }): Promise<any> {
try {
const db = database.active;
const subsCollection = db.get('subscriptions');
@ -86,8 +91,9 @@ export default async function canOpenRoom({ rid, path, isCall }) {
}
const [type, name] = path.split('/');
const t = type as ERoomTypes;
try {
const result = await open.call(this, { type, rid, name });
const result = await open({ type: t, rid, name });
return result;
} catch (e) {
return false;

View File

@ -1,11 +1,12 @@
import sdk from '../rocketchat/services/sdk';
import { compareServerVersion } from '../utils';
import { store as reduxStore } from '../auxStore';
import database from '../database';
import log from '../../utils/log';
import { clearEnterpriseModules, setEnterpriseModules as setEnterpriseModulesAction } from '../../actions/enterpriseModules';
export const LICENSE_OMNICHANNEL_MOBILE_ENTERPRISE = 'omnichannel-mobile-enterprise';
export const LICENSE_LIVECHAT_ENTERPRISE = 'livechat-enterprise';
const LICENSE_OMNICHANNEL_MOBILE_ENTERPRISE = 'omnichannel-mobile-enterprise';
const LICENSE_LIVECHAT_ENTERPRISE = 'livechat-enterprise';
export async function setEnterpriseModules() {
try {
@ -29,17 +30,17 @@ export async function setEnterpriseModules() {
}
export function getEnterpriseModules() {
return new Promise(async resolve => {
return new Promise<void>(async resolve => {
try {
const { version: serverVersion, server: serverId } = reduxStore.getState().server;
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.1.0')) {
// RC 3.1.0
const enterpriseModules = await this.methodCallWrapper('license:getModules');
const enterpriseModules = await sdk.methodCallWrapper('license:getModules');
if (enterpriseModules) {
const serversDB = database.servers;
const serversCollection = serversDB.get('servers');
const server = await serversCollection.find(serverId);
await serversDB.action(async () => {
await serversDB.write(async () => {
await server.update(s => {
s.enterpriseModules = enterpriseModules.join(',');
});
@ -56,7 +57,7 @@ export function getEnterpriseModules() {
});
}
export function hasLicense(module) {
export function hasLicense(module: string) {
const { enterpriseModules } = reduxStore.getState();
return enterpriseModules.includes(module);
}

View File

@ -1,13 +1,22 @@
import orderBy from 'lodash/orderBy';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { ICustomEmojis } from '../../reducers/customEmojis';
import { compareServerVersion } from '../utils';
import { store as reduxStore } from '../auxStore';
import database from '../database';
import log from '../../utils/log';
import { setCustomEmojis as setCustomEmojisAction } from '../../actions/customEmojis';
import { ICustomEmoji, TCustomEmojiModel } from '../../definitions';
import sdk from '../rocketchat/services/sdk';
const getUpdatedSince = allEmojis => {
interface IUpdateEmojis {
update: TCustomEmojiModel[];
remove?: TCustomEmojiModel[];
allRecords: TCustomEmojiModel[];
}
const getUpdatedSince = (allEmojis: ICustomEmoji[]) => {
if (!allEmojis.length) {
return null;
}
@ -19,27 +28,27 @@ const getUpdatedSince = allEmojis => {
return ordered && ordered[0]._updatedAt.toISOString();
};
const updateEmojis = async ({ update = [], remove = [], allRecords }) => {
const updateEmojis = async ({ update = [], remove = [], allRecords }: IUpdateEmojis) => {
if (!((update && update.length) || (remove && remove.length))) {
return;
}
const db = database.active;
const emojisCollection = db.get('custom_emojis');
let emojisToCreate = [];
let emojisToUpdate = [];
let emojisToDelete = [];
let emojisToCreate: TCustomEmojiModel[] = [];
let emojisToUpdate: TCustomEmojiModel[] = [];
let emojisToDelete: TCustomEmojiModel[] = [];
// Create or update
if (update && update.length) {
emojisToCreate = update.filter(i1 => !allRecords.find(i2 => i1._id === i2.id));
emojisToUpdate = allRecords.filter(i1 => update.find(i2 => i1.id === i2._id));
emojisToCreate = emojisToCreate.map(emoji =>
const filterEmojisToCreate = update.filter(i1 => !allRecords.find(i2 => i1._id === i2.id));
const filterEmojisToUpdate = allRecords.filter(i1 => update.find(i2 => i1.id === i2._id));
emojisToCreate = filterEmojisToCreate.map(emoji =>
emojisCollection.prepareCreate(e => {
e._raw = sanitizedRaw({ id: emoji._id }, emojisCollection.schema);
Object.assign(e, emoji);
})
);
emojisToUpdate = emojisToUpdate.map(emoji => {
emojisToUpdate = filterEmojisToUpdate.map(emoji => {
const newEmoji = update.find(e => e._id === emoji.id);
return emoji.prepareUpdate(e => {
Object.assign(e, newEmoji);
@ -48,12 +57,12 @@ const updateEmojis = async ({ update = [], remove = [], allRecords }) => {
}
if (remove && remove.length) {
emojisToDelete = allRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
emojisToDelete = emojisToDelete.map(emoji => emoji.prepareDestroyPermanently());
const filterEmojisToDelete = allRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
emojisToDelete = filterEmojisToDelete.map(emoji => emoji.prepareDestroyPermanently());
}
try {
await db.action(async () => {
await db.write(async () => {
await db.batch(...emojisToCreate, ...emojisToUpdate, ...emojisToDelete);
});
return true;
@ -66,26 +75,34 @@ export async function setCustomEmojis() {
const db = database.active;
const emojisCollection = db.get('custom_emojis');
const allEmojis = await emojisCollection.query().fetch();
const parsed = allEmojis.reduce((ret, item) => {
ret[item.name] = {
name: item.name,
extension: item.extension
};
item.aliases.forEach(alias => {
ret[alias] = {
const parsed = allEmojis.reduce((ret: ICustomEmojis, item) => {
if (item.name) {
ret[item.name] = {
name: item.name,
extension: item.extension
};
});
}
if (item.aliases) {
item.aliases.forEach(alias => {
if (item.name) {
ret[alias] = {
name: item.name,
extension: item.extension
};
}
});
}
return ret;
}, {});
reduxStore.dispatch(setCustomEmojisAction(parsed));
}
export function getCustomEmojis() {
return new Promise(async resolve => {
return new Promise<void>(async resolve => {
try {
const serverVersion = reduxStore.getState().server.version;
const serverVersion = reduxStore.getState().server.version as string;
const db = database.active;
const emojisCollection = db.get('custom_emojis');
const allRecords = await emojisCollection.query().fetch();
@ -94,10 +111,11 @@ export function getCustomEmojis() {
// if server version is lower than 0.75.0, fetches from old api
if (compareServerVersion(serverVersion, 'lowerThan', '0.75.0')) {
// RC 0.61.0
const result = await this.sdk.get('emoji-custom');
// @ts-ignore
const result = await sdk.get('emoji-custom');
// @ts-ignore
let { emojis } = result;
emojis = emojis.filter(emoji => !updatedSince || emoji._updatedAt > updatedSince);
emojis = emojis.filter((emoji: TCustomEmojiModel) => !updatedSince || emoji._updatedAt.toISOString() > updatedSince);
const changedEmojis = await updateEmojis({ update: emojis, allRecords });
// `setCustomEmojis` is fired on selectServer
@ -106,28 +124,28 @@ export function getCustomEmojis() {
setCustomEmojis();
}
return resolve();
} else {
const params = {};
if (updatedSince) {
params.updatedSince = updatedSince;
}
}
const params: { updatedSince: string } = { updatedSince: '' };
if (updatedSince) {
params.updatedSince = updatedSince;
}
// RC 0.75.0
const result = await this.sdk.get('emoji-custom.list', params);
// RC 0.75.0
const result = await sdk.get('emoji-custom.list', params);
if (!result.success) {
return resolve();
}
if (!result.success) {
return resolve();
}
const { emojis } = result;
const { update, remove } = emojis;
const changedEmojis = await updateEmojis({ update, remove, allRecords });
const { emojis } = result;
// @ts-ignore
const { update, remove } = emojis;
const changedEmojis = await updateEmojis({ update, remove, allRecords });
// `setCustomEmojis` is fired on selectServer
// We run it again only if emojis were changed
if (changedEmojis) {
setCustomEmojis();
}
// `setCustomEmojis` is fired on selectServer
// We run it again only if emojis were changed
if (changedEmojis) {
setCustomEmojis();
}
} catch (e) {
log(e);

View File

@ -1,4 +1,5 @@
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import Model from '@nozbe/watermelondb/Model';
import database from '../database';
import { getRoleById } from '../database/services/Role';
@ -6,8 +7,10 @@ import log from '../../utils/log';
import { store as reduxStore } from '../auxStore';
import { removeRoles, setRoles as setRolesAction, updateRoles } from '../../actions/roles';
import protectedFunction from './helpers/protectedFunction';
import { TRoleModel } from '../../definitions';
import sdk from '../rocketchat/services/sdk';
export async function setRoles() {
export async function setRoles(): Promise<void> {
const db = database.active;
const rolesCollection = db.get('roles');
const allRoles = await rolesCollection.query().fetch();
@ -15,7 +18,17 @@ export async function setRoles() {
reduxStore.dispatch(setRolesAction(parsed));
}
export async function onRolesChanged(ddpMessage) {
interface IRolesChanged {
fields: {
args: {
type: string;
_id: string;
description: string;
}[];
};
}
export async function onRolesChanged(ddpMessage: IRolesChanged): Promise<void> {
const { type, _id, description } = ddpMessage.fields.args[0];
if (/changed/.test(type)) {
const db = database.active;
@ -42,7 +55,6 @@ export async function onRolesChanged(ddpMessage) {
}
if (/removed/.test(type)) {
const db = database.active;
const rolesCollection = db.get('roles');
try {
const roleRecord = await getRoleById(_id);
if (roleRecord) {
@ -57,12 +69,12 @@ export async function onRolesChanged(ddpMessage) {
}
}
export function getRoles() {
export function getRoles(): Promise<void> {
const db = database.active;
return new Promise(async resolve => {
try {
// RC 0.70.0
const result = await this.sdk.get('roles.list');
const result = await sdk.get('roles.list');
if (!result.success) {
return resolve();
@ -71,18 +83,18 @@ export function getRoles() {
const { roles } = result;
if (roles && roles.length) {
await db.action(async () => {
await db.write(async () => {
const rolesCollections = db.get('roles');
const allRolesRecords = await rolesCollections.query().fetch();
// filter roles
let rolesToCreate = roles.filter(i1 => !allRolesRecords.find(i2 => i1._id === i2.id));
let rolesToUpdate = allRolesRecords.filter(i1 => roles.find(i2 => i1.id === i2._id));
const filteredRolesToCreate = roles.filter(i1 => !allRolesRecords.find(i2 => i1._id === i2.id));
const filteredRolesToUpdate = allRolesRecords.filter(i1 => roles.find(i2 => i1.id === i2._id));
// Create
rolesToCreate = rolesToCreate.map(role =>
const rolesToCreate = filteredRolesToCreate.map(role =>
rolesCollections.prepareCreate(
protectedFunction(r => {
protectedFunction((r: TRoleModel) => {
r._raw = sanitizedRaw({ id: role._id }, rolesCollections.schema);
Object.assign(r, role);
})
@ -90,16 +102,16 @@ export function getRoles() {
);
// Update
rolesToUpdate = rolesToUpdate.map(role => {
const rolesToUpdate = filteredRolesToUpdate.map(role => {
const newRole = roles.find(r => r._id === role.id);
return role.prepareUpdate(
protectedFunction(r => {
protectedFunction((r: TRoleModel) => {
Object.assign(r, newRole);
})
);
});
const allRecords = [...rolesToCreate, ...rolesToUpdate];
const allRecords: Model[] = [...rolesToCreate, ...rolesToUpdate];
try {
await db.batch(...allRecords);

View File

@ -1,7 +1,8 @@
import { IRoom } from '../../definitions';
import { getSubscriptionByRoomId } from '../database/services/Subscription';
import RocketChat from '../rocketchat';
const getRoomInfo = async rid => {
const getRoomInfo = async (rid: string): Promise<Pick<IRoom, 'rid' | 'name' | 'fname' | 't'> | null> => {
let result;
result = await getSubscriptionByRoomId(rid);
if (result) {

View File

@ -6,8 +6,12 @@ export default function (updatedSince: Date) {
if (updatedSince) {
const updatedDate = updatedSince.toISOString();
// TODO: missing definitions from server
// @ts-ignore
return Promise.all([sdk.get('subscriptions.get', { updatedDate }), sdk.get('rooms.get', { updatedDate })]);
return Promise.all([
// @ts-ignore
sdk.get('subscriptions.get', { updatedSince: updatedDate }),
// @ts-ignore
sdk.get('rooms.get', { updatedSince: updatedDate })
]);
}
// TODO: missing definitions from server
// @ts-ignore

View File

@ -1,14 +1,16 @@
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { Q } from '@nozbe/watermelondb';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { addSettings, clearSettings } from '../../actions/settings';
import RocketChat from '../rocketchat';
import { store as reduxStore } from '../auxStore';
import settings from '../../constants/settings';
import log from '../../utils/log';
import database from '../database';
import fetch from '../../utils/fetch';
import { DEFAULT_AUTO_LOCK } from '../../constants/localAuthentication';
import settings from '../../constants/settings';
import { IPreparedSettings, ISettingsIcon } from '../../definitions';
import fetch from '../../utils/fetch';
import log from '../../utils/log';
import { store as reduxStore } from '../auxStore';
import database from '../database';
import RocketChat from '../rocketchat';
import sdk from '../rocketchat/services/sdk';
import protectedFunction from './helpers/protectedFunction';
const serverInfoKeys = [
@ -41,7 +43,7 @@ const loginSettings = [
'Accounts_Iframe_api_method'
];
const serverInfoUpdate = async (serverInfo, iconSetting) => {
const serverInfoUpdate = async (serverInfo: IPreparedSettings[], iconSetting: ISettingsIcon) => {
const serversDB = database.servers;
const serverId = reduxStore.getState().server.server;
const serversCollection = serversDB.get('servers');
@ -73,7 +75,7 @@ const serverInfoUpdate = async (serverInfo, iconSetting) => {
return { ...allSettings, autoLockTime: DEFAULT_AUTO_LOCK };
}
// if Force_Screen_Lock_After > 0 and forceScreenLock is enabled, use it
if (setting.valueAsNumber > 0 && forceScreenLock) {
if (setting.valueAsNumber && setting.valueAsNumber > 0 && forceScreenLock) {
return { ...allSettings, autoLockTime: setting.valueAsNumber };
}
}
@ -91,7 +93,7 @@ const serverInfoUpdate = async (serverInfo, iconSetting) => {
info = { ...info, iconURL };
}
await serversDB.action(async () => {
await serversDB.write(async () => {
try {
await server.update(record => {
Object.assign(record, info);
@ -102,7 +104,7 @@ const serverInfoUpdate = async (serverInfo, iconSetting) => {
});
};
export async function getLoginSettings({ server }) {
export async function getLoginSettings({ server }: { server: string }): Promise<void> {
try {
const settingsParams = JSON.stringify(loginSettings);
const result = await fetch(`${server}/api/v1/settings.public?query={"_id":{"$in":${settingsParams}}}`).then(response =>
@ -111,14 +113,14 @@ export async function getLoginSettings({ server }) {
if (result.success && result.settings.length) {
reduxStore.dispatch(clearSettings());
reduxStore.dispatch(addSettings(this.parseSettings(this._prepareSettings(result.settings))));
reduxStore.dispatch(addSettings(RocketChat.parseSettings(RocketChat._prepareSettings(result.settings))));
}
} catch (e) {
log(e);
}
}
export async function setSettings() {
export async function setSettings(): Promise<void> {
const db = database.active;
const settingsCollection = db.get('settings');
const settingsRecords = await settingsCollection.query().fetch();
@ -133,17 +135,19 @@ export async function setSettings() {
reduxStore.dispatch(addSettings(RocketChat.parseSettings(parsed.slice(0, parsed.length))));
}
export function subscribeSettings() {
export function subscribeSettings(): void {
return RocketChat.subscribe('stream-notify-all', 'public-settings-changed');
}
export default async function () {
type IData = ISettingsIcon | IPreparedSettings;
export default async function (): Promise<void> {
try {
const db = database.active;
const settingsParams = Object.keys(settings).filter(key => !loginSettings.includes(key));
// RC 0.60.0
const result = await fetch(
`${this.sdk.client.host}/api/v1/settings.public?query={"_id":{"$in":${JSON.stringify(settingsParams)}}}&count=${
`${sdk.current.client.host}/api/v1/settings.public?query={"_id":{"$in":${JSON.stringify(settingsParams)}}}&count=${
settingsParams.length
}`
).then(response => response.json());
@ -151,33 +155,32 @@ export default async function () {
if (!result.success) {
return;
}
const data = result.settings || [];
const filteredSettings = this._prepareSettings(data);
const data: IData[] = result.settings || [];
const filteredSettings: IPreparedSettings[] = RocketChat._prepareSettings(data);
const filteredSettingsIds = filteredSettings.map(s => s._id);
reduxStore.dispatch(addSettings(this.parseSettings(filteredSettings)));
reduxStore.dispatch(addSettings(RocketChat.parseSettings(filteredSettings)));
// filter server info
const serverInfo = filteredSettings.filter(i1 => serverInfoKeys.includes(i1._id));
const iconSetting = data.find(item => item._id === 'Assets_favicon_512');
const iconSetting = data.find(icon => icon._id === 'Assets_favicon_512');
try {
await serverInfoUpdate(serverInfo, iconSetting);
await serverInfoUpdate(serverInfo, iconSetting as ISettingsIcon);
} catch {
// Server not found
}
await db.action(async () => {
await db.write(async () => {
const settingsCollection = db.get('settings');
const allSettingsRecords = await settingsCollection.query(Q.where('id', Q.oneOf(filteredSettingsIds))).fetch();
// filter settings
let settingsToCreate = filteredSettings.filter(i1 => !allSettingsRecords.find(i2 => i1._id === i2.id));
let settingsToUpdate = allSettingsRecords.filter(i1 => filteredSettings.find(i2 => i1.id === i2._id));
const settingsToCreate = filteredSettings.filter(i1 => !allSettingsRecords.find(i2 => i1._id === i2.id));
const settingsToUpdate = allSettingsRecords.filter(i1 => filteredSettings.find(i2 => i1.id === i2._id));
// Create
settingsToCreate = settingsToCreate.map(setting =>
const settingsToCreateMapped = settingsToCreate.map(setting =>
settingsCollection.prepareCreate(
protectedFunction(s => {
protectedFunction((s: any) => {
s._raw = sanitizedRaw({ id: setting._id }, settingsCollection.schema);
Object.assign(s, setting);
})
@ -185,16 +188,16 @@ export default async function () {
);
// Update
settingsToUpdate = settingsToUpdate.map(setting => {
const settingsToUpdateMapped = settingsToUpdate.map(setting => {
const newSetting = filteredSettings.find(s => s._id === setting.id);
return setting.prepareUpdate(
protectedFunction(s => {
protectedFunction((s: any) => {
Object.assign(s, newSetting);
})
);
});
const allRecords = [...settingsToCreate, ...settingsToUpdate];
const allRecords = [...settingsToCreateMapped, ...settingsToUpdateMapped];
try {
await db.batch(...allRecords);

View File

@ -1,6 +1,7 @@
import RocketChat from '../rocketchat';
import { IMessage } from '../../definitions';
const getSingleMessage = (messageId: string) =>
const getSingleMessage = (messageId: string): Promise<IMessage> =>
new Promise(async (resolve, reject) => {
try {
const result = await RocketChat.getSingleMessage(messageId);

View File

@ -1,71 +0,0 @@
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import database from '../database';
import log from '../../utils/log';
import protectedFunction from './helpers/protectedFunction';
export default function () {
const db = database.active;
return new Promise(async resolve => {
try {
// RC 0.60.2
const result = await this.sdk.get('commands.list');
if (!result.success) {
console.log(result);
return resolve();
}
const { commands } = result;
if (commands && commands.length) {
await db.action(async () => {
const slashCommandsCollection = db.get('slash_commands');
const allSlashCommandsRecords = await slashCommandsCollection.query().fetch();
// filter slash commands
let slashCommandsToCreate = commands.filter(i1 => !allSlashCommandsRecords.find(i2 => i1.command === i2.id));
let slashCommandsToUpdate = allSlashCommandsRecords.filter(i1 => commands.find(i2 => i1.id === i2.command));
let slashCommandsToDelete = allSlashCommandsRecords.filter(
i1 => !slashCommandsToCreate.find(i2 => i2.command === i1.id) && !slashCommandsToUpdate.find(i2 => i2.id === i1.id)
);
// Create
slashCommandsToCreate = slashCommandsToCreate.map(command =>
slashCommandsCollection.prepareCreate(
protectedFunction(s => {
s._raw = sanitizedRaw({ id: command.command }, slashCommandsCollection.schema);
Object.assign(s, command);
})
)
);
// Update
slashCommandsToUpdate = slashCommandsToUpdate.map(command => {
const newCommand = commands.find(s => s.command === command.id);
return command.prepareUpdate(
protectedFunction(s => {
Object.assign(s, newCommand);
})
);
});
// Delete
slashCommandsToDelete = slashCommandsToDelete.map(command => command.prepareDestroyPermanently());
const allRecords = [...slashCommandsToCreate, ...slashCommandsToUpdate, ...slashCommandsToDelete];
try {
await db.batch(...allRecords);
} catch (e) {
log(e);
}
return allRecords.length;
});
}
} catch (e) {
log(e);
return resolve();
}
});
}

View File

@ -0,0 +1,78 @@
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import database from '../database';
import log from '../../utils/log';
import protectedFunction from './helpers/protectedFunction';
import { ISlashCommandResult, TSlashCommandModel } from '../../definitions';
import sdk from '../rocketchat/services/sdk';
export default function getSlashCommands() {
const db = database.active;
return new Promise<void>(async resolve => {
try {
// RC 0.60.2
// @ts-ignore
const result = await sdk.get('commands.list');
if (!result.success) {
return resolve();
}
// @ts-ignore
const { commands } = result;
if (commands && commands.length) {
await db.write(async () => {
const slashCommandsCollection = db.get('slash_commands');
const allSlashCommandsRecords = await slashCommandsCollection.query().fetch();
// filter slash commands
const filteredSlashCommandsToCreate = commands.filter(
(i1: ISlashCommandResult) => !allSlashCommandsRecords.find(i2 => i1.command === i2.id)
);
const filteredSlashCommandsToUpdate = allSlashCommandsRecords.filter(i1 =>
commands.find((i2: ISlashCommandResult) => i1.id === i2.command)
);
const filteredSlashCommandsToDelete = allSlashCommandsRecords.filter(
i1 =>
!filteredSlashCommandsToCreate.find((i2: ISlashCommandResult) => i2.command === i1.id) &&
!filteredSlashCommandsToUpdate.find(i2 => i2.id === i1.id)
);
// Create
const slashCommandsToCreate = filteredSlashCommandsToCreate.map((command: ISlashCommandResult) =>
slashCommandsCollection.prepareCreate(
protectedFunction((s: TSlashCommandModel) => {
s._raw = sanitizedRaw({ id: command.command }, slashCommandsCollection.schema);
Object.assign(s, command);
})
)
);
// Update
const slashCommandsToUpdate = filteredSlashCommandsToUpdate.map(command => {
const newCommand = commands.find((s: ISlashCommandResult) => s.command === command.id);
return command.prepareUpdate(
protectedFunction((s: TSlashCommandModel) => {
Object.assign(s, newCommand);
})
);
});
// Delete
const slashCommandsToDelete = filteredSlashCommandsToDelete.map(command => command.prepareDestroyPermanently());
const allRecords = [...slashCommandsToCreate, ...slashCommandsToUpdate, ...slashCommandsToDelete];
try {
await db.batch(...allRecords);
} catch (e) {
log(e);
}
return allRecords.length;
});
}
} catch (e) {
log(e);
return resolve();
}
});
}

View File

@ -6,11 +6,12 @@ import { getThreadById } from '../database/services/Thread';
import log from '../../utils/log';
import { Encryption } from '../encryption';
import getSingleMessage from './getSingleMessage';
import { IThread, TThreadModel } from '../../definitions';
const buildThreadName = thread => thread.msg || thread?.attachments?.[0]?.title;
const buildThreadName = (thread: IThread): string | undefined => thread.msg || thread?.attachments?.[0]?.title;
const getThreadName = async (rid, tmid, messageId) => {
let tmsg;
const getThreadName = async (rid: string, tmid: string, messageId: string): Promise<string | undefined> => {
let tmsg: string | undefined;
try {
const db = database.active;
const threadCollection = db.get('threads');
@ -18,7 +19,7 @@ const getThreadName = async (rid, tmid, messageId) => {
const threadRecord = await getThreadById(tmid);
if (threadRecord) {
tmsg = buildThreadName(threadRecord);
await db.action(async () => {
await db.write(async () => {
await messageRecord?.update(m => {
m.tmsg = tmsg;
});
@ -27,11 +28,11 @@ const getThreadName = async (rid, tmid, messageId) => {
let thread = await getSingleMessage(tmid);
thread = await Encryption.decryptMessage(thread);
tmsg = buildThreadName(thread);
await db.action(async () => {
await db.write(async () => {
await db.batch(
threadCollection?.prepareCreate(t => {
threadCollection?.prepareCreate((t: TThreadModel) => {
t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema);
t.subscription.id = rid;
if (t.subscription) t.subscription.id = rid;
Object.assign(t, thread);
}),
messageRecord?.prepareUpdate(m => {

View File

@ -1,14 +1,17 @@
import { InteractionManager } from 'react-native';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { IActiveUsers } from '../../reducers/activeUsers';
import { compareServerVersion } from '../utils';
import { store as reduxStore } from '../auxStore';
import { setActiveUsers } from '../../actions/activeUsers';
import { setUser } from '../../actions/login';
import database from '../database';
import { IRocketChat, IUser } from '../../definitions';
import sdk from '../rocketchat/services/sdk';
export function subscribeUsersPresence() {
const serverVersion = reduxStore.getState().server.version;
export function subscribeUsersPresence(this: IRocketChat) {
const serverVersion = reduxStore.getState().server.version as string;
// if server is lower than 1.1.0
if (compareServerVersion(serverVersion, 'lowerThan', '1.1.0')) {
@ -17,22 +20,22 @@ export function subscribeUsersPresence() {
this.activeUsersSubTimeout = false;
}
this.activeUsersSubTimeout = setTimeout(() => {
this.sdk.subscribe('activeUsers');
sdk.subscribe('activeUsers');
}, 5000);
} else if (compareServerVersion(serverVersion, 'lowerThan', '4.1.0')) {
this.sdk.subscribe('stream-notify-logged', 'user-status');
sdk.subscribe('stream-notify-logged', 'user-status');
}
// RC 0.49.1
this.sdk.subscribe('stream-notify-logged', 'updateAvatar');
sdk.subscribe('stream-notify-logged', 'updateAvatar');
// RC 0.58.0
this.sdk.subscribe('stream-notify-logged', 'Users:NameChanged');
sdk.subscribe('stream-notify-logged', 'Users:NameChanged');
}
let ids = [];
let ids: string[] = [];
export default async function getUsersPresence() {
const serverVersion = reduxStore.getState().server.version;
const serverVersion = reduxStore.getState().server.version as string;
const { user: loggedUser } = reduxStore.getState().login;
// if server is greather than or equal 1.1.0
@ -51,17 +54,17 @@ export default async function getUsersPresence() {
try {
// RC 1.1.0
const result = await this.sdk.get('users.presence', params);
const result = (await sdk.get('users.presence' as any, params as any)) as any;
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.1.0')) {
this.sdk.subscribeRaw('stream-user-presence', ['', { added: ids }]);
sdk.subscribeRaw('stream-user-presence', ['', { added: ids }]);
}
if (result.success) {
const { users } = result;
const activeUsers = ids.reduce((ret, id) => {
const user = users.find(u => u._id === id) ?? { _id: id, status: 'offline' };
const activeUsers = ids.reduce((ret: IActiveUsers, id) => {
const user = users.find((u: IUser) => u._id === id) ?? { _id: id, status: 'offline' };
const { _id, status, statusText } = user;
if (loggedUser && loggedUser.id === _id) {
@ -78,17 +81,17 @@ export default async function getUsersPresence() {
const db = database.active;
const userCollection = db.get('users');
users.forEach(async user => {
users.forEach(async (user: IUser) => {
try {
const userRecord = await userCollection.find(user._id);
await db.action(async () => {
await db.write(async () => {
await userRecord.update(u => {
Object.assign(u, user);
});
});
} catch (e) {
// User not found
await db.action(async () => {
await db.write(async () => {
await userCollection.create(u => {
u._raw = sanitizedRaw({ id: user._id }, userCollection.schema);
Object.assign(u, user);
@ -103,11 +106,11 @@ export default async function getUsersPresence() {
}
}
let usersTimer = null;
export function getUserPresence(uid) {
let usersTimer: number | null = null;
export function getUserPresence(uid: string) {
if (!usersTimer) {
usersTimer = setTimeout(() => {
getUsersPresence.call(this);
getUsersPresence();
usersTimer = null;
}, 2000);
}

View File

@ -1,15 +1,18 @@
import { Q } from '@nozbe/watermelondb';
import { IServerSubscriptionItem, IServerRoomItem } from '../../../definitions';
import database from '../../database';
export default async (subscriptions = [], rooms = []) => {
export default async (subscriptions: IServerSubscriptionItem[], rooms: IServerRoomItem[]) => {
let sub = subscriptions;
let room = rooms;
try {
const db = database.active;
const subCollection = db.get('subscriptions');
const roomIds = rooms.filter(r => !subscriptions.find(s => s.rid === r._id)).map(r => r._id);
let existingSubs = await subCollection.query(Q.where('rid', Q.oneOf(roomIds))).fetch();
existingSubs = existingSubs.map(s => ({
const existingSubs = await subCollection.query(Q.where('rid', Q.oneOf(roomIds))).fetch();
const mappedExistingSubs = existingSubs.map(s => ({
_id: s._id,
f: s.f,
t: s.t,
@ -55,11 +58,12 @@ export default async (subscriptions = [], rooms = []) => {
E2EKey: s.E2EKey,
avatarETag: s.avatarETag
}));
subscriptions = subscriptions.concat(existingSubs);
// Assign
sub = subscriptions.concat(mappedExistingSubs as unknown as IServerSubscriptionItem);
const subsIds = subscriptions.filter(s => !rooms.find(r => s.rid === r._id)).map(s => s._id);
let existingRooms = await subCollection.query(Q.where('id', Q.oneOf(subsIds))).fetch();
existingRooms = existingRooms.map(r => ({
const existingRooms = await subCollection.query(Q.where('id', Q.oneOf(subsIds))).fetch();
const mappedExistingRooms = existingRooms.map(r => ({
_updatedAt: r._updatedAt,
lastMessage: r.lastMessage,
description: r.description,
@ -84,13 +88,14 @@ export default async (subscriptions = [], rooms = []) => {
e2eKeyId: r.e2eKeyId,
avatarETag: r.avatarETag
}));
rooms = rooms.concat(existingRooms);
// Assign
room = rooms.concat(mappedExistingRooms as unknown as IServerRoomItem);
} catch {
// do nothing
}
return {
subscriptions,
rooms
subscriptions: sub,
rooms: room
};
};

View File

@ -1,4 +1,4 @@
export default function (message) {
export default function (message: { type: string; url: string }) {
if (/image/.test(message.type)) {
return { image_url: message.url };
}

View File

@ -5,17 +5,18 @@ import { store as reduxStore } from '../../auxStore';
import { compareServerVersion } from '../../utils';
import findSubscriptionsRooms from './findSubscriptionsRooms';
import normalizeMessage from './normalizeMessage';
import { ISubscription, IServerRoom, IServerSubscription, IServerSubscriptionItem, IServerRoomItem } from '../../../definitions';
// TODO: delete and update
export const merge = (subscription, room) => {
const serverVersion = reduxStore.getState().server.version;
subscription = EJSON.fromJSONValue(subscription);
room = EJSON.fromJSONValue(room);
export const merge = (
subscription: ISubscription | IServerSubscriptionItem,
room?: ISubscription | IServerRoomItem
): ISubscription => {
const serverVersion = reduxStore.getState().server.version as string;
subscription = EJSON.fromJSONValue(subscription) as ISubscription;
if (!subscription) {
return;
}
if (room) {
room = EJSON.fromJSONValue(room) as ISubscription;
if (room._updatedAt) {
subscription.lastMessage = normalizeMessage(room.lastMessage);
subscription.description = room.description;
@ -28,15 +29,21 @@ export const merge = (subscription, room) => {
subscription.usernames = room.usernames;
subscription.uids = room.uids;
}
if (compareServerVersion(serverVersion, 'lowerThan', '3.7.0')) {
const updatedAt = room?._updatedAt ? new Date(room._updatedAt) : null;
const lastMessageTs = subscription?.lastMessage?.ts ? new Date(subscription.lastMessage.ts) : null;
// @ts-ignore
// If all parameters are null it will return zero, if only one is null it will return its timestamp only.
// "It works", but it's not the best solution. It does not accept "Date" as a parameter, but it works.
subscription.roomUpdatedAt = Math.max(updatedAt, lastMessageTs);
} else {
// https://github.com/RocketChat/Rocket.Chat/blob/develop/app/ui-sidenav/client/roomList.js#L180
const lastRoomUpdate = room.lm || subscription.ts || subscription._updatedAt;
// @ts-ignore Same as above scenario
subscription.roomUpdatedAt = subscription.lr
? Math.max(new Date(subscription.lr), new Date(lastRoomUpdate))
? // @ts-ignore Same as above scenario
Math.max(new Date(subscription.lr), new Date(lastRoomUpdate))
: lastRoomUpdate;
}
subscription.ro = room.ro;
@ -76,7 +83,7 @@ export const merge = (subscription, room) => {
}
if (!subscription.name) {
subscription.name = subscription.fname;
subscription.name = subscription.fname as string;
}
if (!subscription.autoTranslate) {
@ -88,29 +95,24 @@ export const merge = (subscription, room) => {
return subscription;
};
export default async (subscriptions = [], rooms = []) => {
if (subscriptions.update) {
subscriptions = subscriptions.update;
rooms = rooms.update;
}
export default async (serverSubscriptions: IServerSubscription, serverRooms: IServerRoom): Promise<ISubscription[]> => {
const subscriptions = serverSubscriptions.update;
const rooms = serverRooms.update;
// Find missing rooms/subscriptions on local database
({ subscriptions, rooms } = await findSubscriptionsRooms(subscriptions, rooms));
const findData = await findSubscriptionsRooms(subscriptions, rooms);
// Merge each subscription into a room
subscriptions = subscriptions.map(s => {
const index = rooms.findIndex(({ _id }) => _id === s.rid);
const mergedSubscriptions = findData.subscriptions.map(subscription => {
const index = rooms.findIndex(({ _id }) => _id === subscription.rid);
// Room not found
if (index < 0) {
return merge(s);
return merge(subscription);
}
const [room] = rooms.splice(index, 1);
return merge(s, room);
return merge(subscription, room);
});
// Decrypt all subscriptions missing decryption
subscriptions = await Encryption.decryptSubscriptions(subscriptions);
const decryptedSubscriptions = (await Encryption.decryptSubscriptions(mergedSubscriptions)) as ISubscription[];
return {
subscriptions,
rooms
};
return decryptedSubscriptions;
};

View File

@ -5,32 +5,41 @@ import log from '../../utils/log';
import { getMessageById } from '../database/services/Message';
import { generateLoadMoreId } from '../utils';
import updateMessages from './updateMessages';
import { IMessage, TMessageModel } from '../../definitions';
import sdk from '../rocketchat/services/sdk';
import roomTypeToApiType, { RoomTypes } from '../rocketchat/methods/roomTypeToApiType';
const COUNT = 50;
async function load({ rid: roomId, latest, t }) {
let params = { roomId, count: COUNT };
async function load({ rid: roomId, latest, t }: { rid: string; latest?: string; t: RoomTypes }) {
let params = { roomId, count: COUNT } as { roomId: string; count: number; latest?: string };
if (latest) {
params = { ...params, latest: new Date(latest).toISOString() };
}
const apiType = this.roomTypeToApiType(t);
const apiType = roomTypeToApiType(t);
if (!apiType) {
return [];
}
// RC 0.48.0
const data = await this.sdk.get(`${apiType}.history`, params);
// @ts-ignore
const data: any = await sdk.get(`${apiType}.history`, params);
if (!data || data.status === 'error') {
return [];
}
return data.messages;
}
export default function loadMessagesForRoom(args) {
export default function loadMessagesForRoom(args: {
rid: string;
t: RoomTypes;
latest: string;
loaderItem: TMessageModel;
}): Promise<IMessage[] | []> {
return new Promise(async (resolve, reject) => {
try {
const data = await load.call(this, args);
const data = await load(args);
if (data?.length) {
const lastMessage = data[data.length - 1];
const lastMessageRecord = await getMessageById(lastMessage._id);
@ -46,9 +55,8 @@ export default function loadMessagesForRoom(args) {
}
await updateMessages({ rid: args.rid, update: data, loaderItem: args.loaderItem });
return resolve(data);
} else {
return resolve([]);
}
return resolve([]);
} catch (e) {
log(e);
reject(e);

View File

@ -1,44 +0,0 @@
import database from '../database';
import log from '../../utils/log';
import updateMessages from './updateMessages';
const getLastUpdate = async rid => {
try {
const db = database.active;
const subsCollection = db.get('subscriptions');
const sub = await subsCollection.find(rid);
return sub.lastOpen.toISOString();
} catch (e) {
// Do nothing
}
return null;
};
async function load({ rid: roomId, lastOpen }) {
let lastUpdate;
if (lastOpen) {
lastUpdate = new Date(lastOpen).toISOString();
} else {
lastUpdate = await getLastUpdate(roomId);
}
// RC 0.60.0
const { result } = await this.sdk.get('chat.syncMessages', { roomId, lastUpdate });
return result;
}
export default function loadMissedMessages(args) {
return new Promise(async (resolve, reject) => {
try {
const data = await load.call(this, { rid: args.rid, lastOpen: args.lastOpen });
if (data) {
const { updated, deleted } = data;
await updateMessages({ rid: args.rid, update: updated, remove: deleted });
}
resolve();
} catch (e) {
log(e);
reject(e);
}
});
}

View File

@ -0,0 +1,47 @@
import { ILastMessage } from '../../definitions';
import log from '../../utils/log';
import database from '../database';
import sdk from '../rocketchat/services/sdk';
import updateMessages from './updateMessages';
const getLastUpdate = async (rid: string) => {
try {
const db = database.active;
const subsCollection = db.get('subscriptions');
const sub = await subsCollection.find(rid);
return sub.lastOpen?.toISOString();
} catch (e) {
// Do nothing
}
return null;
};
async function load({ rid: roomId, lastOpen }: { rid: string; lastOpen: string }) {
let lastUpdate;
if (lastOpen) {
lastUpdate = new Date(lastOpen).toISOString();
} else {
lastUpdate = await getLastUpdate(roomId);
}
// RC 0.60.0
// @ts-ignore // this method dont have type
const { result } = await sdk.get('chat.syncMessages', { roomId, lastUpdate });
return result;
}
export default function loadMissedMessages(args: { rid: string; lastOpen: string }): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
const data = await load({ rid: args.rid, lastOpen: args.lastOpen });
if (data) {
const { updated, deleted }: { updated: ILastMessage[]; deleted: ILastMessage[] } = data;
// @ts-ignore // TODO: remove loaderItem obligatoriness
await updateMessages({ rid: args.rid, update: updated, remove: deleted });
}
resolve();
} catch (e) {
log(e);
reject(e);
}
});
}

View File

@ -7,13 +7,22 @@ import { getMessageById } from '../database/services/Message';
import { MESSAGE_TYPE_LOAD_NEXT_CHUNK } from '../../constants/messageTypeLoad';
import { generateLoadMoreId } from '../utils';
import updateMessages from './updateMessages';
import { IMessage, TMessageModel } from '../../definitions';
import RocketChat from '../rocketchat';
const COUNT = 50;
export default function loadNextMessages(args) {
interface ILoadNextMessages {
rid: string;
ts: string;
tmid: string;
loaderItem: TMessageModel;
}
export default function loadNextMessages(args: ILoadNextMessages): Promise<IMessage | []> {
return new Promise(async (resolve, reject) => {
try {
const data = await this.methodCallWrapper('loadNextMessages', args.rid, args.ts, COUNT);
const data = await RocketChat.methodCallWrapper('loadNextMessages', args.rid, args.ts, COUNT);
let messages = EJSON.fromJSONValue(data?.messages);
messages = orderBy(messages, 'ts');
if (messages?.length) {
@ -31,9 +40,8 @@ export default function loadNextMessages(args) {
}
await updateMessages({ rid: args.rid, update: messages, loaderItem: args.loaderItem });
return resolve(messages);
} else {
return resolve([]);
}
return resolve([]);
} catch (e) {
log(e);
reject(e);

View File

@ -1,18 +1,22 @@
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
import { FetchBlobResponse, StatefulPromise } from 'rn-fetch-blob';
import isEmpty from 'lodash/isEmpty';
import FileUpload from '../../utils/fileUpload';
import database from '../database';
import log from '../../utils/log';
import { IUpload, IUser, TUploadModel } from '../../definitions';
import { IFileUpload } from '../../utils/fileUpload/interfaces';
const uploadQueue = {};
const uploadQueue: { [index: string]: StatefulPromise<FetchBlobResponse> } = {};
export function isUploadActive(path) {
export function isUploadActive(path: string): boolean {
return !!uploadQueue[path];
}
export async function cancelUpload(item) {
if (uploadQueue[item.path]) {
export async function cancelUpload(item: TUploadModel): Promise<void> {
if (!isEmpty(uploadQueue[item.path])) {
try {
await uploadQueue[item.path].cancel();
} catch {
@ -20,7 +24,7 @@ export async function cancelUpload(item) {
}
try {
const db = database.active;
await db.action(async () => {
await db.write(async () => {
await item.destroyPermanently();
});
} catch (e) {
@ -30,7 +34,13 @@ export async function cancelUpload(item) {
}
}
export function sendFileMessage(rid, fileInfo, tmid, server, user) {
export function sendFileMessage(
rid: string,
fileInfo: IUpload,
tmid: string,
server: string,
user: IUser
): Promise<FetchBlobResponse | void> {
return new Promise(async (resolve, reject) => {
try {
const { id, token } = user;
@ -41,16 +51,18 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
const db = database.active;
const uploadsCollection = db.get('uploads');
let uploadRecord;
let uploadRecord: TUploadModel;
try {
uploadRecord = await uploadsCollection.find(fileInfo.path);
} catch (error) {
try {
await db.action(async () => {
await db.write(async () => {
uploadRecord = await uploadsCollection.create(u => {
u._raw = sanitizedRaw({ id: fileInfo.path }, uploadsCollection.schema);
Object.assign(u, fileInfo);
u.subscription.id = rid;
if (u.subscription) {
u.subscription.id = rid;
}
});
});
} catch (e) {
@ -58,7 +70,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
}
}
const formData = [];
const formData: IFileUpload[] = [];
formData.push({
name: 'file',
type: fileInfo.type,
@ -89,9 +101,9 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
uploadQueue[fileInfo.path] = FileUpload.fetch('POST', uploadUrl, headers, formData);
uploadQueue[fileInfo.path].uploadProgress(async (loaded, total) => {
uploadQueue[fileInfo.path].uploadProgress(async (loaded: number, total: number) => {
try {
await db.action(async () => {
await db.write(async () => {
await uploadRecord.update(u => {
u.progress = Math.floor((loaded / total) * 100);
});
@ -105,7 +117,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
if (response.respInfo.status >= 200 && response.respInfo.status < 400) {
// If response is all good...
try {
await db.action(async () => {
await db.write(async () => {
await uploadRecord.destroyPermanently();
});
resolve(response);
@ -114,7 +126,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
}
} else {
try {
await db.action(async () => {
await db.write(async () => {
await uploadRecord.update(u => {
u.error = true;
});
@ -132,7 +144,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
uploadQueue[fileInfo.path].catch(async error => {
try {
await db.action(async () => {
await db.write(async () => {
await uploadRecord.update(u => {
u.error = true;
});

View File

@ -1,4 +1,5 @@
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { Model } from '@nozbe/watermelondb';
import messagesStatus from '../../constants/messagesStatus';
import database from '../database';
@ -6,12 +7,14 @@ import log from '../../utils/log';
import random from '../../utils/random';
import { Encryption } from '../encryption';
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../encryption/constants';
import { IMessage, IUser, TMessageModel } from '../../definitions';
import sdk from '../rocketchat/services/sdk';
const changeMessageStatus = async (id, tmid, status, message) => {
const changeMessageStatus = async (id: string, status: number, tmid?: string, message?: IMessage) => {
const db = database.active;
const msgCollection = db.get('messages');
const threadMessagesCollection = db.get('thread_messages');
const successBatch = [];
const successBatch: Model[] = [];
const messageRecord = await msgCollection.find(id);
successBatch.push(
messageRecord.prepareUpdate(m => {
@ -37,7 +40,7 @@ const changeMessageStatus = async (id, tmid, status, message) => {
}
try {
await db.action(async () => {
await db.write(async () => {
await db.batch(...successBatch);
});
} catch (error) {
@ -45,48 +48,44 @@ const changeMessageStatus = async (id, tmid, status, message) => {
}
};
export async function sendMessageCall(message) {
export async function sendMessageCall(message: any) {
const { _id, tmid } = message;
try {
const sdk = this.shareSDK || this.sdk;
// RC 0.60.0
// @ts-ignore
const result = await sdk.post('chat.sendMessage', { message });
if (result.success) {
return changeMessageStatus(_id, tmid, messagesStatus.SENT, result.message);
// @ts-ignore
return changeMessageStatus(_id, messagesStatus.SENT, tmid, result.message);
}
} catch {
// do nothing
}
return changeMessageStatus(_id, tmid, messagesStatus.ERROR);
return changeMessageStatus(_id, messagesStatus.ERROR, tmid);
}
export async function resendMessage(message, tmid) {
export async function resendMessage(message: TMessageModel, tmid?: string) {
const db = database.active;
try {
await db.action(async () => {
await db.write(async () => {
await message.update(m => {
m.status = messagesStatus.TEMP;
});
});
let m = {
const m = await Encryption.encryptMessage({
_id: message.id,
rid: message.subscription.id,
msg: message.msg
};
if (tmid) {
m = {
...m,
tmid
};
}
m = await Encryption.encryptMessage(m);
await sendMessageCall.call(this, m);
rid: message.subscription ? message.subscription.id : '',
msg: message.msg,
...(tmid && { tmid })
} as IMessage);
await sendMessageCall(m);
} catch (e) {
log(e);
}
}
export default async function (rid, msg, tmid, user, tshow) {
export default async function (rid: string, msg: string, tmid: string, user: IUser, tshow?: boolean) {
try {
const db = database.active;
const subsCollection = db.get('subscriptions');
@ -94,19 +93,18 @@ export default async function (rid, msg, tmid, user, tshow) {
const threadCollection = db.get('threads');
const threadMessagesCollection = db.get('thread_messages');
const messageId = random(17);
const batch = [];
const batch: Model[] = [];
let message = {
const message = await Encryption.encryptMessage({
_id: messageId,
rid,
msg,
tmid,
tshow
};
message = await Encryption.encryptMessage(message);
} as IMessage);
const messageDate = new Date();
let tMessageRecord;
let tMessageRecord: TMessageModel;
// If it's replying to a thread
if (tmid) {
@ -116,7 +114,9 @@ export default async function (rid, msg, tmid, user, tshow) {
batch.push(
tMessageRecord.prepareUpdate(m => {
m.tlm = messageDate;
m.tcount += 1;
if (m.tcount) {
m.tcount += 1;
}
})
);
@ -128,7 +128,9 @@ export default async function (rid, msg, tmid, user, tshow) {
batch.push(
threadCollection.prepareCreate(tm => {
tm._raw = sanitizedRaw({ id: tmid }, threadCollection.schema);
tm.subscription.id = rid;
if (tm.subscription) {
tm.subscription.id = rid;
}
tm.tmid = tmid;
tm.msg = tMessageRecord.msg;
tm.ts = tMessageRecord.ts;
@ -147,7 +149,9 @@ export default async function (rid, msg, tmid, user, tshow) {
batch.push(
threadMessagesCollection.prepareCreate(tm => {
tm._raw = sanitizedRaw({ id: messageId }, threadMessagesCollection.schema);
tm.subscription.id = rid;
if (tm.subscription) {
tm.subscription.id = rid;
}
tm.rid = tmid;
tm.msg = msg;
tm.ts = messageDate;
@ -173,7 +177,9 @@ export default async function (rid, msg, tmid, user, tshow) {
batch.push(
msgCollection.prepareCreate(m => {
m._raw = sanitizedRaw({ id: messageId }, msgCollection.schema);
m.subscription.id = rid;
if (m.subscription) {
m.subscription.id = rid;
}
m.msg = msg;
m.ts = messageDate;
m._updatedAt = messageDate;
@ -210,7 +216,7 @@ export default async function (rid, msg, tmid, user, tshow) {
}
try {
await db.action(async () => {
await db.write(async () => {
await db.batch(...batch);
});
} catch (e) {
@ -218,7 +224,7 @@ export default async function (rid, msg, tmid, user, tshow) {
return;
}
await sendMessageCall.call(this, message);
await sendMessageCall(message);
} catch (e) {
log(e);
}

View File

@ -127,8 +127,11 @@ const createOrUpdateSubscription = async (subscription, room) => {
}
}
let tmp = merge(subscription, room);
tmp = await Encryption.decryptSubscription(tmp);
let tmp;
if (subscription) {
tmp = merge(subscription, room);
tmp = await Encryption.decryptSubscription(tmp);
}
let sub;
try {
sub = await subCollection.find(tmp.rid);

View File

@ -13,7 +13,7 @@ import { getSubscriptionByRoomId } from '../database/services/Subscription';
interface IUpdateMessages {
rid: string;
update: IMessage[];
remove: IMessage[];
remove?: IMessage[];
loaderItem?: TMessageModel;
}
@ -105,7 +105,7 @@ export default async function updateMessages({
threadCollection.prepareCreate(
protectedFunction((t: TThreadModel) => {
t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema);
t.subscription.id = sub.id;
if (t.subscription) t.subscription.id = sub.id;
Object.assign(t, thread);
})
)

View File

@ -0,0 +1,16 @@
import RNFetchBlob from 'rn-fetch-blob';
export const getServerTimeSync = async (server: string) => {
try {
const response = await Promise.race([
RNFetchBlob.fetch('GET', `${server}/_timesync`),
new Promise<undefined>(res => setTimeout(res, 2000))
]);
if (response?.data) {
return parseInt(response.data);
}
return null;
} catch {
return null;
}
};

View File

@ -34,10 +34,8 @@ export const createChannel = ({
return sdk.post(type ? 'groups.create' : 'channels.create', params);
};
export const e2eSetUserPublicAndPrivateKeys = (public_key: string, private_key: string): any =>
export const e2eSetUserPublicAndPrivateKeys = (public_key: string, private_key: string) =>
// RC 2.2.0
// TODO: missing definitions from server
// @ts-ignore
sdk.post('e2e.setUserPublicAndPrivateKeys', { public_key, private_key });
export const e2eRequestSubscriptionKeys = (): any =>
@ -221,12 +219,6 @@ export const teamListRoomsOfUser = ({ teamId, userId }: { teamId: string; userId
// @ts-ignore
sdk.get('teams.listRoomsOfUser', { teamId, userId });
export const getTeamInfo = ({ teamId }: { teamId: string }): any =>
// RC 3.13.0
// TODO: missing definitions from server
// @ts-ignore
sdk.get('teams.info', { teamId });
export const convertChannelToTeam = ({ rid, name, type }: { rid: string; name: string; type: 'c' | 'p' }): any => {
const params = {
...(type === 'c'

View File

@ -18,7 +18,7 @@ const methods = {
};
export const compareServerVersion = (
currentServerVersion: string,
currentServerVersion: string | null,
method: keyof typeof methods,
versionToCompare: string
): boolean =>

View File

@ -1,4 +1,5 @@
import { clearActiveUsers, setActiveUsers } from '../actions/activeUsers';
import { UserStatus } from '../definitions/UserStatus';
import { IActiveUsers, initialState } from './activeUsers';
import { mockedStore } from './mockedStore';
@ -8,7 +9,7 @@ describe('test reducer', () => {
expect(state).toEqual(initialState);
});
it('should return modified store after action', () => {
const activeUsers: IActiveUsers = { any: { status: 'online', statusText: 'any' } };
const activeUsers: IActiveUsers = { any: { status: UserStatus.ONLINE, statusText: 'any' } };
mockedStore.dispatch(setActiveUsers(activeUsers));
const state = mockedStore.getState().activeUsers;
expect(state).toEqual({ ...activeUsers });

View File

@ -1,9 +1,9 @@
import { TApplicationActions } from '../definitions';
import { ACTIVE_USERS } from '../actions/actionsTypes';
import { TApplicationActions } from '../definitions';
import { UserStatus } from '../definitions/UserStatus';
type TUserStatus = 'online' | 'offline' | 'away' | 'busy';
export interface IActiveUser {
status: TUserStatus;
status: UserStatus;
statusText: string;
}

View File

@ -1,7 +1,7 @@
import { TActionEnterpriseModules } from '../actions/enterpriseModules';
import { ENTERPRISE_MODULES } from '../actions/actionsTypes';
export type IEnterpriseModules = 'omnichannel-mobile-enterprise' | 'livechat-enterprise';
export type IEnterpriseModules = string;
export const initialState: IEnterpriseModules[] = [];

109
app/reducers/login.test.ts Normal file
View File

@ -0,0 +1,109 @@
import {
loginFailure,
loginRequest,
loginSuccess,
logout,
setLocalAuthenticated,
setLoginServices,
setUser
} from '../actions/login';
import { UserStatus } from '../definitions/UserStatus';
import { initialState } from './login';
import { mockedStore } from './mockedStore';
describe('test selectedUsers reducer', () => {
it('should return initial state', () => {
const state = mockedStore.getState().login;
expect(state).toEqual(initialState);
});
it('should return modified store after loginRequest', () => {
mockedStore.dispatch(loginRequest({ user: 'carlitos@email.com', password: '123456' }));
const state = mockedStore.getState().login;
expect(state).toEqual({ ...initialState, isFetching: true, isAuthenticated: false, failure: false, error: {} });
});
it('should return modified store after loginFailure', () => {
mockedStore.dispatch(loginFailure({ error: 'error' }));
const state = mockedStore.getState().login.error.error;
expect(state).toEqual('error');
});
it('should return modified store after loginSuccess', () => {
const user = {
id: 'ajhsiahsa',
token: 'asdasdasdas',
username: 'carlitos',
name: 'Carlitos',
customFields: {
phonenumber: ''
},
emails: [
{
address: 'carlitos@email.com',
verified: true
}
],
roles: ['user'],
isFromWebView: false,
showMessageInMainThread: false,
enableMessageParserEarlyAdoption: false,
status: UserStatus.ONLINE,
statusText: 'online'
};
mockedStore.dispatch(loginSuccess(user));
const state = mockedStore.getState().login.user;
expect(state).toEqual(user);
});
it('should return modified store after setUser', () => {
const user = {
id: 'ajhsiahsa',
token: 'asdasdasdas',
username: 'carlito',
name: 'Carlitos',
customFields: {
phonenumber: ''
},
emails: [
{
address: 'carlitos@email.com',
verified: true
}
],
roles: ['user'],
isFromWebView: false,
showMessageInMainThread: false,
enableMessageParserEarlyAdoption: false
};
mockedStore.dispatch(setUser(user));
const state = mockedStore.getState().login.user.username;
expect(state).toEqual(user.username);
});
// TODO PREFERENCE REDUCER WITH EMPTY PREFERENCE - NON USED?
// it('should return modified store after setPreference', () => {
// mockedStore.dispatch(setPreference({ showAvatar: true }));
// const state = mockedStore.getState().login;
// console.log(state);
// expect(state).toEqual('error');
// });
it('should return modified store after setLocalAuthenticated', () => {
mockedStore.dispatch(setLocalAuthenticated(true));
const state = mockedStore.getState().login.isLocalAuthenticated;
expect(state).toEqual(true);
});
it('should return modified store after setLoginServices', () => {
mockedStore.dispatch(setLoginServices({ facebook: { clientId: 'xxx' } }));
const state = mockedStore.getState().login.services.facebook.clientId;
expect(state).toEqual('xxx');
});
it('should return modified store after logout', () => {
mockedStore.dispatch(logout());
const state = mockedStore.getState().login;
expect(state).toEqual(initialState);
});
});

View File

@ -1,15 +1,47 @@
import { UserStatus } from '../definitions/UserStatus';
import * as types from '../actions/actionsTypes';
import { TActionsLogin } from '../actions/login';
import { IUser } from '../definitions';
const initialState = {
export interface IUserLogin {
id: string;
token: string;
username: string;
name: string;
language?: string;
status: UserStatus;
statusText: string;
roles: string[];
avatarETag?: string;
isFromWebView: boolean;
showMessageInMainThread: boolean;
enableMessageParserEarlyAdoption: boolean;
emails: Record<string, any>[];
customFields: Record<string, string>;
settings?: Record<string, string>;
}
export interface ILogin {
user: Partial<IUser>;
isLocalAuthenticated: boolean;
isAuthenticated: boolean;
isFetching: boolean;
error: Record<string, any>;
services: Record<string, any>;
failure: boolean;
}
export const initialState: ILogin = {
isLocalAuthenticated: true,
isAuthenticated: false,
isFetching: false,
user: {},
error: {},
services: {}
services: {},
failure: false
};
export default function login(state = initialState, action) {
export default function login(state = initialState, action: TActionsLogin): ILogin {
switch (action.type) {
case types.APP.INIT:
return initialState;
@ -60,13 +92,14 @@ export default function login(state = initialState, action) {
...state,
user: {
...state.user,
settings: {
...state.user.settings,
preferences: {
...state.user.settings.preferences,
...action.preference
}
}
settings: state.user?.settings
? {
...state.user?.settings,
preferences: state.user?.settings?.preferences
? { ...state.user.settings.preferences, ...action.preference }
: { ...action.preference }
}
: { profile: {}, preferences: {} }
}
};
case types.LOGIN.SET_LOCAL_AUTHENTICATED:

58
app/reducers/room.test.ts Normal file
View File

@ -0,0 +1,58 @@
import { closeRoom, deleteRoom, forwardRoom, leaveRoom, removedRoom, subscribeRoom, unsubscribeRoom } from '../actions/room';
import { ERoomType } from '../definitions/ERoomType';
import { mockedStore } from './mockedStore';
import { initialState } from './room';
describe('test room reducer', () => {
it('should return initial state', () => {
const state = mockedStore.getState().room;
expect(state).toEqual(initialState);
});
it('should return modified store after subscribeRoom', () => {
mockedStore.dispatch(subscribeRoom('GENERAL'));
const state = mockedStore.getState().room;
expect(state.rooms).toEqual(['GENERAL']);
});
it('should return empty store after remove unsubscribeRoom', () => {
mockedStore.dispatch(unsubscribeRoom('GENERAL'));
const state = mockedStore.getState().room;
expect(state.rooms).toEqual([]);
});
it('should return initial state after leaveRoom', () => {
mockedStore.dispatch(leaveRoom(ERoomType.c, { rid: ERoomType.c }));
const { rid, isDeleting } = mockedStore.getState().room;
expect(rid).toEqual(ERoomType.c);
expect(isDeleting).toEqual(true);
});
it('should return initial state after deleteRoom', () => {
mockedStore.dispatch(deleteRoom(ERoomType.l, { rid: ERoomType.l }));
const { rid, isDeleting } = mockedStore.getState().room;
expect(rid).toEqual(ERoomType.l);
expect(isDeleting).toEqual(true);
});
it('should return initial state after closeRoom', () => {
mockedStore.dispatch(closeRoom('CLOSING'));
const { rid, isDeleting } = mockedStore.getState().room;
expect(rid).toEqual('CLOSING');
expect(isDeleting).toEqual(true);
});
it('should return initial state after forwardRoom', () => {
const transferData = { roomId: 'FORWARDING' };
mockedStore.dispatch(forwardRoom('FORWARDING', transferData));
const { rid, isDeleting } = mockedStore.getState().room;
expect(rid).toEqual('FORWARDING');
expect(isDeleting).toEqual(true);
});
it('should return loading after call removedRoom', () => {
mockedStore.dispatch(removedRoom());
const { isDeleting } = mockedStore.getState().room;
expect(isDeleting).toEqual(false);
});
});

View File

@ -1,12 +1,21 @@
import { TActionsRoom } from '../actions/room';
import { ROOM } from '../actions/actionsTypes';
const initialState = {
rid: null,
export type IRoomRecord = string[];
export interface IRoom {
rid: string;
isDeleting: boolean;
rooms: IRoomRecord;
}
export const initialState: IRoom = {
rid: '',
isDeleting: false,
rooms: []
};
export default function (state = initialState, action) {
export default function (state = initialState, action: TActionsRoom): IRoom {
switch (action.type) {
case ROOM.SUBSCRIBE:
return {

View File

@ -57,8 +57,7 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) {
}
const [subscriptionsResult, roomsResult] = yield RocketChat.getRooms(roomsUpdatedAt);
const { subscriptions } = yield mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
const subscriptions = yield mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
const db = database.active;
const subCollection = db.get('subscriptions');
const messagesCollection = db.get('messages');

View File

@ -1,7 +1,7 @@
import { createSelector } from 'reselect';
import isEmpty from 'lodash/isEmpty';
import { IApplicationState } from '../definitions';
import { IApplicationState, IUser } from '../definitions';
interface IServices {
facebook: { clientId: string };
@ -13,7 +13,7 @@ interface IServices {
wordpress: { clientId: string; serverURL: string };
}
const getUser = (state: IApplicationState) => {
const getUser = (state: IApplicationState): Partial<IUser> => {
if (!isEmpty(state.share?.user)) {
return state.share.user;
}
@ -23,7 +23,8 @@ const getLoginServices = (state: IApplicationState) => (state.login.services as
const getShowFormLoginSetting = (state: IApplicationState) => (state.settings.Accounts_ShowFormLogin as boolean) || false;
const getIframeEnabledSetting = (state: IApplicationState) => (state.settings.Accounts_iframe_enabled as boolean) || false;
export const getUserSelector = createSelector([getUser], user => user);
// TODO: we need to change 42 files to fix a correct type, i believe is better to do this later
export const getUserSelector = createSelector([getUser], user => user) as any;
export const getShowLoginButton = createSelector(
[getLoginServices, getShowFormLoginSetting, getIframeEnabledSetting],

View File

@ -43,6 +43,7 @@ class FileUpload {
upload.formData.append(item.name, {
// @ts-ignore
uri: item.uri,
// @ts-ignore
type: item.type,
name: item.filename
});

View File

@ -1,7 +1,7 @@
export interface IFileUpload {
name: string;
uri?: string;
type: string;
filename: string;
data: any;
type?: string;
filename?: string;
data?: any;
}

View File

@ -1,12 +1,13 @@
import * as LocalAuthentication from 'expo-local-authentication';
import moment from 'moment';
import RNBootSplash from 'react-native-bootsplash';
import AsyncStorage from '@react-native-community/async-storage';
import { sha256 } from 'js-sha256';
import moment from 'moment';
import UserPreferences from '../lib/userPreferences';
import { store } from '../lib/auxStore';
import database from '../lib/database';
import { getServerTimeSync } from '../lib/rocketchat/services/getServerTimeSync';
import {
ATTEMPTS_KEY,
BIOMETRY_ENABLED_KEY,
@ -21,16 +22,25 @@ import { TServerModel } from '../definitions/IServer';
import EventEmitter from './events';
import { isIOS } from './deviceInfo';
export const saveLastLocalAuthenticationSession = async (server: string, serverRecord?: TServerModel): Promise<void> => {
export const saveLastLocalAuthenticationSession = async (
server: string,
serverRecord?: TServerModel,
timesync?: number | null
): Promise<void> => {
if (!timesync) {
timesync = new Date().getTime();
}
const serversDB = database.servers;
const serversCollection = serversDB.get('servers');
await serversDB.write(async () => {
try {
if (!serverRecord) {
serverRecord = (await serversCollection.find(server)) as TServerModel;
serverRecord = await serversCollection.find(server);
}
const time = timesync || 0;
await serverRecord.update(record => {
record.lastLocalAuthenticatedSession = new Date();
record.lastLocalAuthenticatedSession = new Date(time);
});
} catch (e) {
// Do nothing
@ -103,6 +113,9 @@ export const localAuthenticate = async (server: string): Promise<void> => {
// if screen lock is enabled
if (serverRecord?.autoLock) {
// Get time from server
const timesync = await getServerTimeSync(server);
// Make sure splash screen has been hidden
try {
await RNBootSplash.hide();
@ -116,10 +129,10 @@ export const localAuthenticate = async (server: string): Promise<void> => {
// `checkHasPasscode` results newPasscode = true if a passcode has been set
if (!result?.newPasscode) {
// diff to last authenticated session
const diffToLastSession = moment().diff(serverRecord?.lastLocalAuthenticatedSession, 'seconds');
const diffToLastSession = moment(timesync).diff(serverRecord?.lastLocalAuthenticatedSession, 'seconds');
// if last authenticated session is older than configured auto lock time, authentication is required
if (diffToLastSession >= serverRecord.autoLockTime!) {
// if it was not possible to get `timesync` from server or the last authenticated session is older than the configured auto lock time, authentication is required
if (!timesync || (serverRecord?.autoLockTime && diffToLastSession >= serverRecord.autoLockTime)) {
// set isLocalAuthenticated to false
store.dispatch(setLocalAuthenticated(false));
@ -141,7 +154,7 @@ export const localAuthenticate = async (server: string): Promise<void> => {
}
await resetAttempts();
await saveLastLocalAuthenticationSession(server, serverRecord);
await saveLastLocalAuthenticationSession(server, serverRecord, timesync);
}
};

View File

@ -58,5 +58,33 @@ export const MessageTypeValues = [
{
value: 'room_unarchived',
text: 'Message_HideType_room_unarchived'
},
{
value: 'removed-user-from-team',
text: 'Message_HideType_removed_user_from_team'
},
{
value: 'added-user-to-team',
text: 'Message_HideType_added_user_to_team'
},
{
value: 'user-added-room-to-team',
text: 'Message_HideType_user_added_room_to_team'
},
{
value: 'user-converted-to-channel',
text: 'Message_HideType_user_converted_to_channel'
},
{
value: 'user-converted-to-team',
text: 'Message_HideType_user_converted_to_team'
},
{
value: 'user-deleted-room-from-team',
text: 'Message_HideType_user_deleted_room_from_team'
},
{
value: 'user-removed-room-from-team',
text: 'Message_HideType_user_removed_room_from_team'
}
];

View File

@ -22,7 +22,7 @@ export const capitalize = (s: string): string => {
return s.charAt(0).toUpperCase() + s.slice(1);
};
export const formatDate = (date: Date): string =>
export const formatDate = (date: string | Date): string =>
moment(date).calendar(null, {
lastDay: `[${I18n.t('Yesterday')}]`,
sameDay: 'LT',
@ -30,7 +30,7 @@ export const formatDate = (date: Date): string =>
sameElse: 'L'
});
export const formatDateThreads = (date: Date): string =>
export const formatDateThreads = (date: string | Date): string =>
moment(date).calendar(null, {
sameDay: 'LT',
lastDay: `[${I18n.t('Yesterday')}] LT`,

View File

@ -260,7 +260,7 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView
/>
</HeaderButton.Container>
),
headerTitle: () => <SearchHeader onSearchChangeText={onChangeText} />,
headerTitle: () => <SearchHeader onSearchChangeText={onChangeText} testID='team-channels-view-search-header' />,
headerTitleContainerStyle: {
left: headerTitlePosition.left,
right: headerTitlePosition.right

View File

@ -1,20 +1,17 @@
import React, { useEffect, useState } from 'react';
import { StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native';
import { StyleSheet, View } from 'react-native';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import isEmpty from 'lodash/isEmpty';
import React, { useEffect, useState } from 'react';
import { StyleSheet, View } from 'react-native';
import { useDispatch } from 'react-redux';
import I18n from '../i18n';
import { withTheme } from '../theme';
import { forwardRoom, ITransferData } from '../actions/room';
import { themes } from '../constants/colors';
import RocketChat from '../lib/rocketchat';
import OrSeparator from '../containers/OrSeparator';
import Input from '../containers/UIKit/MultiSelect/Input';
import { forwardRoom as forwardRoomAction } from '../actions/room';
import { IRoom } from '../definitions';
import { IBaseScreen, IRoom } from '../definitions';
import I18n from '../i18n';
import RocketChat from '../lib/rocketchat';
import { ChatsStackParamList } from '../stacks/types';
import { withTheme } from '../theme';
import { IOptionsField } from './NotificationPreferencesView/options';
const styles = StyleSheet.create({
@ -23,33 +20,26 @@ const styles = StyleSheet.create({
padding: 16
}
});
interface ITransferData {
roomId: string;
userId?: string;
departmentId?: string;
}
interface IUser {
username: string;
_id: string;
}
interface IForwardLivechatViewProps {
navigation: StackNavigationProp<ChatsStackParamList, 'ForwardLivechatView'>;
route: RouteProp<ChatsStackParamList, 'ForwardLivechatView'>;
theme: string;
forwardRoom: (rid: string, transferData: ITransferData) => void;
interface IParsedData {
label: string;
value: string;
}
const COUNT_DEPARTMENT = 50;
const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForwardLivechatViewProps) => {
const [departments, setDepartments] = useState<IOptionsField[]>([]);
const ForwardLivechatView = ({ navigation, route, theme }: IBaseScreen<ChatsStackParamList, 'ForwardLivechatView'>) => {
const [departments, setDepartments] = useState<IParsedData[]>([]);
const [departmentId, setDepartment] = useState('');
const [departmentTotal, setDepartmentTotal] = useState(0);
const [users, setUsers] = useState<IOptionsField[]>([]);
const [userId, setUser] = useState();
const [room, setRoom] = useState<IRoom>({} as IRoom);
const dispatch = useDispatch();
const rid = route.params?.rid;
@ -57,7 +47,7 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForward
try {
const result = await RocketChat.getDepartments({ count: COUNT_DEPARTMENT, text, offset });
if (result.success) {
const parsedDepartments: IOptionsField[] = result.departments.map(department => ({
const parsedDepartments: IParsedData[] = result.departments.map(department => ({
label: department.name,
value: department._id
}));
@ -116,7 +106,7 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForward
transferData.departmentId = departmentId;
}
forwardRoom(rid, transferData);
dispatch(forwardRoom(rid, transferData));
};
useEffect(() => {
@ -171,8 +161,4 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForward
);
};
const mapDispatchToProps = (dispatch: Dispatch) => ({
forwardRoom: (rid: string, transferData: ITransferData) => dispatch(forwardRoomAction(rid, transferData))
});
export default connect(null, mapDispatchToProps)(withTheme(ForwardLivechatView));
export default withTheme(ForwardLivechatView);

View File

@ -1,21 +1,20 @@
import { dequal } from 'dequal';
import React from 'react';
import { Alert, Keyboard, StyleSheet, Text, View } from 'react-native';
import { connect } from 'react-redux';
import { dequal } from 'dequal';
import { StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/core';
import Button from '../containers/Button';
import I18n from '../i18n';
import * as HeaderButton from '../containers/HeaderButton';
import { loginRequest } from '../actions/login';
import { themes } from '../constants/colors';
import { withTheme } from '../theme';
import Button from '../containers/Button';
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
import TextInput from '../containers/TextInput';
import { loginRequest as loginRequestAction } from '../actions/login';
import * as HeaderButton from '../containers/HeaderButton';
import LoginServices from '../containers/LoginServices';
import sharedStyles from './Styles';
import TextInput from '../containers/TextInput';
import { IApplicationState, IBaseScreen } from '../definitions';
import I18n from '../i18n';
import { OutsideParamList } from '../stacks/types';
import { withTheme } from '../theme';
import sharedStyles from './Styles';
const styles = StyleSheet.create({
registerDisabled: {
@ -48,9 +47,7 @@ const styles = StyleSheet.create({
}
});
interface ILoginViewProps {
navigation: StackNavigationProp<OutsideParamList, 'LoginView'>;
route: RouteProp<OutsideParamList, 'LoginView'>;
interface ILoginViewProps extends IBaseScreen<OutsideParamList, 'LoginView'> {
Site_Name: string;
Accounts_RegistrationForm: string;
Accounts_RegistrationForm_LinkReplacementText: string;
@ -63,7 +60,6 @@ interface ILoginViewProps {
error: string;
};
failure: boolean;
theme: string;
loginRequest: Function;
inviteLinkToken: string;
}
@ -132,9 +128,9 @@ class LoginView extends React.Component<ILoginViewProps, any> {
}
const { user, password } = this.state;
const { loginRequest } = this.props;
const { dispatch } = this.props;
Keyboard.dismiss();
loginRequest({ user, password });
dispatch(loginRequest({ user, password }));
};
renderUserForm = () => {
@ -243,23 +239,19 @@ class LoginView extends React.Component<ILoginViewProps, any> {
}
}
const mapStateToProps = (state: any) => ({
const mapStateToProps = (state: IApplicationState) => ({
server: state.server.server,
Site_Name: state.settings.Site_Name,
Accounts_ShowFormLogin: state.settings.Accounts_ShowFormLogin,
Accounts_RegistrationForm: state.settings.Accounts_RegistrationForm,
Accounts_RegistrationForm_LinkReplacementText: state.settings.Accounts_RegistrationForm_LinkReplacementText,
Site_Name: state.settings.Site_Name as string,
Accounts_ShowFormLogin: state.settings.Accounts_ShowFormLogin as boolean,
Accounts_RegistrationForm: state.settings.Accounts_RegistrationForm as string,
Accounts_RegistrationForm_LinkReplacementText: state.settings.Accounts_RegistrationForm_LinkReplacementText as string,
isFetching: state.login.isFetching,
failure: state.login.failure,
error: state.login.error && state.login.error.data,
Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder,
Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder,
Accounts_PasswordReset: state.settings.Accounts_PasswordReset,
Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder as string,
Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder as string,
Accounts_PasswordReset: state.settings.Accounts_PasswordReset as boolean,
inviteLinkToken: state.inviteLinks.token
});
const mapDispatchToProps = (dispatch: any) => ({
loginRequest: (params: any) => dispatch(loginRequestAction(params))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(LoginView));
export default connect(mapStateToProps)(withTheme(LoginView));

View File

@ -71,6 +71,8 @@ interface IMessageItem {
msg?: string;
starred: boolean;
pinned: boolean;
type: string;
url: string;
}
interface IParams {

View File

@ -1,26 +1,25 @@
import React from 'react';
import { Keyboard, StyleSheet, Text, View } from 'react-native';
import { StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/core';
import { connect } from 'react-redux';
import RNPickerSelect from 'react-native-picker-select';
import { connect } from 'react-redux';
import { OutsideParamList } from '../stacks/types';
import log, { events, logEvent } from '../utils/log';
import Button from '../containers/Button';
import I18n from '../i18n';
import * as HeaderButton from '../containers/HeaderButton';
import { loginRequest } from '../actions/login';
import { themes } from '../constants/colors';
import { withTheme } from '../theme';
import Button from '../containers/Button';
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
import TextInput from '../containers/TextInput';
import isValidEmail from '../utils/isValidEmail';
import { showErrorAlert } from '../utils/info';
import RocketChat from '../lib/rocketchat';
import { loginRequest as loginRequestAction } from '../actions/login';
import openLink from '../utils/openLink';
import * as HeaderButton from '../containers/HeaderButton';
import LoginServices from '../containers/LoginServices';
import TextInput from '../containers/TextInput';
import { IApplicationState, IBaseScreen } from '../definitions';
import I18n from '../i18n';
import RocketChat from '../lib/rocketchat';
import { getShowLoginButton } from '../selectors/login';
import { OutsideParamList } from '../stacks/types';
import { withTheme } from '../theme';
import { showErrorAlert } from '../utils/info';
import isValidEmail from '../utils/isValidEmail';
import log, { events, logEvent } from '../utils/log';
import openLink from '../utils/openLink';
import sharedStyles from './Styles';
const styles = StyleSheet.create({
@ -51,9 +50,7 @@ const styles = StyleSheet.create({
}
});
interface IProps {
navigation: StackNavigationProp<OutsideParamList, 'RegisterView'>;
route: RouteProp<OutsideParamList, 'RegisterView'>;
interface IProps extends IBaseScreen<OutsideParamList, 'RegisterView'> {
server: string;
Site_Name: string;
Gitlab_URL: string;
@ -63,8 +60,6 @@ interface IProps {
Accounts_EmailVerification: boolean;
Accounts_ManuallyApproveNewUsers: boolean;
showLoginButton: boolean;
loginRequest: Function;
theme: string;
}
class RegisterView extends React.Component<IProps, any> {
@ -130,7 +125,7 @@ class RegisterView extends React.Component<IProps, any> {
Keyboard.dismiss();
const { name, email, password, username, customFields } = this.state;
const { loginRequest, Accounts_EmailVerification, navigation, Accounts_ManuallyApproveNewUsers } = this.props;
const { dispatch, Accounts_EmailVerification, navigation, Accounts_ManuallyApproveNewUsers } = this.props;
try {
await RocketChat.register({
@ -148,11 +143,11 @@ class RegisterView extends React.Component<IProps, any> {
await navigation.goBack();
showErrorAlert(I18n.t('Wait_activation_warning'), I18n.t('Registration_Succeeded'));
} else {
await loginRequest({ user: email, password });
dispatch(loginRequest({ user: email, password }));
}
} catch (e: any) {
if (e.data?.errorType === 'username-invalid') {
return loginRequest({ user: email, password });
return dispatch(loginRequest({ user: email, password }));
}
if (e.data?.error) {
logEvent(events.REGISTER_DEFAULT_SIGN_UP_F);
@ -349,20 +344,16 @@ class RegisterView extends React.Component<IProps, any> {
}
}
const mapStateToProps = (state: any) => ({
const mapStateToProps = (state: IApplicationState) => ({
server: state.server.server,
Site_Name: state.settings.Site_Name,
Gitlab_URL: state.settings.API_Gitlab_URL,
CAS_enabled: state.settings.CAS_enabled,
CAS_login_url: state.settings.CAS_login_url,
Accounts_CustomFields: state.settings.Accounts_CustomFields,
Accounts_EmailVerification: state.settings.Accounts_EmailVerification,
Accounts_ManuallyApproveNewUsers: state.settings.Accounts_ManuallyApproveNewUsers,
Site_Name: state.settings.Site_Name as string,
Gitlab_URL: state.settings.API_Gitlab_URL as string,
CAS_enabled: state.settings.CAS_enabled as boolean,
CAS_login_url: state.settings.CAS_login_url as string,
Accounts_CustomFields: state.settings.Accounts_CustomFields as string,
Accounts_EmailVerification: state.settings.Accounts_EmailVerification as boolean,
Accounts_ManuallyApproveNewUsers: state.settings.Accounts_ManuallyApproveNewUsers as boolean,
showLoginButton: getShowLoginButton(state)
});
const mapDispatchToProps = (dispatch: any) => ({
loginRequest: (params: any) => dispatch(loginRequestAction(params))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(RegisterView));
export default connect(mapStateToProps)(withTheme(RegisterView));

View File

@ -7,8 +7,8 @@ import { Q } from '@nozbe/watermelondb';
import { compareServerVersion } from '../../lib/utils';
import Touch from '../../utils/touch';
import { setLoading as setLoadingAction } from '../../actions/selectedUsers';
import { closeRoom as closeRoomAction, leaveRoom as leaveRoomAction } from '../../actions/room';
import { setLoading } from '../../actions/selectedUsers';
import { closeRoom, leaveRoom } from '../../actions/room';
import sharedStyles from '../Styles';
import Avatar from '../../containers/Avatar';
import Status from '../../containers/Status';
@ -334,9 +334,9 @@ class RoomActionsView extends React.Component {
const {
room: { rid }
} = this.state;
const { closeRoom } = this.props;
const { dispatch } = this.props;
closeRoom(rid);
dispatch(closeRoom(rid));
};
returnLivechat = () => {
@ -375,16 +375,16 @@ class RoomActionsView extends React.Component {
addUser = async () => {
const { room } = this.state;
const { setLoadingInvite, navigation } = this.props;
const { dispatch, navigation } = this.props;
const { rid } = room;
try {
setLoadingInvite(true);
dispatch(setLoading(true));
await RocketChat.addUsersToRoom(rid);
navigation.pop();
} catch (e) {
log(e);
} finally {
setLoadingInvite(false);
dispatch(setLoading(false));
}
};
@ -458,12 +458,12 @@ class RoomActionsView extends React.Component {
leaveChannel = () => {
const { room } = this.state;
const { leaveRoom } = this.props;
const { dispatch } = this.props;
showConfirmationAlert({
message: I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: RocketChat.getRoomTitle(room) }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
onPress: () => leaveRoom('channel', room)
onPress: () => dispatch(leaveRoom('channel', room))
});
};
@ -522,7 +522,7 @@ class RoomActionsView extends React.Component {
leaveTeam = async () => {
const { room } = this.state;
const { navigation, leaveRoom } = this.props;
const { navigation, dispatch } = this.props;
try {
const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId: room.u._id });
@ -538,21 +538,21 @@ class RoomActionsView extends React.Component {
title: 'Leave_Team',
data: teamChannels,
infoText: 'Select_Team_Channels',
nextAction: data => leaveRoom('team', room, data),
nextAction: data => dispatch(leaveRoom('team', room, data)),
showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_leave'))
});
} else {
showConfirmationAlert({
message: I18n.t('You_are_leaving_the_team', { team: RocketChat.getRoomTitle(room) }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
onPress: () => leaveRoom('team', room)
onPress: () => dispatch(leaveRoom('team', room))
});
}
} catch (e) {
showConfirmationAlert({
message: I18n.t('You_are_leaving_the_team', { team: RocketChat.getRoomTitle(room) }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
onPress: () => leaveRoom('team', room)
onPress: () => dispatch(leaveRoom('team', room))
});
}
};
@ -1242,10 +1242,4 @@ const mapStateToProps = state => ({
viewCannedResponsesPermission: state.permissions['view-canned-responses']
});
const mapDispatchToProps = dispatch => ({
leaveRoom: (roomType, room, selected) => dispatch(leaveRoomAction(roomType, room, selected)),
closeRoom: rid => dispatch(closeRoomAction(rid)),
setLoadingInvite: loading => dispatch(setLoadingAction(loading))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(withDimensions(RoomActionsView)));
export default connect(mapStateToProps)(withTheme(withDimensions(RoomActionsView)));

View File

@ -32,6 +32,7 @@ import { isIOS } from '../../utils/deviceInfo';
import { compareServerVersion } from '../../lib/utils';
import styles from './styles';
import { InsideStackParamList, ChatsStackParamList } from '../../stacks/types';
import { IRoom } from '../../definitions';
import { IEmoji } from '../../definitions/IEmoji';
const QUERY_SIZE = 50;
@ -81,7 +82,7 @@ class SearchMessagesView extends React.Component<ISearchMessagesViewProps, ISear
private encrypted: boolean | undefined;
private room: { rid: any; name: any; fname: any; t: any } | null | undefined;
private room: Pick<IRoom, 'rid' | 'name' | 'fname' | 't'> | null | undefined;
static navigationOptions = ({ navigation, route }: INavigationOption) => {
const options: StackNavigationOptions = {

View File

@ -1,25 +1,26 @@
import React from 'react';
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { Dispatch } from 'redux';
import { ScrollView, StyleSheet, Text } from 'react-native';
import { connect } from 'react-redux';
import Orientation from 'react-native-orientation-locker';
import { RouteProp } from '@react-navigation/native';
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import React from 'react';
import { ScrollView, StyleSheet, Text } from 'react-native';
import Orientation from 'react-native-orientation-locker';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { loginRequest as loginRequestAction } from '../actions/login';
import TextInput from '../containers/TextInput';
import { loginRequest } from '../actions/login';
import { themes } from '../constants/colors';
import Button from '../containers/Button';
import KeyboardView from '../presentation/KeyboardView';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import SafeAreaView from '../containers/SafeAreaView';
import StatusBar from '../containers/StatusBar';
import TextInput from '../containers/TextInput';
import { IApplicationState } from '../definitions';
import I18n from '../i18n';
import RocketChat from '../lib/rocketchat';
import StatusBar from '../containers/StatusBar';
import { withTheme } from '../theme';
import { themes } from '../constants/colors';
import { isTablet } from '../utils/deviceInfo';
import KeyboardView from '../presentation/KeyboardView';
import { getUserSelector } from '../selectors/login';
import { withTheme } from '../theme';
import { isTablet } from '../utils/deviceInfo';
import { showErrorAlert } from '../utils/info';
import SafeAreaView from '../containers/SafeAreaView';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import sharedStyles from './Styles';
const styles = StyleSheet.create({
@ -39,9 +40,9 @@ interface ISetUsernameViewProps {
route: RouteProp<{ SetUsernameView: { title: string } }, 'SetUsernameView'>;
server: string;
userId: string;
loginRequest: ({ resume }: { resume: string }) => void;
token: string;
theme: string;
dispatch: Dispatch;
}
class SetUsernameView extends React.Component<ISetUsernameViewProps, ISetUsernameViewState> {
@ -86,7 +87,7 @@ class SetUsernameView extends React.Component<ISetUsernameViewProps, ISetUsernam
submit = async () => {
const { username } = this.state;
const { loginRequest, token } = this.props;
const { dispatch, token } = this.props;
if (!username.trim()) {
return;
@ -95,7 +96,7 @@ class SetUsernameView extends React.Component<ISetUsernameViewProps, ISetUsernam
this.setState({ saving: true });
try {
await RocketChat.saveUserProfile({ username });
await loginRequest({ resume: token });
dispatch(loginRequest({ resume: token }));
} catch (e: any) {
showErrorAlert(e.message, I18n.t('Oops'));
}
@ -144,13 +145,9 @@ class SetUsernameView extends React.Component<ISetUsernameViewProps, ISetUsernam
}
}
const mapStateToProps = (state: any) => ({
const mapStateToProps = (state: IApplicationState) => ({
server: state.server.server,
token: getUserSelector(state).token
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
loginRequest: (params: { resume: string }) => dispatch(loginRequestAction(params))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(SetUsernameView));
export default connect(mapStateToProps)(withTheme(SetUsernameView));

View File

@ -29,6 +29,7 @@ import Header from './Header';
import styles from './styles';
import { IAttachment } from './interfaces';
import { ISubscription } from '../../definitions/ISubscription';
import { IUser } from '../../definitions';
interface IShareViewState {
selected: IAttachment;
@ -230,6 +231,7 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
},
thread?.id,
server,
// @ts-ignore
{ id: user.id, token: user.token }
);
}
@ -239,7 +241,7 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
// Send text message
} else if (text.length) {
await RocketChat.sendMessage(room.rid, text, thread?.id, { id: user.id, token: user.token });
await RocketChat.sendMessage(room.rid, text, thread?.id, { id: user.id, token: user.token } as IUser);
}
} catch {
// Do nothing

View File

@ -1,24 +1,24 @@
import React from 'react';
import { StackNavigationProp } from '@react-navigation/stack';
import { FlatList, StyleSheet } from 'react-native';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import I18n from '../i18n';
import { UserStatus } from '../definitions/UserStatus';
import { setUser } from '../actions/login';
import * as HeaderButton from '../containers/HeaderButton';
import * as List from '../containers/List';
import Loading from '../containers/Loading';
import SafeAreaView from '../containers/SafeAreaView';
import Status from '../containers/Status/Status';
import TextInput from '../containers/TextInput';
import { LISTENER } from '../containers/Toast';
import { IApplicationState, IBaseScreen } from '../definitions';
import I18n from '../i18n';
import RocketChat from '../lib/rocketchat';
import { getUserSelector } from '../selectors/login';
import { withTheme } from '../theme';
import EventEmitter from '../utils/events';
import { showErrorAlert } from '../utils/info';
import Loading from '../containers/Loading';
import RocketChat from '../lib/rocketchat';
import log, { events, logEvent } from '../utils/log';
import { LISTENER } from '../containers/Toast';
import { withTheme } from '../theme';
import { getUserSelector } from '../selectors/login';
import * as HeaderButton from '../containers/HeaderButton';
import { setUser as setUserAction } from '../actions/login';
import SafeAreaView from '../containers/SafeAreaView';
const STATUS = [
{
@ -65,12 +65,9 @@ interface IStatusViewState {
loading: boolean;
}
interface IStatusViewProps {
navigation: StackNavigationProp<any, 'StatusView'>;
interface IStatusViewProps extends IBaseScreen<any, 'StatusView'> {
user: IUser;
theme: string;
isMasterDetail: boolean;
setUser: (user: IUser) => void;
Accounts_AllowInvisibleStatusOption: boolean;
}
@ -111,7 +108,7 @@ class StatusView extends React.Component<IStatusViewProps, IStatusViewState> {
};
setCustomStatus = async (statusText: string) => {
const { user, setUser } = this.props;
const { user, dispatch } = this.props;
this.setState({ loading: true });
@ -119,7 +116,7 @@ class StatusView extends React.Component<IStatusViewProps, IStatusViewState> {
const result = await RocketChat.setUserStatus(user.status, statusText);
if (result.success) {
logEvent(events.STATUS_CUSTOM);
setUser({ statusText });
dispatch(setUser({ statusText }));
EventEmitter.emit(LISTENER, { message: I18n.t('Status_saved_successfully') });
} else {
logEvent(events.STATUS_CUSTOM_F);
@ -156,7 +153,7 @@ class StatusView extends React.Component<IStatusViewProps, IStatusViewState> {
renderItem = ({ item }: { item: { id: string; name: string } }) => {
const { statusText } = this.state;
const { user, setUser } = this.props;
const { user, dispatch } = this.props;
const { id, name } = item;
return (
<List.Item
@ -168,7 +165,7 @@ class StatusView extends React.Component<IStatusViewProps, IStatusViewState> {
try {
const result = await RocketChat.setUserStatus(item.id, statusText);
if (result.success) {
setUser({ status: item.id });
dispatch(setUser({ status: item.id as UserStatus }));
}
} catch (e: any) {
showErrorAlert(I18n.t(e.data.errorType));
@ -205,14 +202,10 @@ class StatusView extends React.Component<IStatusViewProps, IStatusViewState> {
}
}
const mapStateToProps = (state: any) => ({
const mapStateToProps = (state: IApplicationState) => ({
user: getUserSelector(state),
isMasterDetail: state.app.isMasterDetail,
Accounts_AllowInvisibleStatusOption: state.settings.Accounts_AllowInvisibleStatusOption ?? true
Accounts_AllowInvisibleStatusOption: (state.settings.Accounts_AllowInvisibleStatusOption as boolean) ?? true
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
setUser: (user: IUser) => dispatch(setUserAction(user))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(StatusView));
export default connect(mapStateToProps)(withTheme(StatusView));

View File

@ -4,6 +4,7 @@ import React from 'react';
import { Alert, FlatList, Keyboard } from 'react-native';
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { deleteRoom } from '../actions/room';
import { themes } from '../constants/colors';
@ -17,6 +18,7 @@ import SafeAreaView from '../containers/SafeAreaView';
import SearchHeader from '../containers/SearchHeader';
import StatusBar from '../containers/StatusBar';
import { IApplicationState, IBaseScreen } from '../definitions';
import { ERoomType } from '../definitions/ERoomType';
import { withDimensions } from '../dimensions';
import I18n from '../i18n';
import database from '../lib/database';
@ -48,7 +50,7 @@ const keyExtractor = (item: IItem) => item._id;
// This interface comes from request RocketChat.getTeamListRoom
interface IItem {
_id: string;
_id: ERoomType;
fname: string;
customFields: object;
broadcast: boolean;
@ -97,6 +99,7 @@ interface ITeamChannelsViewProps extends IProps {
showActionSheet: (options: any) => void;
showAvatar: boolean;
displayMode: string;
dispatch: Dispatch;
}
class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChannelsViewState> {
private teamId: string;
@ -438,7 +441,8 @@ class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChan
{
text: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
style: 'destructive',
onPress: () => dispatch(deleteRoom(item._id, item.t))
// VERIFY ON PR
onPress: () => dispatch(deleteRoom(item._id, item))
}
],
{ cancelable: false }

View File

@ -0,0 +1,13 @@
diff --git a/node_modules/@types/ejson/index.d.ts b/node_modules/@types/ejson/index.d.ts
index 3a35636..278ef98 100755
--- a/node_modules/@types/ejson/index.d.ts
+++ b/node_modules/@types/ejson/index.d.ts
@@ -17,7 +17,7 @@ export function parse(str: string): any;
export function stringify(obj: any, options?: StringifyOptions): string;
export function toJSONValue(obj: any): string;
-export function fromJSONValue(obj: string): any;
+export function fromJSONValue(obj: Object): any;
export function isBinary(value: any): boolean;
export function newBinary(len: number): Uint8Array;
export function equals(a: any, b: any, options?: CloneOptions): boolean;

View File

@ -797,6 +797,13 @@ stories.add('System messages', () => (
<Message msg='public' type='room_changed_privacy' isInfo />
<Message type='room_e2e_disabled' isInfo />
<Message type='room_e2e_enabled' isInfo />
<Message type='removed-user-from-team' isInfo />
<Message type='added-user-to-team' isInfo />
<Message type='user-added-room-to-team' isInfo msg='channel-name' />
<Message type='user-converted-to-team' isInfo msg='channel-name' />
<Message type='user-converted-to-channel' isInfo msg='channel-name' />
<Message type='user-deleted-room-from-team' isInfo msg='channel-name' />
<Message type='user-removed-room-from-team' isInfo msg='channel-name' />
</>
));

File diff suppressed because one or more lines are too long