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

View File

@ -45,7 +45,14 @@ export const SYSTEM_MESSAGES = [
'message_snippeted', 'message_snippeted',
'thread-created', 'thread-created',
'room_e2e_enabled', '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 = { export const SYSTEM_MESSAGE_TYPES = {
@ -56,7 +63,14 @@ export const SYSTEM_MESSAGE_TYPES = {
USER_JOINED_TEAM: 'ujt', USER_JOINED_TEAM: 'ujt',
USER_JOINED_DISCUSSION: 'ut', USER_JOINED_DISCUSSION: 'ut',
USER_LEFT_CHANNEL: 'ul', 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 = [ 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_TEAM,
SYSTEM_MESSAGE_TYPES.USER_JOINED_DISCUSSION, SYSTEM_MESSAGE_TYPES.USER_JOINED_DISCUSSION,
SYSTEM_MESSAGE_TYPES.USER_LEFT_CHANNEL, 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 = { type TInfoMessage = {
@ -76,6 +97,7 @@ type TInfoMessage = {
msg: string; msg: string;
author: { username: string }; author: { username: string };
}; };
export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage): string => { export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage): string => {
const { username } = author; const { username } = author;
if (type === 'rm') { if (type === 'rm') {
@ -147,6 +169,27 @@ export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage): strin
if (type === 'room_e2e_enabled') { if (type === 'room_e2e_enabled') {
return I18n.t('This_room_encryption_has_been_enabled_by__username_', { username }); 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 ''; 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 { export interface IAttachment {
ts: Date; ts: string | Date;
title: string; title: string;
type: string; type: string;
description: string; description: string;

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
import Model from '@nozbe/watermelondb/Model'; import Model from '@nozbe/watermelondb/Model';
import { MarkdownAST } from '@rocket.chat/message-parser';
import { IAttachment } from './IAttachment';
import { IMessage } from './IMessage'; import { IMessage } from './IMessage';
import { IServedBy } from './IServedBy'; import { IServedBy } from './IServedBy';
import { SubscriptionType } from './ISubscription'; import { SubscriptionType } from './ISubscription';
@ -37,6 +39,7 @@ export interface IRoom {
tags?: string[]; tags?: string[];
e2eKeyId?: string; e2eKeyId?: string;
avatarETag?: string; avatarETag?: string;
latest?: string;
default?: true; default?: true;
featured?: true; featured?: true;
} }
@ -100,3 +103,52 @@ export interface IOmnichannelRoom extends Omit<IRoom, 'default' | 'featured' | '
} }
export type TRoomModel = IRoom & Model; 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 Model from '@nozbe/watermelondb/Model';
import { IEnterpriseModules } from '../reducers/enterpriseModules';
export interface IServer { export interface IServer {
name: string; name: string;
iconURL: string; iconURL: string;
@ -13,7 +15,7 @@ export interface IServer {
autoLockTime?: number; autoLockTime?: number;
biometry?: boolean; biometry?: boolean;
uniqueID: string; uniqueID: string;
enterpriseModules: string; enterpriseModules: IEnterpriseModules;
E2E_Enable: boolean; E2E_Enable: boolean;
} }

View File

@ -9,4 +9,22 @@ export interface ISettings {
_updatedAt?: Date; _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; export type TSettingsModel = ISettings & Model;

View File

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

View File

@ -24,12 +24,20 @@ export interface IVisitor {
lastMessageTs: Date; lastMessageTs: Date;
} }
export enum ERoomTypes {
DIRECT = 'direct',
GROUP = 'group',
CHANNEL = 'channel'
}
export interface ISubscription { export interface ISubscription {
_id: string; // _id belongs watermelonDB _id: string; // _id belongs watermelonDB
id: string; // id from server id: string; // id from server
_updatedAt?: string; // from server
v?: IVisitor;
f: boolean; f: boolean;
t: SubscriptionType; t: SubscriptionType;
ts: Date; ts: string | Date;
ls: Date; ls: Date;
name: string; name: string;
fname?: string; fname?: string;
@ -38,12 +46,14 @@ export interface ISubscription {
alert: boolean; alert: boolean;
roles?: string[]; roles?: string[];
unread: number; unread: number;
lm: string;
lr: string;
userMentions: number; userMentions: number;
groupMentions: number; groupMentions: number;
tunread?: string[]; tunread?: string[];
tunreadUser?: string[]; tunreadUser?: string[];
tunreadGroup?: string[]; tunreadGroup?: string[];
roomUpdatedAt: Date; roomUpdatedAt: Date | number;
ro: boolean; ro: boolean;
lastOpen?: Date; lastOpen?: Date;
description?: string; description?: string;
@ -59,7 +69,7 @@ export interface ISubscription {
ignored?: string[]; ignored?: string[];
broadcast?: boolean; broadcast?: boolean;
prid?: string; prid?: string;
draftMessage?: string; draftMessage?: string | null;
lastThreadSync?: Date; lastThreadSync?: Date;
jitsiTimeout?: number; jitsiTimeout?: number;
autoTranslate?: boolean; autoTranslate?: boolean;
@ -88,3 +98,30 @@ export interface ISubscription {
} }
export type TSubscriptionModel = ISubscription & Model; 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 { export interface IThreadResult {
id: string;
_id: string; _id: string;
rid: string; rid: string;
ts: string | Date; ts: string | Date;
@ -23,13 +24,13 @@ export interface IThreadResult {
attachments?: IAttachment[]; attachments?: IAttachment[];
md?: MarkdownAST; md?: MarkdownAST;
u: IUserMessage; u: IUserMessage;
_updatedAt: Date; _updatedAt: string | Date;
urls?: IUrl[]; urls?: IUrl[];
mentions?: IUserMention[]; mentions?: IUserMention[];
channels?: IUserChannel[]; channels?: IUserChannel[];
replies?: string[]; replies?: string[];
tcount?: number; tcount?: number;
tlm?: Date; tlm?: string | Date;
} }
export interface IThread { export interface IThread {
@ -38,8 +39,8 @@ export interface IThread {
msg?: string; msg?: string;
t?: MessageType; t?: MessageType;
rid: string; rid: string;
_updatedAt?: Date; _updatedAt?: string | Date;
ts?: Date; ts?: string | Date;
u?: IUserMessage; u?: IUserMessage;
alias?: string; alias?: string;
parseUrls?: boolean; parseUrls?: boolean;
@ -56,10 +57,10 @@ export interface IThread {
role?: string; role?: string;
drid?: string; drid?: string;
dcount?: number | string; dcount?: number | string;
dlm?: number; dlm?: string | Date;
tmid?: string; tmid?: string;
tcount?: number | string; tcount?: number | string;
tlm?: string; tlm?: string | Date;
replies?: string[]; replies?: string[];
mentions?: IUserMention[]; mentions?: IUserMention[];
channels?: IUserChannel[]; channels?: IUserChannel[];
@ -67,7 +68,7 @@ export interface IThread {
autoTranslate?: boolean; autoTranslate?: boolean;
translations?: any; translations?: any;
e2e?: string; e2e?: string;
subscription: { id: string }; subscription?: { id: string };
} }
export type TThreadModel = IThread & Model; export type TThreadModel = IThread & Model;

View File

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

View File

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

View File

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

View File

@ -23,7 +23,9 @@ import { ICreateChannel } from '../../reducers/createChannel';
import { ICreateDiscussion } from '../../reducers/createDiscussion'; import { ICreateDiscussion } from '../../reducers/createDiscussion';
import { IEncryption } from '../../reducers/encryption'; import { IEncryption } from '../../reducers/encryption';
import { IInviteLinks } from '../../reducers/inviteLinks'; import { IInviteLinks } from '../../reducers/inviteLinks';
import { ILogin } from '../../reducers/login';
import { IRoles } from '../../reducers/roles'; import { IRoles } from '../../reducers/roles';
import { IRoom } from '../../reducers/room';
import { ISelectedUsers } from '../../reducers/selectedUsers'; import { ISelectedUsers } from '../../reducers/selectedUsers';
import { IServer } from '../../reducers/server'; import { IServer } from '../../reducers/server';
import { ISettings } from '../../reducers/settings'; import { ISettings } from '../../reducers/settings';
@ -33,13 +35,13 @@ import { IEnterpriseModules } from '../../reducers/enterpriseModules';
export interface IApplicationState { export interface IApplicationState {
settings: ISettings; settings: ISettings;
login: any;
meteor: IConnect; meteor: IConnect;
login: ILogin;
server: IServer; server: IServer;
selectedUsers: ISelectedUsers; selectedUsers: ISelectedUsers;
app: IApp; app: IApp;
createChannel: ICreateChannel; createChannel: ICreateChannel;
room: any; room: IRoom;
rooms: any; rooms: any;
sortPreferences: any; sortPreferences: any;
share: IShare; 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; } & PaginatedResult;
}; };
'emoji-custom.list': { 'emoji-custom.list': {
GET: (params: { query: string }) => { GET: (params: { updatedSince: string }) => {
emojis?: { emojis?: {
update: ICustomEmojiDescriptor[]; update: ICustomEmojiDescriptor[];
}; };

View File

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

View File

@ -788,5 +788,24 @@
"Enable_Message_Parser": "Enable Message Parser", "Enable_Message_Parser": "Enable Message Parser",
"Unsupported_format": "Unsupported format", "Unsupported_format": "Unsupported format",
"Downloaded_file": "Downloaded file", "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 // Decrypt a message
decryptMessage = async (message: Partial<IMessage>) => { decryptMessage = async (message: Pick<IMessage, 't' | 'e2e' | 'rid' | 'msg' | 'tmsg'>) => {
const { t, e2e } = message; const { t, e2e } = message;
// Prevent create a new instance if this room was encrypted sometime ago // 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 { ISubscription } from '../../definitions';
import Navigation from '../Navigation';
import { events, logEvent } from '../../utils/log'; import { events, logEvent } from '../../utils/log';
import { store } from '../auxStore';
import Navigation from '../Navigation';
import sdk from '../rocketchat';
async function jitsiURL({ room }) { async function jitsiURL({ room }: { room: ISubscription }) {
const { settings } = reduxStore.getState(); const { settings } = store.getState();
const { Jitsi_Enabled } = settings; const { Jitsi_Enabled } = settings;
if (!Jitsi_Enabled) { if (!Jitsi_Enabled) {
@ -19,7 +21,7 @@ async function jitsiURL({ room }) {
let queryString = ''; let queryString = '';
if (Jitsi_Enabled_TokenAuth) { if (Jitsi_Enabled_TokenAuth) {
try { try {
const accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', room?.rid); const accessToken = await sdk.methodCallWrapper('jitsi:generateAccessToken', room?.rid);
queryString = `?jwt=${accessToken}`; queryString = `?jwt=${accessToken}`;
} catch { } catch {
logEvent(events.RA_JITSI_F); logEvent(events.RA_JITSI_F);
@ -30,23 +32,23 @@ async function jitsiURL({ room }) {
if (Jitsi_URL_Room_Hash) { if (Jitsi_URL_Room_Hash) {
rname = uniqueID + room?.rid; rname = uniqueID + room?.rid;
} else { } 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}`; return `${protocol}${domain}${prefix}${rname}${queryString}`;
} }
export function callJitsiWithoutServer(path) { export function callJitsiWithoutServer(path: string): void {
logEvent(events.RA_JITSI_VIDEO); logEvent(events.RA_JITSI_VIDEO);
const { Jitsi_SSL } = reduxStore.getState().settings; const { Jitsi_SSL } = store.getState().settings;
const protocol = Jitsi_SSL ? 'https://' : 'http://'; const protocol = Jitsi_SSL ? 'https://' : 'http://';
const url = `${protocol}${path}`; const url = `${protocol}${path}`;
Navigation.navigate('JitsiMeetView', { url, onlyAudio: false }); 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); 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 }); 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 { store } from '../auxStore';
import database from '../database';
import RocketChat from '../rocketchat';
import sdk from '../rocketchat/services/sdk';
const restTypes = { const restTypes = {
channel: 'channels', channel: 'channels',
@ -7,27 +10,28 @@ const restTypes = {
group: 'groups' group: 'groups'
}; };
async function open({ type, rid, name }) { async function open({ type, rid, name }: { type: ERoomTypes; rid: string; name: string }) {
try { try {
const params = rid ? { roomId: rid } : { roomName: name }; const params = rid ? { roomId: rid } : { roomName: name };
// if it's a direct link without rid we'll create a new dm // 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 the dm already exists it'll return the existent
if (type === 'direct' && !rid) { if (type === ERoomTypes.DIRECT && !rid) {
const result = await this.createDirectMessage(name); const result = await RocketChat.createDirectMessage(name);
if (result.success) { if (result.success) {
const { room } = result; const { room } = result;
room.rid = room._id; room.rid = room._id as string;
return room; return room;
} }
} }
// if it's a group we need to check if you can open // if it's a group we need to check if you can open
if (type === 'group') { if (type === ERoomTypes.GROUP) {
try { try {
// RC 0.61.0 // RC 0.61.0
await this.sdk.post(`${restTypes[type]}.open`, params); // @ts-ignore
} catch (e) { await sdk.post(`${restTypes[type]}.open`, params);
} catch (e: any) {
if (!(e.data && /is already open/.test(e.data.error))) { if (!(e.data && /is already open/.test(e.data.error))) {
return false; 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 // if it's a channel or group and the link don't have rid
// we'll get info from the room // 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 // 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) { if (result.success) {
const room = result[type]; const room = result[type];
room.rid = room._id; 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 { try {
const db = database.active; const db = database.active;
const subsCollection = db.get('subscriptions'); const subsCollection = db.get('subscriptions');
@ -86,8 +91,9 @@ export default async function canOpenRoom({ rid, path, isCall }) {
} }
const [type, name] = path.split('/'); const [type, name] = path.split('/');
const t = type as ERoomTypes;
try { try {
const result = await open.call(this, { type, rid, name }); const result = await open({ type: t, rid, name });
return result; return result;
} catch (e) { } catch (e) {
return false; return false;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import RocketChat from '../rocketchat'; import RocketChat from '../rocketchat';
import { IMessage } from '../../definitions';
const getSingleMessage = (messageId: string) => const getSingleMessage = (messageId: string): Promise<IMessage> =>
new Promise(async (resolve, reject) => { new Promise(async (resolve, reject) => {
try { try {
const result = await RocketChat.getSingleMessage(messageId); 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 log from '../../utils/log';
import { Encryption } from '../encryption'; import { Encryption } from '../encryption';
import getSingleMessage from './getSingleMessage'; 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) => { const getThreadName = async (rid: string, tmid: string, messageId: string): Promise<string | undefined> => {
let tmsg; let tmsg: string | undefined;
try { try {
const db = database.active; const db = database.active;
const threadCollection = db.get('threads'); const threadCollection = db.get('threads');
@ -18,7 +19,7 @@ const getThreadName = async (rid, tmid, messageId) => {
const threadRecord = await getThreadById(tmid); const threadRecord = await getThreadById(tmid);
if (threadRecord) { if (threadRecord) {
tmsg = buildThreadName(threadRecord); tmsg = buildThreadName(threadRecord);
await db.action(async () => { await db.write(async () => {
await messageRecord?.update(m => { await messageRecord?.update(m => {
m.tmsg = tmsg; m.tmsg = tmsg;
}); });
@ -27,11 +28,11 @@ const getThreadName = async (rid, tmid, messageId) => {
let thread = await getSingleMessage(tmid); let thread = await getSingleMessage(tmid);
thread = await Encryption.decryptMessage(thread); thread = await Encryption.decryptMessage(thread);
tmsg = buildThreadName(thread); tmsg = buildThreadName(thread);
await db.action(async () => { await db.write(async () => {
await db.batch( await db.batch(
threadCollection?.prepareCreate(t => { threadCollection?.prepareCreate((t: TThreadModel) => {
t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema); t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema);
t.subscription.id = rid; if (t.subscription) t.subscription.id = rid;
Object.assign(t, thread); Object.assign(t, thread);
}), }),
messageRecord?.prepareUpdate(m => { messageRecord?.prepareUpdate(m => {

View File

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

View File

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

View File

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

View File

@ -5,32 +5,41 @@ import log from '../../utils/log';
import { getMessageById } from '../database/services/Message'; import { getMessageById } from '../database/services/Message';
import { generateLoadMoreId } from '../utils'; import { generateLoadMoreId } from '../utils';
import updateMessages from './updateMessages'; 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; const COUNT = 50;
async function load({ rid: roomId, latest, t }) { async function load({ rid: roomId, latest, t }: { rid: string; latest?: string; t: RoomTypes }) {
let params = { roomId, count: COUNT }; let params = { roomId, count: COUNT } as { roomId: string; count: number; latest?: string };
if (latest) { if (latest) {
params = { ...params, latest: new Date(latest).toISOString() }; params = { ...params, latest: new Date(latest).toISOString() };
} }
const apiType = this.roomTypeToApiType(t); const apiType = roomTypeToApiType(t);
if (!apiType) { if (!apiType) {
return []; return [];
} }
// RC 0.48.0 // 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') { if (!data || data.status === 'error') {
return []; return [];
} }
return data.messages; 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) => { return new Promise(async (resolve, reject) => {
try { try {
const data = await load.call(this, args); const data = await load(args);
if (data?.length) { if (data?.length) {
const lastMessage = data[data.length - 1]; const lastMessage = data[data.length - 1];
const lastMessageRecord = await getMessageById(lastMessage._id); 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 }); await updateMessages({ rid: args.rid, update: data, loaderItem: args.loaderItem });
return resolve(data); return resolve(data);
} else {
return resolve([]);
} }
return resolve([]);
} catch (e) { } catch (e) {
log(e); log(e);
reject(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 { MESSAGE_TYPE_LOAD_NEXT_CHUNK } from '../../constants/messageTypeLoad';
import { generateLoadMoreId } from '../utils'; import { generateLoadMoreId } from '../utils';
import updateMessages from './updateMessages'; import updateMessages from './updateMessages';
import { IMessage, TMessageModel } from '../../definitions';
import RocketChat from '../rocketchat';
const COUNT = 50; 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) => { return new Promise(async (resolve, reject) => {
try { 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); let messages = EJSON.fromJSONValue(data?.messages);
messages = orderBy(messages, 'ts'); messages = orderBy(messages, 'ts');
if (messages?.length) { if (messages?.length) {
@ -31,9 +40,8 @@ export default function loadNextMessages(args) {
} }
await updateMessages({ rid: args.rid, update: messages, loaderItem: args.loaderItem }); await updateMessages({ rid: args.rid, update: messages, loaderItem: args.loaderItem });
return resolve(messages); return resolve(messages);
} else {
return resolve([]);
} }
return resolve([]);
} catch (e) { } catch (e) {
log(e); log(e);
reject(e); reject(e);

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ import { getSubscriptionByRoomId } from '../database/services/Subscription';
interface IUpdateMessages { interface IUpdateMessages {
rid: string; rid: string;
update: IMessage[]; update: IMessage[];
remove: IMessage[]; remove?: IMessage[];
loaderItem?: TMessageModel; loaderItem?: TMessageModel;
} }
@ -105,7 +105,7 @@ export default async function updateMessages({
threadCollection.prepareCreate( threadCollection.prepareCreate(
protectedFunction((t: TThreadModel) => { protectedFunction((t: TThreadModel) => {
t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema); 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); 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); 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 // RC 2.2.0
// TODO: missing definitions from server
// @ts-ignore
sdk.post('e2e.setUserPublicAndPrivateKeys', { public_key, private_key }); sdk.post('e2e.setUserPublicAndPrivateKeys', { public_key, private_key });
export const e2eRequestSubscriptionKeys = (): any => export const e2eRequestSubscriptionKeys = (): any =>
@ -221,12 +219,6 @@ export const teamListRoomsOfUser = ({ teamId, userId }: { teamId: string; userId
// @ts-ignore // @ts-ignore
sdk.get('teams.listRoomsOfUser', { teamId, userId }); 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 => { export const convertChannelToTeam = ({ rid, name, type }: { rid: string; name: string; type: 'c' | 'p' }): any => {
const params = { const params = {
...(type === 'c' ...(type === 'c'

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { TActionEnterpriseModules } from '../actions/enterpriseModules'; import { TActionEnterpriseModules } from '../actions/enterpriseModules';
import { ENTERPRISE_MODULES } from '../actions/actionsTypes'; import { ENTERPRISE_MODULES } from '../actions/actionsTypes';
export type IEnterpriseModules = 'omnichannel-mobile-enterprise' | 'livechat-enterprise'; export type IEnterpriseModules = string;
export const initialState: IEnterpriseModules[] = []; 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 * 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, isLocalAuthenticated: true,
isAuthenticated: false, isAuthenticated: false,
isFetching: false, isFetching: false,
user: {}, user: {},
error: {}, error: {},
services: {} services: {},
failure: false
}; };
export default function login(state = initialState, action) { export default function login(state = initialState, action: TActionsLogin): ILogin {
switch (action.type) { switch (action.type) {
case types.APP.INIT: case types.APP.INIT:
return initialState; return initialState;
@ -60,13 +92,14 @@ export default function login(state = initialState, action) {
...state, ...state,
user: { user: {
...state.user, ...state.user,
settings: { settings: state.user?.settings
...state.user.settings, ? {
preferences: { ...state.user?.settings,
...state.user.settings.preferences, preferences: state.user?.settings?.preferences
...action.preference ? { ...state.user.settings.preferences, ...action.preference }
} : { ...action.preference }
} }
: { profile: {}, preferences: {} }
} }
}; };
case types.LOGIN.SET_LOCAL_AUTHENTICATED: 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'; import { ROOM } from '../actions/actionsTypes';
const initialState = { export type IRoomRecord = string[];
rid: null,
export interface IRoom {
rid: string;
isDeleting: boolean;
rooms: IRoomRecord;
}
export const initialState: IRoom = {
rid: '',
isDeleting: false, isDeleting: false,
rooms: [] rooms: []
}; };
export default function (state = initialState, action) { export default function (state = initialState, action: TActionsRoom): IRoom {
switch (action.type) { switch (action.type) {
case ROOM.SUBSCRIBE: case ROOM.SUBSCRIBE:
return { return {

View File

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

View File

@ -1,7 +1,7 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import { IApplicationState } from '../definitions'; import { IApplicationState, IUser } from '../definitions';
interface IServices { interface IServices {
facebook: { clientId: string }; facebook: { clientId: string };
@ -13,7 +13,7 @@ interface IServices {
wordpress: { clientId: string; serverURL: string }; wordpress: { clientId: string; serverURL: string };
} }
const getUser = (state: IApplicationState) => { const getUser = (state: IApplicationState): Partial<IUser> => {
if (!isEmpty(state.share?.user)) { if (!isEmpty(state.share?.user)) {
return 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 getShowFormLoginSetting = (state: IApplicationState) => (state.settings.Accounts_ShowFormLogin as boolean) || false;
const getIframeEnabledSetting = (state: IApplicationState) => (state.settings.Accounts_iframe_enabled 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( export const getShowLoginButton = createSelector(
[getLoginServices, getShowFormLoginSetting, getIframeEnabledSetting], [getLoginServices, getShowFormLoginSetting, getIframeEnabledSetting],

View File

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

View File

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

View File

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

View File

@ -58,5 +58,33 @@ export const MessageTypeValues = [
{ {
value: 'room_unarchived', value: 'room_unarchived',
text: 'Message_HideType_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); 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, { moment(date).calendar(null, {
lastDay: `[${I18n.t('Yesterday')}]`, lastDay: `[${I18n.t('Yesterday')}]`,
sameDay: 'LT', sameDay: 'LT',
@ -30,7 +30,7 @@ export const formatDate = (date: Date): string =>
sameElse: 'L' sameElse: 'L'
}); });
export const formatDateThreads = (date: Date): string => export const formatDateThreads = (date: string | Date): string =>
moment(date).calendar(null, { moment(date).calendar(null, {
sameDay: 'LT', sameDay: 'LT',
lastDay: `[${I18n.t('Yesterday')}] LT`, lastDay: `[${I18n.t('Yesterday')}] LT`,

View File

@ -260,7 +260,7 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView
/> />
</HeaderButton.Container> </HeaderButton.Container>
), ),
headerTitle: () => <SearchHeader onSearchChangeText={onChangeText} />, headerTitle: () => <SearchHeader onSearchChangeText={onChangeText} testID='team-channels-view-search-header' />,
headerTitleContainerStyle: { headerTitleContainerStyle: {
left: headerTitlePosition.left, left: headerTitlePosition.left,
right: headerTitlePosition.right 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 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 { forwardRoom, ITransferData } from '../actions/room';
import { withTheme } from '../theme';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import RocketChat from '../lib/rocketchat';
import OrSeparator from '../containers/OrSeparator'; import OrSeparator from '../containers/OrSeparator';
import Input from '../containers/UIKit/MultiSelect/Input'; import Input from '../containers/UIKit/MultiSelect/Input';
import { forwardRoom as forwardRoomAction } from '../actions/room'; import { IBaseScreen, IRoom } from '../definitions';
import { IRoom } from '../definitions'; import I18n from '../i18n';
import RocketChat from '../lib/rocketchat';
import { ChatsStackParamList } from '../stacks/types'; import { ChatsStackParamList } from '../stacks/types';
import { withTheme } from '../theme';
import { IOptionsField } from './NotificationPreferencesView/options'; import { IOptionsField } from './NotificationPreferencesView/options';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -23,33 +20,26 @@ const styles = StyleSheet.create({
padding: 16 padding: 16
} }
}); });
interface ITransferData {
roomId: string;
userId?: string;
departmentId?: string;
}
interface IUser { interface IUser {
username: string; username: string;
_id: string; _id: string;
} }
interface IForwardLivechatViewProps {
navigation: StackNavigationProp<ChatsStackParamList, 'ForwardLivechatView'>; interface IParsedData {
route: RouteProp<ChatsStackParamList, 'ForwardLivechatView'>; label: string;
theme: string; value: string;
forwardRoom: (rid: string, transferData: ITransferData) => void;
} }
const COUNT_DEPARTMENT = 50; const COUNT_DEPARTMENT = 50;
const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForwardLivechatViewProps) => { const ForwardLivechatView = ({ navigation, route, theme }: IBaseScreen<ChatsStackParamList, 'ForwardLivechatView'>) => {
const [departments, setDepartments] = useState<IOptionsField[]>([]); const [departments, setDepartments] = useState<IParsedData[]>([]);
const [departmentId, setDepartment] = useState(''); const [departmentId, setDepartment] = useState('');
const [departmentTotal, setDepartmentTotal] = useState(0); const [departmentTotal, setDepartmentTotal] = useState(0);
const [users, setUsers] = useState<IOptionsField[]>([]); const [users, setUsers] = useState<IOptionsField[]>([]);
const [userId, setUser] = useState(); const [userId, setUser] = useState();
const [room, setRoom] = useState<IRoom>({} as IRoom); const [room, setRoom] = useState<IRoom>({} as IRoom);
const dispatch = useDispatch();
const rid = route.params?.rid; const rid = route.params?.rid;
@ -57,7 +47,7 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForward
try { try {
const result = await RocketChat.getDepartments({ count: COUNT_DEPARTMENT, text, offset }); const result = await RocketChat.getDepartments({ count: COUNT_DEPARTMENT, text, offset });
if (result.success) { if (result.success) {
const parsedDepartments: IOptionsField[] = result.departments.map(department => ({ const parsedDepartments: IParsedData[] = result.departments.map(department => ({
label: department.name, label: department.name,
value: department._id value: department._id
})); }));
@ -116,7 +106,7 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForward
transferData.departmentId = departmentId; transferData.departmentId = departmentId;
} }
forwardRoom(rid, transferData); dispatch(forwardRoom(rid, transferData));
}; };
useEffect(() => { useEffect(() => {
@ -171,8 +161,4 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForward
); );
}; };
const mapDispatchToProps = (dispatch: Dispatch) => ({ export default withTheme(ForwardLivechatView);
forwardRoom: (rid: string, transferData: ITransferData) => dispatch(forwardRoomAction(rid, transferData))
});
export default connect(null, mapDispatchToProps)(withTheme(ForwardLivechatView));

View File

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

View File

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

View File

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

View File

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

View File

@ -32,6 +32,7 @@ import { isIOS } from '../../utils/deviceInfo';
import { compareServerVersion } from '../../lib/utils'; import { compareServerVersion } from '../../lib/utils';
import styles from './styles'; import styles from './styles';
import { InsideStackParamList, ChatsStackParamList } from '../../stacks/types'; import { InsideStackParamList, ChatsStackParamList } from '../../stacks/types';
import { IRoom } from '../../definitions';
import { IEmoji } from '../../definitions/IEmoji'; import { IEmoji } from '../../definitions/IEmoji';
const QUERY_SIZE = 50; const QUERY_SIZE = 50;
@ -81,7 +82,7 @@ class SearchMessagesView extends React.Component<ISearchMessagesViewProps, ISear
private encrypted: boolean | undefined; 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) => { static navigationOptions = ({ navigation, route }: INavigationOption) => {
const options: StackNavigationOptions = { 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 { 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 { loginRequest } from '../actions/login';
import TextInput from '../containers/TextInput'; import { themes } from '../constants/colors';
import Button from '../containers/Button'; import Button from '../containers/Button';
import KeyboardView from '../presentation/KeyboardView'; import SafeAreaView from '../containers/SafeAreaView';
import scrollPersistTaps from '../utils/scrollPersistTaps'; import StatusBar from '../containers/StatusBar';
import TextInput from '../containers/TextInput';
import { IApplicationState } from '../definitions';
import I18n from '../i18n'; import I18n from '../i18n';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import StatusBar from '../containers/StatusBar'; import KeyboardView from '../presentation/KeyboardView';
import { withTheme } from '../theme';
import { themes } from '../constants/colors';
import { isTablet } from '../utils/deviceInfo';
import { getUserSelector } from '../selectors/login'; import { getUserSelector } from '../selectors/login';
import { withTheme } from '../theme';
import { isTablet } from '../utils/deviceInfo';
import { showErrorAlert } from '../utils/info'; import { showErrorAlert } from '../utils/info';
import SafeAreaView from '../containers/SafeAreaView'; import scrollPersistTaps from '../utils/scrollPersistTaps';
import sharedStyles from './Styles'; import sharedStyles from './Styles';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -39,9 +40,9 @@ interface ISetUsernameViewProps {
route: RouteProp<{ SetUsernameView: { title: string } }, 'SetUsernameView'>; route: RouteProp<{ SetUsernameView: { title: string } }, 'SetUsernameView'>;
server: string; server: string;
userId: string; userId: string;
loginRequest: ({ resume }: { resume: string }) => void;
token: string; token: string;
theme: string; theme: string;
dispatch: Dispatch;
} }
class SetUsernameView extends React.Component<ISetUsernameViewProps, ISetUsernameViewState> { class SetUsernameView extends React.Component<ISetUsernameViewProps, ISetUsernameViewState> {
@ -86,7 +87,7 @@ class SetUsernameView extends React.Component<ISetUsernameViewProps, ISetUsernam
submit = async () => { submit = async () => {
const { username } = this.state; const { username } = this.state;
const { loginRequest, token } = this.props; const { dispatch, token } = this.props;
if (!username.trim()) { if (!username.trim()) {
return; return;
@ -95,7 +96,7 @@ class SetUsernameView extends React.Component<ISetUsernameViewProps, ISetUsernam
this.setState({ saving: true }); this.setState({ saving: true });
try { try {
await RocketChat.saveUserProfile({ username }); await RocketChat.saveUserProfile({ username });
await loginRequest({ resume: token }); dispatch(loginRequest({ resume: token }));
} catch (e: any) { } catch (e: any) {
showErrorAlert(e.message, I18n.t('Oops')); 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, server: state.server.server,
token: getUserSelector(state).token token: getUserSelector(state).token
}); });
const mapDispatchToProps = (dispatch: Dispatch) => ({ export default connect(mapStateToProps)(withTheme(SetUsernameView));
loginRequest: (params: { resume: string }) => dispatch(loginRequestAction(params))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(SetUsernameView));

View File

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

View File

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

View File

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