Chore: Migrate Utils Folder to Typescript (#3544)

Co-authored-by: AlexAlexandre <alexalexandrejr@gmail.com>
This commit is contained in:
Reinaldo Neto 2022-01-12 09:54:04 -03:00 committed by GitHub
parent f7418791a2
commit a2b92f5e70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 458 additions and 353 deletions

View File

@ -1,7 +1,8 @@
import React from 'react';
import { TouchableOpacity } from 'react-native';
import { isAndroid } from '../../utils/deviceInfo';
import Touch from '../../utils/touch';
// Taken from https://github.com/rgommezz/react-native-scroll-bottom-sheet#touchables
export const Button = isAndroid ? Touch : TouchableOpacity;
export const Button: typeof React.Component = isAndroid ? Touch : TouchableOpacity;

View File

@ -5,6 +5,7 @@ import Touchable from 'react-native-platform-touchable';
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
import { avatarURL } from '../../utils/avatar';
import { SubscriptionType } from '../../definitions/ISubscription';
import Emoji from '../markdown/Emoji';
import { IAvatar } from './interfaces';
@ -27,8 +28,8 @@ const Avatar = React.memo(
text,
size = 25,
borderRadius = 4,
type = 'd'
}: Partial<IAvatar>) => {
type = SubscriptionType.DIRECT
}: IAvatar) => {
if ((!text && !avatar && !emoji && !rid) || !server) {
return null;
}

View File

@ -7,17 +7,17 @@ import { getUserSelector } from '../../selectors/login';
import Avatar from './Avatar';
import { IAvatar } from './interfaces';
class AvatarContainer extends React.Component<Partial<IAvatar>, any> {
class AvatarContainer extends React.Component<IAvatar, any> {
private mounted: boolean;
private subscription!: any;
private subscription: any;
static defaultProps = {
text: '',
type: 'd'
};
constructor(props: Partial<IAvatar>) {
constructor(props: IAvatar) {
super(props);
this.mounted = false;
this.state = { avatarETag: '' };
@ -55,7 +55,7 @@ class AvatarContainer extends React.Component<Partial<IAvatar>, any> {
try {
if (this.isDirect) {
const { text } = this.props;
const [user] = await usersCollection.query(Q.where('username', text!)).fetch();
const [user] = await usersCollection.query(Q.where('username', text)).fetch();
record = user;
} else {
const { rid } = this.props;
@ -82,7 +82,7 @@ class AvatarContainer extends React.Component<Partial<IAvatar>, any> {
render() {
const { avatarETag } = this.state;
const { serverVersion } = this.props;
return <Avatar avatarETag={avatarETag} serverVersion={serverVersion} {...this.props} />;
return <Avatar {...this.props} avatarETag={avatarETag} serverVersion={serverVersion} />;
}
}

View File

@ -1,23 +1,23 @@
export interface IAvatar {
server: string;
style: any;
server?: string;
style?: any;
text: string;
avatar: string;
emoji: string;
size: number;
borderRadius: number;
type: string;
children: JSX.Element;
user: {
id: string;
token: string;
avatar?: string;
emoji?: string;
size?: number;
borderRadius?: number;
type?: string;
children?: JSX.Element;
user?: {
id?: string;
token?: string;
};
theme: string;
onPress(): void;
getCustomEmoji(): any;
avatarETag: string;
isStatic: boolean | string;
rid: string;
blockUnauthenticatedAccess: boolean;
serverVersion: string;
theme?: string;
onPress?: () => void;
getCustomEmoji?: () => any;
avatarETag?: string;
isStatic?: boolean | string;
rid?: string;
blockUnauthenticatedAccess?: boolean;
serverVersion?: string;
}

View File

@ -305,8 +305,6 @@ const MessageActions = React.memo(
};
const handleDelete = (message: any) => {
// TODO - migrate this function for ts when fix the lint erros
// @ts-ignore
showConfirmationAlert({
message: I18n.t('You_will_not_be_able_to_recover_this_message'),
confirmationText: I18n.t('Delete'),

View File

@ -7,28 +7,28 @@ import Touch from '../../../utils/touch';
import { CustomIcon } from '../../../lib/Icons';
interface IPasscodeButton {
text: string;
icon: string;
text?: string;
icon?: string;
theme: string;
disabled: boolean;
onPress: Function;
disabled?: boolean;
onPress?: Function;
}
const Button = React.memo(({ text, disabled, theme, onPress, icon }: Partial<IPasscodeButton>) => {
const press = () => onPress && onPress(text!);
const Button = React.memo(({ text, disabled, theme, onPress, icon }: IPasscodeButton) => {
const press = () => onPress && onPress(text);
return (
<Touch
style={[styles.buttonView, { backgroundColor: 'transparent' }]}
underlayColor={themes[theme!].passcodeButtonActive}
rippleColor={themes[theme!].passcodeButtonActive}
underlayColor={themes[theme].passcodeButtonActive}
rippleColor={themes[theme].passcodeButtonActive}
enabled={!disabled}
theme={theme}
onPress={press}>
{icon ? (
<CustomIcon name={icon} size={36} color={themes[theme!].passcodePrimary} />
<CustomIcon name={icon} size={36} color={themes[theme].passcodePrimary} />
) : (
<Text style={[styles.buttonText, { color: themes[theme!].passcodePrimary }]}>{text}</Text>
<Text style={[styles.buttonText, { color: themes[theme].passcodePrimary }]}>{text}</Text>
)}
</Touch>
);

View File

@ -84,7 +84,7 @@ export interface IMessageContent {
export interface IMessageDiscussion {
msg: string;
dcount: number;
dlm: string;
dlm: Date;
theme: string;
}

View File

@ -0,0 +1,6 @@
export interface ICommand {
event: {
input: string;
modifierFlags: number;
};
}

View File

@ -10,8 +10,8 @@ export interface IServer {
version: string;
lastLocalAuthenticatedSession: Date;
autoLock: boolean;
autoLockTime: number | null;
biometry: boolean | null;
autoLockTime?: number;
biometry?: boolean;
uniqueID: string;
enterpriseModules: string;
E2E_Enable: boolean;

View File

@ -79,6 +79,8 @@ export interface ISubscription {
avatarETag?: string;
teamId?: string;
teamMain?: boolean;
search?: boolean;
username?: string;
// https://nozbe.github.io/WatermelonDB/Relation.html#relation-api
messages: Relation<TMessageModel>;
threads: Relation<TThreadModel>;

View File

@ -0,0 +1,8 @@
export type TThemeMode = 'automatic' | 'light' | 'dark';
export type TDarkLevel = 'black' | 'dark';
export interface IThemePreference {
currentTheme: TThemeMode;
darkLevel: TDarkLevel;
}

View File

@ -13,3 +13,4 @@ declare module 'react-native-mime-types';
declare module 'react-native-restart';
declare module 'react-native-prompt-android';
declare module 'react-native-jitsi-meet';
declare module 'rn-root-view';

View File

@ -30,6 +30,8 @@ import InAppNotification from './containers/InAppNotification';
import { ActionSheetProvider } from './containers/ActionSheet';
import debounce from './utils/debounce';
import { isFDroidBuild } from './constants/environment';
import { IThemePreference } from './definitions/ITheme';
import { ICommand } from './definitions/ICommand';
RNScreens.enableScreens();
@ -42,10 +44,7 @@ interface IDimensions {
interface IState {
theme: string;
themePreferences: {
currentTheme: 'automatic' | 'light';
darkLevel: string;
};
themePreferences: IThemePreference;
width: number;
height: number;
scale: number;
@ -175,7 +174,7 @@ export default class Root extends React.Component<{}, IState> {
setTheme = (newTheme = {}) => {
// change theme state
this.setState(
prevState => newThemeState(prevState, newTheme),
prevState => newThemeState(prevState, newTheme as IThemePreference),
() => {
const { themePreferences } = this.state;
// subscribe to Appearance changes
@ -191,7 +190,7 @@ export default class Root extends React.Component<{}, IState> {
initTablet = () => {
const { width } = this.state;
this.setMasterDetail(width);
this.onKeyCommands = KeyCommandsEmitter.addListener('onKeyCommand', (command: unknown) => {
this.onKeyCommands = KeyCommandsEmitter.addListener('onKeyCommand', (command: ICommand) => {
EventEmitter.emit(KEY_COMMAND, { event: command });
});
};

View File

@ -14,6 +14,7 @@ import { defaultHeader, getActiveRouteName, navigationTheme, themedHeader } from
import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat';
import { ThemeContext } from './theme';
import { localAuthenticate } from './utils/localAuthentication';
import { IThemePreference } from './definitions/ITheme';
import ScreenLockedView from './views/ScreenLockedView';
// Outside Stack
import WithoutServersView from './views/WithoutServersView';
@ -36,10 +37,7 @@ interface IDimensions {
interface IState {
theme: string;
themePreferences: {
currentTheme: 'automatic' | 'light';
darkLevel: string;
};
themePreferences: IThemePreference;
root: any;
width: number;
height: number;
@ -135,7 +133,7 @@ class Root extends React.Component<{}, IState> {
setTheme = (newTheme = {}) => {
// change theme state
this.setState(
prevState => newThemeState(prevState, newTheme),
prevState => newThemeState(prevState, newTheme as IThemePreference),
() => {
const { themePreferences } = this.state;
// subscribe to Appearance changes

View File

@ -1,12 +1,11 @@
import React from 'react';
import hoistNonReactStatics from 'hoist-non-react-statics';
import { IThemePreference } from './definitions/ITheme';
interface IThemeContextProps {
theme: string;
themePreferences?: {
currentTheme: 'automatic' | 'light';
darkLevel: string;
};
themePreferences?: IThemePreference;
setTheme?: (newTheme?: {}) => void;
}

View File

@ -4,7 +4,7 @@ import { isIOS } from './deviceInfo';
const { AppGroup } = NativeModules;
const appGroup = {
const appGroup: { path: string } = {
path: isIOS ? AppGroup.path : ''
};

View File

@ -1,6 +1,8 @@
import { compareServerVersion, methods } from '../lib/utils';
import { SubscriptionType } from '../definitions/ISubscription';
import { IAvatar } from '../containers/Avatar/interfaces';
const formatUrl = (url, size, query) => `${url}?format=png&size=${size}${query}`;
const formatUrl = (url: string, size: number, query: string) => `${url}?format=png&size=${size}${query}`;
export const avatarURL = ({
type,
@ -13,9 +15,9 @@ export const avatarURL = ({
rid,
blockUnauthenticatedAccess,
serverVersion
}) => {
}: IAvatar): string => {
let room;
if (type === 'd') {
if (type === SubscriptionType.DIRECT) {
room = text;
} else if (rid && !compareServerVersion(serverVersion, '3.6.0', methods.lowerThan)) {
room = `room/${rid}`;

View File

@ -1,8 +1,8 @@
/* eslint-disable no-bitwise */
// https://github.com/beatgammit/base64-js/blob/master/index.js
const lookup = [];
const revLookup = [];
const lookup: string[] = [];
const revLookup: number[] = [];
const Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array;
const code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
@ -16,7 +16,7 @@ for (let i = 0, len = code.length; i < len; i += 1) {
revLookup['-'.charCodeAt(0)] = 62;
revLookup['_'.charCodeAt(0)] = 63;
const getLens = b64 => {
const getLens = (b64: string) => {
const len = b64.length;
// We're encoding some strings not multiple of 4, so, disable this check
@ -37,16 +37,17 @@ const getLens = b64 => {
};
// base64 is 4/3 + up to two characters of the original data
export const byteLength = b64 => {
export const byteLength = (b64: string) => {
const lens = getLens(b64);
const validLen = lens[0];
const placeHoldersLen = lens[1];
return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
};
const _byteLength = (b64, validLen, placeHoldersLen) => ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
const _byteLength = (b64: string, validLen: number, placeHoldersLen: number) =>
((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
export const toByteArray = b64 => {
export const toByteArray = (b64: string) => {
let tmp;
const lens = getLens(b64);
const validLen = lens[0];
@ -92,10 +93,10 @@ export const toByteArray = b64 => {
return arr;
};
const tripletToBase64 = num =>
const tripletToBase64 = (num: number) =>
lookup[(num >> 18) & 0x3f] + lookup[(num >> 12) & 0x3f] + lookup[(num >> 6) & 0x3f] + lookup[num & 0x3f];
const encodeChunk = (uint8, start, end) => {
const encodeChunk = (uint8: number[] | Uint8Array, start: number, end: number) => {
let tmp;
const output = [];
for (let i = start; i < end; i += 3) {
@ -105,7 +106,7 @@ const encodeChunk = (uint8, start, end) => {
return output.join('');
};
export const fromByteArray = uint8 => {
export const fromByteArray = (uint8: number[] | Uint8Array) => {
let tmp;
const len = uint8.length;
const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes

View File

@ -1,20 +0,0 @@
export default function debounce(func, wait, immediate) {
let timeout;
function _debounce(...args) {
const context = this;
const later = function __debounce() {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
func.apply(context, args);
}
}
_debounce.stop = () => clearTimeout(timeout);
return _debounce;
}

22
app/utils/debounce.ts Normal file
View File

@ -0,0 +1,22 @@
export default function debounce(func: Function, wait?: number, immediate?: boolean) {
let timeout: number | null;
function _debounce(...args: any[]) {
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-this-alias
const context = this;
const later = function __debounce() {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
const callNow = immediate && !timeout;
clearTimeout(timeout!);
timeout = setTimeout(later, wait);
if (callNow) {
func.apply(context, args);
}
}
_debounce.stop = () => clearTimeout(timeout!);
return _debounce;
}

View File

@ -9,7 +9,7 @@ export const getBundleId = DeviceInfo.getBundleId();
export const getDeviceModel = DeviceInfo.getModel();
// Theme is supported by system on iOS 13+ or Android 10+
export const supportSystemTheme = () => {
export const supportSystemTheme = (): boolean => {
const systemVersion = parseInt(DeviceInfo.getSystemVersion(), 10);
return systemVersion >= (isIOS ? 13 : 10);
};

View File

@ -1,11 +1,25 @@
import { ICommand } from '../definitions/ICommand';
import log from './log';
type TEventEmitterEmmitArgs =
| { rid: string }
| { message: string }
| { method: string }
| { invalid: boolean }
| { force: boolean }
| { hasBiometry: boolean }
| { event: string | ICommand }
| { cancel: () => void }
| { submit: (param: string) => void };
class EventEmitter {
private events: { [key: string]: any };
constructor() {
this.events = {};
}
addEventListener(event, listener) {
addEventListener(event: string, listener: Function) {
if (typeof this.events[event] !== 'object') {
this.events[event] = [];
}
@ -13,7 +27,7 @@ class EventEmitter {
return listener;
}
removeListener(event, listener) {
removeListener(event: string, listener: Function) {
if (typeof this.events[event] === 'object') {
const idx = this.events[event].indexOf(listener);
if (idx > -1) {
@ -25,9 +39,9 @@ class EventEmitter {
}
}
emit(event, ...args) {
emit(event: string, ...args: TEventEmitterEmmitArgs[]) {
if (typeof this.events[event] === 'object') {
this.events[event].forEach(listener => {
this.events[event].forEach((listener: Function) => {
try {
listener.apply(this, args);
} catch (e) {

View File

@ -4,15 +4,20 @@ import { settings as RocketChatSettings } from '@rocket.chat/sdk';
import RocketChat from '../lib/rocketchat';
interface CustomHeaders {
'User-Agent': string;
Authorization?: string;
}
// this form is required by Rocket.Chat's parser in "app/statistics/server/lib/UAParserCustom.js"
export const headers = {
export const headers: CustomHeaders = {
'User-Agent': `RC Mobile; ${
Platform.OS
} ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})`
};
let _basicAuth;
export const setBasicAuth = basicAuth => {
export const setBasicAuth = (basicAuth: string): void => {
_basicAuth = basicAuth;
if (basicAuth) {
RocketChatSettings.customHeaders = { ...headers, Authorization: `Basic ${_basicAuth}` };
@ -24,12 +29,15 @@ export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY';
RocketChatSettings.customHeaders = headers;
export default (url, options = {}) => {
export default (url: string, options: { headers?: Headers; signal?: AbortSignal } = {}): Promise<Response> => {
let customOptions = { ...options, headers: RocketChatSettings.customHeaders };
if (options && options.headers) {
customOptions = { ...customOptions, headers: { ...options.headers, ...customOptions.headers } };
}
// TODO: Refactor when migrate rocketchat.js
// @ts-ignore
if (RocketChat.controller) {
// @ts-ignore
const { signal } = RocketChat.controller;
customOptions = { ...customOptions, signal };
}

View File

@ -1,19 +1,25 @@
import { IFileUpload } from './interfaces';
class Upload {
public xhr: XMLHttpRequest;
public formData: FormData;
constructor() {
this.xhr = new XMLHttpRequest();
this.formData = new FormData();
}
then = callback => {
then = (callback: (param: { respInfo: XMLHttpRequest }) => XMLHttpRequest) => {
this.xhr.onload = () => callback({ respInfo: this.xhr });
this.xhr.send(this.formData);
};
catch = callback => {
catch = (callback: ((this: XMLHttpRequest, ev: ProgressEvent<EventTarget>) => any) | null) => {
this.xhr.onerror = callback;
};
uploadProgress = callback => {
uploadProgress = (callback: (param: number, arg1: number) => any) => {
this.xhr.upload.onprogress = ({ total, loaded }) => callback(loaded, total);
};
@ -24,7 +30,7 @@ class Upload {
}
class FileUpload {
fetch = (method, url, headers, data) => {
fetch = (method: string, url: string, headers: { [x: string]: string }, data: IFileUpload[]) => {
const upload = new Upload();
upload.xhr.open(method, url);
@ -35,6 +41,7 @@ class FileUpload {
data.forEach(item => {
if (item.uri) {
upload.formData.append(item.name, {
// @ts-ignore
uri: item.uri,
type: item.type,
name: item.filename

View File

@ -1,7 +1,11 @@
import RNFetchBlob from 'rn-fetch-blob';
import { IFileUpload } from './interfaces';
type TMethods = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'post' | 'get' | 'delete' | 'put';
class FileUpload {
fetch = (method, url, headers, data) => {
fetch = (method: TMethods, url: string, headers: { [key: string]: string }, data: IFileUpload[]) => {
const formData = data.map(item => {
if (item.uri) {
return {

View File

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

View File

@ -1,7 +1,17 @@
import { ChatsStackParamList } from '../stacks/types';
import Navigation from '../lib/Navigation';
import RocketChat from '../lib/rocketchat';
import { ISubscription, SubscriptionType } from '../definitions/ISubscription';
const navigate = ({ item, isMasterDetail, ...props }) => {
const navigate = ({
item,
isMasterDetail,
...props
}: {
item: IItem;
isMasterDetail: boolean;
navigationMethod?: () => ChatsStackParamList;
}) => {
let navigationMethod = props.navigationMethod ?? Navigation.navigate;
if (isMasterDetail) {
@ -20,7 +30,22 @@ const navigate = ({ item, isMasterDetail, ...props }) => {
});
};
export const goRoom = async ({ item = {}, isMasterDetail = false, ...props }) => {
interface IItem extends Partial<ISubscription> {
rid: string;
name: string;
t: SubscriptionType;
}
export const goRoom = async ({
item,
isMasterDetail = false,
...props
}: {
item: IItem;
isMasterDetail: boolean;
navigationMethod?: any;
jumpToMessageId?: string;
}): Promise<void> => {
if (item.t === 'd' && item.search) {
// if user is using the search we need first to join/create room
try {
@ -30,8 +55,8 @@ export const goRoom = async ({ item = {}, isMasterDetail = false, ...props }) =>
return navigate({
item: {
rid: result.room._id,
name: username,
t: 'd'
name: username!,
t: SubscriptionType.DIRECT
},
isMasterDetail,
...props

View File

@ -1,25 +0,0 @@
import { Alert } from 'react-native';
import I18n from '../i18n';
export const showErrorAlert = (message, title, onPress = () => {}) =>
Alert.alert(title, message, [{ text: 'OK', onPress }], { cancelable: true });
export const showConfirmationAlert = ({ title, message, confirmationText, dismissText = I18n.t('Cancel'), onPress, onCancel }) =>
Alert.alert(
title || I18n.t('Are_you_sure_question_mark'),
message,
[
{
text: dismissText,
onPress: onCancel,
style: 'cancel'
},
{
text: confirmationText,
style: 'destructive',
onPress
}
],
{ cancelable: false }
);

41
app/utils/info.ts Normal file
View File

@ -0,0 +1,41 @@
import { Alert } from 'react-native';
import I18n from '../i18n';
export const showErrorAlert = (message: string, title?: string, onPress = () => {}): void =>
Alert.alert(title!, message, [{ text: 'OK', onPress }], { cancelable: true });
interface IShowConfirmationAlert {
title?: string;
message: string;
confirmationText: string;
dismissText?: string;
onPress: () => void;
onCancel?: () => void;
}
export const showConfirmationAlert = ({
title,
message,
confirmationText,
dismissText = I18n.t('Cancel'),
onPress,
onCancel
}: IShowConfirmationAlert): void =>
Alert.alert(
title || I18n.t('Are_you_sure_question_mark'),
message,
[
{
text: dismissText,
onPress: onCancel,
style: 'cancel'
},
{
text: confirmationText,
style: 'destructive',
onPress
}
],
{ cancelable: false }
);

View File

@ -1,16 +1,21 @@
import RocketChat from '../lib/rocketchat';
import reduxStore from '../lib/createStore';
import { ISubscription } from '../definitions/ISubscription';
const canPostReadOnly = async ({ rid }) => {
const canPostReadOnly = async ({ rid }: { rid: string }) => {
// TODO: this is not reactive. If this permission changes, the component won't be updated
const postReadOnlyPermission = reduxStore.getState().permissions['post-readonly'];
const permission = await RocketChat.hasPermission([postReadOnlyPermission], rid);
return permission[0];
};
const isMuted = (room, user) => room && room.muted && room.muted.find && !!room.muted.find(m => m === user.username);
const isMuted = (room: ISubscription, user: { username: string }) =>
room && room.muted && room.muted.find && !!room.muted.find(m => m === user.username);
export const isReadOnly = async (room, user) => {
export const isReadOnly = async (
room: ISubscription,
user: { id?: string; username: string; token?: string }
): Promise<boolean> => {
if (room.archived) {
return true;
}

View File

@ -1,4 +1,4 @@
export default function isValidEmail(email) {
export default function isValidEmail(email: string): boolean {
/* eslint-disable no-useless-escape */
const reg =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

View File

@ -16,16 +16,17 @@ import {
} from '../constants/localAuthentication';
import I18n from '../i18n';
import { setLocalAuthenticated } from '../actions/login';
import { TServerModel } from '../definitions/IServer';
import EventEmitter from './events';
import { isIOS } from './deviceInfo';
export const saveLastLocalAuthenticationSession = async (server, serverRecord) => {
export const saveLastLocalAuthenticationSession = async (server: string, serverRecord?: TServerModel): Promise<void> => {
const serversDB = database.servers;
const serversCollection = serversDB.get('servers');
await serversDB.action(async () => {
await serversDB.write(async () => {
try {
if (!serverRecord) {
serverRecord = await serversCollection.find(server);
serverRecord = (await serversCollection.find(server)) as TServerModel;
}
await serverRecord.update(record => {
record.lastLocalAuthenticatedSession = new Date();
@ -36,31 +37,31 @@ export const saveLastLocalAuthenticationSession = async (server, serverRecord) =
});
};
export const resetAttempts = () => AsyncStorage.multiRemove([LOCKED_OUT_TIMER_KEY, ATTEMPTS_KEY]);
export const resetAttempts = (): Promise<void> => AsyncStorage.multiRemove([LOCKED_OUT_TIMER_KEY, ATTEMPTS_KEY]);
const openModal = hasBiometry =>
new Promise(resolve => {
const openModal = (hasBiometry: boolean) =>
new Promise<void>(resolve => {
EventEmitter.emit(LOCAL_AUTHENTICATE_EMITTER, {
submit: () => resolve(),
hasBiometry
});
});
const openChangePasscodeModal = ({ force }) =>
new Promise((resolve, reject) => {
const openChangePasscodeModal = ({ force }: { force: boolean }) =>
new Promise<string>((resolve, reject) => {
EventEmitter.emit(CHANGE_PASSCODE_EMITTER, {
submit: passcode => resolve(passcode),
submit: (passcode: string) => resolve(passcode),
cancel: () => reject(),
force
});
});
export const changePasscode = async ({ force = false }) => {
export const changePasscode = async ({ force = false }: { force: boolean }): Promise<void> => {
const passcode = await openChangePasscodeModal({ force });
await UserPreferences.setStringAsync(PASSCODE_KEY, sha256(passcode));
};
export const biometryAuth = force =>
export const biometryAuth = (force?: boolean): Promise<LocalAuthentication.LocalAuthenticationResult> =>
LocalAuthentication.authenticateAsync({
disableDeviceFallback: true,
cancelLabel: force ? I18n.t('Dont_activate') : I18n.t('Local_authentication_biometry_fallback'),
@ -71,11 +72,11 @@ export const biometryAuth = force =>
* It'll help us to get the permission to use FaceID
* and enable/disable the biometry when user put their first passcode
*/
const checkBiometry = async serverRecord => {
const checkBiometry = async (serverRecord: TServerModel) => {
const serversDB = database.servers;
const result = await biometryAuth(true);
await serversDB.action(async () => {
await serversDB.write(async () => {
try {
await serverRecord.update(record => {
record.biometry = !!result?.success;
@ -86,7 +87,13 @@ const checkBiometry = async serverRecord => {
});
};
export const checkHasPasscode = async ({ force = true, serverRecord }) => {
export const checkHasPasscode = async ({
force = true,
serverRecord
}: {
force?: boolean;
serverRecord: TServerModel;
}): Promise<{ newPasscode?: boolean } | void> => {
const storedPasscode = await UserPreferences.getStringAsync(PASSCODE_KEY);
if (!storedPasscode) {
await changePasscode({ force });
@ -96,13 +103,13 @@ export const checkHasPasscode = async ({ force = true, serverRecord }) => {
return Promise.resolve();
};
export const localAuthenticate = async server => {
export const localAuthenticate = async (server: string): Promise<void> => {
const serversDB = database.servers;
const serversCollection = serversDB.get('servers');
let serverRecord;
let serverRecord: TServerModel;
try {
serverRecord = await serversCollection.find(server);
serverRecord = (await serversCollection.find(server)) as TServerModel;
} catch (error) {
return Promise.reject();
}
@ -125,7 +132,7 @@ export const localAuthenticate = async server => {
const diffToLastSession = moment().diff(serverRecord?.lastLocalAuthenticatedSession, 'seconds');
// if last authenticated session is older than configured auto lock time, authentication is required
if (diffToLastSession >= serverRecord?.autoLockTime) {
if (diffToLastSession >= serverRecord.autoLockTime!) {
// set isLocalAuthenticated to false
store.dispatch(setLocalAuthenticated(false));
@ -150,7 +157,7 @@ export const localAuthenticate = async server => {
}
};
export const supportedBiometryLabel = async () => {
export const supportedBiometryLabel = async (): Promise<string | null> => {
try {
const enrolled = await LocalAuthentication.isEnrolledAsync();

View File

@ -4,13 +4,13 @@ import { isFDroidBuild } from '../../constants/environment';
import events from './events';
const analytics = firebaseAnalytics || '';
let bugsnag = '';
let crashlytics;
let bugsnag: any = '';
let crashlytics: any;
let reportCrashErrors = true;
let reportAnalyticsEvents = true;
export const getReportCrashErrorsValue = () => reportCrashErrors;
export const getReportAnalyticsEventsValue = () => reportAnalyticsEvents;
export const getReportCrashErrorsValue = (): boolean => reportCrashErrors;
export const getReportAnalyticsEventsValue = (): boolean => reportAnalyticsEvents;
if (!isFDroidBuild) {
bugsnag = require('@bugsnag/react-native').default;
@ -18,7 +18,7 @@ if (!isFDroidBuild) {
onBreadcrumb() {
return reportAnalyticsEvents;
},
onError(error) {
onError(error: { breadcrumbs: string[] }) {
if (!reportAnalyticsEvents) {
error.breadcrumbs = [];
}
@ -34,13 +34,13 @@ export { events };
let metadata = {};
export const logServerVersion = serverVersion => {
export const logServerVersion = (serverVersion: string): void => {
metadata = {
serverVersion
};
};
export const logEvent = (eventName, payload) => {
export const logEvent = (eventName: string, payload?: { [key: string]: any }): void => {
try {
if (!isFDroidBuild) {
analytics().logEvent(eventName, payload);
@ -51,26 +51,26 @@ export const logEvent = (eventName, payload) => {
}
};
export const setCurrentScreen = currentScreen => {
export const setCurrentScreen = (currentScreen: string): void => {
if (!isFDroidBuild) {
analytics().setCurrentScreen(currentScreen);
bugsnag.leaveBreadcrumb(currentScreen, { type: 'navigation' });
}
};
export const toggleCrashErrorsReport = value => {
export const toggleCrashErrorsReport = (value: boolean): boolean => {
crashlytics().setCrashlyticsCollectionEnabled(value);
return (reportCrashErrors = value);
};
export const toggleAnalyticsEventsReport = value => {
export const toggleAnalyticsEventsReport = (value: boolean): boolean => {
analytics().setAnalyticsCollectionEnabled(value);
return (reportAnalyticsEvents = value);
};
export default e => {
export default (e: any): void => {
if (e instanceof Error && bugsnag && e.message !== 'Aborted' && !__DEV__) {
bugsnag.notify(e, event => {
bugsnag.notify(e, (event: { addMetadata: (arg0: string, arg1: {}) => void }) => {
event.addMetadata('details', { ...metadata });
});
if (!isFDroidBuild) {

View File

@ -1,4 +1,11 @@
export const canUploadFile = (file, allowList, maxFileSize, permissionToUploadFile) => {
import { IAttachment } from '../views/ShareView/interfaces';
export const canUploadFile = (
file: IAttachment,
allowList: string,
maxFileSize: number,
permissionToUploadFile: boolean
): { success: boolean; error?: string } => {
if (!(file && file.path)) {
return { success: true };
}
@ -13,11 +20,11 @@ export const canUploadFile = (file, allowList, maxFileSize, permissionToUploadFi
return { success: true };
}
const allowedMime = allowList.split(',');
if (allowedMime.includes(file.mime)) {
if (allowedMime.includes(file.mime!)) {
return { success: true };
}
const wildCardGlob = '/*';
const wildCards = allowedMime.filter(item => item.indexOf(wildCardGlob) > 0);
const wildCards = allowedMime.filter((item: string) => item.indexOf(wildCardGlob) > 0);
if (file.mime && wildCards.includes(file.mime.replace(/(\/.*)$/, wildCardGlob))) {
return { success: true };
}

View File

@ -1,4 +1,4 @@
const localeKeys = {
const localeKeys: { [key: string]: string } = {
en: 'en',
ru: 'ru',
'pt-BR': 'pt-br',
@ -13,4 +13,4 @@ const localeKeys = {
'zh-TW': 'zh-tw'
};
export const toMomentLocale = locale => localeKeys[locale];
export const toMomentLocale = (locale: string): string => localeKeys[locale];

View File

@ -1,12 +1,14 @@
import { Animated, Easing } from 'react-native';
import { HeaderStyleInterpolators, TransitionPresets } from '@react-navigation/stack';
import { HeaderStyleInterpolators, TransitionPreset, TransitionPresets } from '@react-navigation/stack';
// eslint-disable-next-line import/no-unresolved
import { StackCardStyleInterpolator, TransitionSpec } from '@react-navigation/stack/lib/typescript/src/types';
import { isAndroid } from '../deviceInfo';
import conditional from './conditional';
const { multiply } = Animated;
const forFadeFromCenter = ({ current, closing }) => {
const forFadeFromCenter: StackCardStyleInterpolator = ({ current, closing }) => {
const opacity = conditional(
closing,
current.progress,
@ -23,7 +25,7 @@ const forFadeFromCenter = ({ current, closing }) => {
};
};
const FadeIn = {
const FadeIn: TransitionSpec = {
animation: 'timing',
config: {
duration: 250,
@ -31,7 +33,7 @@ const FadeIn = {
}
};
const FadeOut = {
const FadeOut: TransitionSpec = {
animation: 'timing',
config: {
duration: 150,
@ -48,7 +50,7 @@ export const FadeFromCenterModal = {
cardStyleInterpolator: forFadeFromCenter
};
const forStackAndroid = ({ current, inverted, layouts: { screen } }) => {
const forStackAndroid: StackCardStyleInterpolator = ({ current, inverted, layouts: { screen } }) => {
const translateX = multiply(
current.progress.interpolate({
inputRange: [0, 1],
@ -70,7 +72,7 @@ const forStackAndroid = ({ current, inverted, layouts: { screen } }) => {
};
};
const StackAndroid = {
const StackAndroid: TransitionPreset = {
gestureDirection: 'horizontal',
transitionSpec: {
open: FadeIn,

View File

@ -10,7 +10,11 @@ const { add, multiply } = Animated;
* @param main Animated Node to use if the condition is `true`
* @param fallback Animated Node to use if the condition is `false`
*/
export default function conditional(condition, main, fallback) {
export default function conditional(
condition: Animated.AnimatedInterpolation,
main: Animated.Animated,
fallback: Animated.Animated
): Animated.AnimatedAddition {
// To implement this behavior, we multiply the main node with the condition.
// So if condition is 0, result will be 0, and if condition is 1, result will be main node.
// Then we multiple reverse of the condition (0 if condition is 1) with the fallback.

View File

@ -14,7 +14,7 @@ const scheme = {
brave: 'brave:'
};
const appSchemeURL = (url, browser) => {
const appSchemeURL = (url: string, browser: string): string => {
let schemeUrl = url;
const parsedUrl = parse(url, true);
const { protocol } = parsedUrl;
@ -35,7 +35,7 @@ const appSchemeURL = (url, browser) => {
return schemeUrl;
};
const openLink = async (url, theme = 'light') => {
const openLink = async (url: string, theme = 'light'): Promise<void> => {
try {
const browser = await UserPreferences.getStringAsync(DEFAULT_BROWSER_KEY);
@ -43,11 +43,12 @@ const openLink = async (url, theme = 'light') => {
await WebBrowser.openBrowserAsync(url, {
toolbarColor: themes[theme].headerBackground,
controlsColor: themes[theme].headerTintColor,
collapseToolbar: true,
// https://github.com/expo/expo/pull/4923
enableBarCollapsing: true,
showTitle: true
});
} else {
const schemeUrl = appSchemeURL(url, browser.replace(':', ''));
const schemeUrl = appSchemeURL(url, browser!.replace(':', ''));
await Linking.openURL(schemeUrl);
}
} catch {

View File

@ -1,4 +1,4 @@
export default function random(length) {
export default function random(length: number): string {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i += 1) {

View File

@ -15,7 +15,7 @@ const reviewDelay = 2000;
const numberOfDays = 7;
const numberOfPositiveEvent = 5;
const daysBetween = (date1, date2) => {
const daysBetween = (date1: Date, date2: Date): number => {
const one_day = 1000 * 60 * 60 * 24;
const date1_ms = date1.getTime();
const date2_ms = date2.getTime();
@ -32,7 +32,7 @@ const onCancelPress = () => {
}
};
export const onReviewPress = async () => {
export const onReviewPress = async (): Promise<void> => {
logEvent(events.SE_REVIEW_THIS_APP);
await onCancelPress();
try {

View File

@ -1,53 +0,0 @@
import moment from 'moment';
import { themes } from '../constants/colors';
import I18n from '../i18n';
export const isBlocked = room => {
if (room) {
const { t, blocked, blocker } = room;
if (t === 'd' && (blocked || blocker)) {
return true;
}
}
return false;
};
export const capitalize = s => {
if (typeof s !== 'string') {
return '';
}
return s.charAt(0).toUpperCase() + s.slice(1);
};
export const formatDate = date =>
moment(date).calendar(null, {
lastDay: `[${I18n.t('Yesterday')}]`,
sameDay: 'LT',
lastWeek: 'dddd',
sameElse: 'L'
});
export const formatDateThreads = date =>
moment(date).calendar(null, {
sameDay: 'LT',
lastDay: `[${I18n.t('Yesterday')}] LT`,
lastWeek: 'dddd LT',
sameElse: 'LL'
});
export const getBadgeColor = ({ subscription, messageId, theme }) => {
if (subscription?.tunreadUser?.includes(messageId)) {
return themes[theme].mentionMeColor;
}
if (subscription?.tunreadGroup?.includes(messageId)) {
return themes[theme].mentionGroupColor;
}
if (subscription?.tunread?.includes(messageId)) {
return themes[theme].tunreadColor;
}
};
export const makeThreadName = messageRecord => messageRecord.msg || messageRecord?.attachments[0]?.title;
export const isTeamRoom = ({ teamId, joined }) => teamId && joined;

65
app/utils/room.ts Normal file
View File

@ -0,0 +1,65 @@
import moment from 'moment';
import { themes } from '../constants/colors';
import I18n from '../i18n';
import { IAttachment } from '../definitions/IAttachment';
import { ISubscription, SubscriptionType } from '../definitions/ISubscription';
export const isBlocked = (room: ISubscription): boolean => {
if (room) {
const { t, blocked, blocker } = room;
if (t === SubscriptionType.DIRECT && (blocked || blocker)) {
return true;
}
}
return false;
};
export const capitalize = (s: string): string => {
if (typeof s !== 'string') {
return '';
}
return s.charAt(0).toUpperCase() + s.slice(1);
};
export const formatDate = (date: Date): string =>
moment(date).calendar(null, {
lastDay: `[${I18n.t('Yesterday')}]`,
sameDay: 'LT',
lastWeek: 'dddd',
sameElse: 'L'
});
export const formatDateThreads = (date: Date): string =>
moment(date).calendar(null, {
sameDay: 'LT',
lastDay: `[${I18n.t('Yesterday')}] LT`,
lastWeek: 'dddd LT',
sameElse: 'LL'
});
export const getBadgeColor = ({
subscription,
messageId,
theme
}: {
// TODO: Refactor when migrate model folder
subscription: any;
messageId: string;
theme: string;
}): string | undefined => {
if (subscription?.tunreadUser?.includes(messageId)) {
return themes[theme].mentionMeColor;
}
if (subscription?.tunreadGroup?.includes(messageId)) {
return themes[theme].mentionGroupColor;
}
if (subscription?.tunread?.includes(messageId)) {
return themes[theme].tunreadColor;
}
};
export const makeThreadName = (messageRecord: { id?: string; msg?: string; attachments?: IAttachment[] }): string | undefined =>
messageRecord.msg || messageRecord.attachments![0].title;
export const isTeamRoom = ({ teamId, joined }: { teamId: string; joined: boolean }): boolean => !!teamId && joined;

View File

@ -3,7 +3,7 @@
url = 'https://open.rocket.chat/method'
hostname = 'open.rocket.chat'
*/
export const extractHostname = url => {
export const extractHostname = (url: string): string => {
let hostname;
if (url.indexOf('//') > -1) {

View File

@ -3,7 +3,7 @@
/* eslint-disable object-curly-spacing */
/* eslint-disable comma-spacing */
/* eslint-disable key-spacing */
const ascii = {
const ascii: { [key: string]: string } = {
'*\\0/*': '🙆',
'*\\O/*': '🙆',
'-___-': '😑',

View File

@ -3,7 +3,7 @@
/* eslint-disable object-curly-spacing */
/* eslint-disable comma-spacing */
/* eslint-disable key-spacing */
const emojis = {
const emojis: { [key: string]: string } = {
':england:': '🏴󠁧󠁢󠁥󠁮󠁧󠁿',
':scotland:': '🏴󠁧󠁢󠁳󠁣󠁴󠁿',
':wales:': '🏴󠁧󠁢󠁷󠁬󠁳󠁿',

View File

@ -2,11 +2,11 @@ import emojis from './emojis';
import ascii, { asciiRegexp } from './ascii';
const shortnamePattern = new RegExp(/:[-+_a-z0-9]+:/, 'gi');
const replaceShortNameWithUnicode = shortname => emojis[shortname] || shortname;
const replaceShortNameWithUnicode = (shortname: string) => emojis[shortname] || shortname;
const regAscii = new RegExp(`((\\s|^)${asciiRegexp}(?=\\s|$|[!,.?]))`, 'gi');
const unescapeHTML = string => {
const unescaped = {
const unescapeHTML = (string: string) => {
const unescaped: { [key: string]: string } = {
'&amp;': '&',
'&#38;': '&',
'&#x26;': '&',
@ -27,7 +27,7 @@ const unescapeHTML = string => {
return string.replace(/&(?:amp|#38|#x26|lt|#60|#x3C|gt|#62|#x3E|apos|#39|#x27|quot|#34|#x22);/gi, match => unescaped[match]);
};
const shortnameToUnicode = str => {
const shortnameToUnicode = (str: string): string => {
str = str.replace(shortnamePattern, replaceShortNameWithUnicode);
str = str.replace(regAscii, (entire, m1, m2, m3) => {

View File

@ -9,13 +9,18 @@ import { extractHostname } from './server';
const { SSLPinning } = NativeModules;
const { documentDirectory } = FileSystem;
const extractFileScheme = path => path.replace('file://', ''); // file:// isn't allowed by obj-C
const extractFileScheme = (path: string) => path.replace('file://', ''); // file:// isn't allowed by obj-C
const getPath = name => `${documentDirectory}/${name}`;
const getPath = (name: string) => `${documentDirectory}/${name}`;
const persistCertificate = async (name, password) => {
interface ICertificate {
path: string;
password: string;
}
const persistCertificate = async (name: string, password: string) => {
const certificatePath = getPath(name);
const certificate = {
const certificate: ICertificate = {
path: extractFileScheme(certificatePath),
password
};
@ -29,6 +34,7 @@ const RCSSLPinning = Platform.select({
new Promise(async (resolve, reject) => {
try {
const res = await DocumentPicker.pick({
// @ts-ignore
type: ['com.rsa.pkcs-12']
});
const { uri, name } = res;
@ -42,7 +48,7 @@ const RCSSLPinning = Platform.select({
try {
const certificatePath = getPath(name);
await FileSystem.copyAsync({ from: uri, to: certificatePath });
await persistCertificate(name, password);
await persistCertificate(name, password!);
resolve(name);
} catch (e) {
reject(e);
@ -56,10 +62,10 @@ const RCSSLPinning = Platform.select({
reject(e);
}
}),
setCertificate: async (name, server) => {
setCertificate: async (name: string, server: string) => {
if (name) {
let certificate = await UserPreferences.getMapAsync(name);
if (!certificate.path.match(extractFileScheme(documentDirectory))) {
let certificate = (await UserPreferences.getMapAsync(name)) as ICertificate;
if (!certificate.path.match(extractFileScheme(documentDirectory!))) {
certificate = await persistCertificate(name, certificate.password);
}
await UserPreferences.setMapAsync(extractHostname(server), certificate);

View File

@ -2,12 +2,13 @@ import { Appearance } from 'react-native-appearance';
import changeNavigationBarColor from 'react-native-navigation-bar-color';
import setRootViewColor from 'rn-root-view';
import { IThemePreference, TThemeMode } from '../definitions/ITheme';
import { themes } from '../constants/colors';
import { isAndroid } from './deviceInfo';
let themeListener;
let themeListener: { remove: () => void } | null;
export const defaultTheme = () => {
export const defaultTheme = (): TThemeMode => {
const systemTheme = Appearance.getColorScheme();
if (systemTheme && systemTheme !== 'no-preference') {
return systemTheme;
@ -15,7 +16,7 @@ export const defaultTheme = () => {
return 'light';
};
export const getTheme = themePreferences => {
export const getTheme = (themePreferences: IThemePreference): string => {
const { darkLevel, currentTheme } = themePreferences;
let theme = currentTheme;
if (currentTheme === 'automatic') {
@ -24,7 +25,7 @@ export const getTheme = themePreferences => {
return theme === 'dark' ? darkLevel : 'light';
};
export const newThemeState = (prevState, newTheme) => {
export const newThemeState = (prevState: { themePreferences: IThemePreference }, newTheme: IThemePreference) => {
// new theme preferences
const themePreferences = {
...prevState.themePreferences,
@ -35,12 +36,13 @@ export const newThemeState = (prevState, newTheme) => {
return { themePreferences, theme: getTheme(themePreferences) };
};
export const setNativeTheme = async themePreferences => {
export const setNativeTheme = async (themePreferences: IThemePreference): Promise<void> => {
const theme = getTheme(themePreferences);
if (isAndroid) {
const iconsLight = theme === 'light';
try {
await changeNavigationBarColor(themes[theme].navbarBackground, iconsLight);
// The late param as default is true @ react-native-navigation-bar-color/src/index.js line 8
await changeNavigationBarColor(themes[theme].navbarBackground, iconsLight, true);
} catch (error) {
// Do nothing
}
@ -55,7 +57,7 @@ export const unsubscribeTheme = () => {
}
};
export const subscribeTheme = (themePreferences, setTheme) => {
export const subscribeTheme = (themePreferences: IThemePreference, setTheme: () => void): void => {
const { currentTheme } = themePreferences;
if (!themeListener && currentTheme === 'automatic') {
// not use listener params because we use getTheme

View File

@ -1,26 +0,0 @@
export default function throttle(fn, threshhold = 250, scope) {
let last;
let deferTimer;
const _throttle = (...args) => {
const context = scope || this;
const now = +new Date();
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(() => {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};
_throttle.stop = () => clearTimeout(deferTimer);
return _throttle;
}

View File

@ -1,19 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
import { RectButton } from 'react-native-gesture-handler';
import { RectButton, RectButtonProps } from 'react-native-gesture-handler';
import { themes } from '../constants/colors';
class Touch extends React.Component {
setNativeProps(props) {
interface ITouchProps extends RectButtonProps {
children: React.ReactNode;
theme: string;
accessibilityLabel?: string;
testID?: string;
}
class Touch extends React.Component<ITouchProps> {
private ref: any;
setNativeProps(props: ITouchProps): void {
this.ref.setNativeProps(props);
}
getRef = ref => {
getRef = (ref: RectButton): void => {
this.ref = ref;
};
render() {
render(): JSX.Element {
const { children, onPress, theme, underlayColor, ...props } = this.props;
return (
@ -30,11 +38,4 @@ class Touch extends React.Component {
}
}
Touch.propTypes = {
children: PropTypes.node,
onPress: PropTypes.func,
theme: PropTypes.string,
underlayColor: PropTypes.string
};
export default Touch;

View File

@ -3,13 +3,18 @@ import { settings } from '@rocket.chat/sdk';
import { TWO_FACTOR } from '../containers/TwoFactor';
import EventEmitter from './events';
export const twoFactor = ({ method, invalid }) =>
interface ITwoFactor {
method: string;
invalid: boolean;
}
export const twoFactor = ({ method, invalid }: ITwoFactor): Promise<{ twoFactorCode: string; twoFactorMethod: string }> =>
new Promise((resolve, reject) => {
EventEmitter.emit(TWO_FACTOR, {
method,
invalid,
cancel: () => reject(),
submit: code => {
submit: (code: string) => {
settings.customHeaders = {
...settings.customHeaders,
'x-2fa-code': code,

View File

@ -1,4 +1,4 @@
export const isValidURL = url => {
export const isValidURL = (url: string): boolean => {
const pattern = new RegExp(
'^(https?:\\/\\/)?' + // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
@ -12,4 +12,4 @@ export const isValidURL = url => {
};
// Use useSsl: false only if server url starts with http://
export const useSsl = url => !/http:\/\//.test(url);
export const useSsl = (url: string): boolean => !/http:\/\//.test(url);

View File

@ -32,8 +32,6 @@ const SelectChannel = ({
}, 300);
const getAvatar = (item: any) =>
// TODO: remove this ts-ignore when migrate the file: app/utils/avatar.js
// @ts-ignore
avatarURL({
text: RocketChat.getRoomAvatar(item),
type: item.t,

View File

@ -12,6 +12,7 @@ import { MultiSelect } from '../../containers/UIKit/MultiSelect';
import { themes } from '../../constants/colors';
import styles from './styles';
import { ICreateDiscussionViewSelectUsers } from './interfaces';
import { SubscriptionType } from '../../definitions/ISubscription';
interface IUser {
name: string;
@ -62,11 +63,9 @@ const SelectUsers = ({
}, 300);
const getAvatar = (item: any) =>
// TODO: remove this ts-ignore when migrate the file: app/utils/avatar.js
// @ts-ignore
avatarURL({
text: RocketChat.getRoomAvatar(item),
type: 'd',
type: SubscriptionType.DIRECT,
user: { id: userId, token },
server,
avatarETag: item.avatarETag,

View File

@ -2,6 +2,7 @@ import { RouteProp } from '@react-navigation/core';
import { StackNavigationProp } from '@react-navigation/stack';
import { NewMessageStackParamList } from '../../stacks/types';
import { SubscriptionType } from '../../definitions/ISubscription';
export interface ICreateChannelViewProps {
navigation: StackNavigationProp<NewMessageStackParamList, 'CreateDiscussionView'>;
@ -15,7 +16,7 @@ export interface ICreateChannelViewProps {
loading: boolean;
result: {
rid: string;
t: string;
t: SubscriptionType;
prid: string;
};
failure: boolean;

View File

@ -75,8 +75,6 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
if (!newPassword.trim()) {
return;
}
// TODO: Remove ts-ignore when migrate the showConfirmationAlert
// @ts-ignore
showConfirmationAlert({
title: I18n.t('Are_you_sure_question_mark'),
message: I18n.t('E2E_encryption_change_password_message'),
@ -98,8 +96,6 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
};
resetOwnKey = () => {
// TODO: Remove ts-ignore when migrate the showConfirmationAlert
// @ts-ignore
showConfirmationAlert({
title: I18n.t('Are_you_sure_question_mark'),
message: I18n.t('E2E_encryption_reset_message'),

View File

@ -269,11 +269,10 @@ class NewServerView extends React.Component<INewServerView, IState> {
uriToPath = (uri: string) => uri.replace('file://', '');
handleRemove = () => {
// TODO: Remove ts-ignore when migrate the showConfirmationAlert
// @ts-ignore
showConfirmationAlert({
message: I18n.t('You_will_unset_a_certificate_for_this_server'),
confirmationText: I18n.t('Remove'),
// @ts-ignore
onPress: this.setState({ certificate: null }) // We not need delete file from DocumentPicker because it is a temp file
});
};

View File

@ -424,7 +424,6 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
logoutOtherLocations = () => {
logEvent(events.PL_OTHER_LOCATIONS);
// @ts-ignore
showConfirmationAlert({
message: I18n.t('You_will_be_logged_out_from_other_locations'),
confirmationText: I18n.t('Logout'),

View File

@ -26,9 +26,9 @@ export interface IParams {
}
export interface IAvatarButton {
key: React.Key;
key: string;
child: React.ReactNode;
onPress: Function;
onPress: () => void;
disabled: boolean;
}

View File

@ -2,7 +2,6 @@ import React from 'react';
import { Switch } from 'react-native';
import { connect } from 'react-redux';
import { StackNavigationOptions } from '@react-navigation/stack';
import Model from '@nozbe/watermelondb/Model';
import { Subscription } from 'rxjs';
import I18n from '../i18n';
@ -15,15 +14,10 @@ import { changePasscode, checkHasPasscode, supportedBiometryLabel } from '../uti
import { DEFAULT_AUTO_LOCK } from '../constants/localAuthentication';
import SafeAreaView from '../containers/SafeAreaView';
import { events, logEvent } from '../utils/log';
import { TServerModel } from '../definitions/IServer';
const DEFAULT_BIOMETRY = false;
interface IServerRecords extends Model {
autoLock?: boolean;
autoLockTime?: number;
biometry?: boolean;
}
interface IItem {
title: string;
value: number;
@ -38,14 +32,14 @@ interface IScreenLockConfigViewProps {
}
interface IScreenLockConfigViewState {
autoLock?: boolean;
autoLock: boolean;
autoLockTime?: number | null;
biometry?: boolean;
biometryLabel: null;
biometryLabel: string | null;
}
class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, IScreenLockConfigViewState> {
private serverRecord?: IServerRecords;
private serverRecord?: TServerModel;
private observable?: Subscription;
@ -98,7 +92,7 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
const serversDB = database.servers;
const serversCollection = serversDB.get('servers');
try {
this.serverRecord = await serversCollection.find(server);
this.serverRecord = (await serversCollection.find(server)) as TServerModel;
this.setState({
autoLock: this.serverRecord?.autoLock,
autoLockTime: this.serverRecord?.autoLockTime === null ? DEFAULT_AUTO_LOCK : this.serverRecord?.autoLockTime,
@ -151,7 +145,7 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
const { autoLock } = this.state;
if (autoLock) {
try {
await checkHasPasscode({ force: false, serverRecord: this.serverRecord });
await checkHasPasscode({ force: false, serverRecord: this.serverRecord! });
} catch {
this.toggleAutoLock();
}

View File

@ -88,7 +88,6 @@ class SettingsView extends React.Component<ISettingsViewProps, any> {
handleLogout = () => {
logEvent(events.SE_LOG_OUT);
// @ts-ignore
showConfirmationAlert({
message: I18n.t('You_will_be_logged_out_of_this_application'),
confirmationText: I18n.t('Logout'),
@ -98,7 +97,6 @@ class SettingsView extends React.Component<ISettingsViewProps, any> {
handleClearCache = () => {
logEvent(events.SE_CLEAR_LOCAL_SERVER_CACHE);
/* @ts-ignore */
showConfirmationAlert({
message: I18n.t('This_will_clear_all_your_offline_data'),
confirmationText: I18n.t('Clear'),

View File

@ -25,12 +25,12 @@ import { sanitizeLikeString } from '../../lib/database/utils';
import styles from './styles';
import ShareListHeader from './Header';
interface IFile {
interface IDataFromShare {
value: string;
type: string;
}
interface IAttachment {
interface IFileToShare {
filename: string;
description: string;
size: number;
@ -61,7 +61,7 @@ interface IState {
searchResults: IChat[];
chats: IChat[];
serversCount: number;
attachments: IAttachment[];
attachments: IFileToShare[];
text: string;
loading: boolean;
serverInfo: IServerInfo;
@ -121,7 +121,7 @@ class ShareListView extends React.Component<IShareListViewProps, IState> {
async componentDidMount() {
const { server } = this.props;
try {
const data = (await ShareExtension.data()) as IFile[];
const data = (await ShareExtension.data()) as IDataFromShare[];
if (isAndroid) {
await this.askForPermission(data);
}
@ -136,7 +136,7 @@ class ShareListView extends React.Component<IShareListViewProps, IState> {
size: file.size,
mime: mime.lookup(file.uri),
path: file.uri
})) as IAttachment[];
})) as IFileToShare[];
const text = data.filter(item => item.type === 'text').reduce((acc, item) => `${item.value}\n${acc}`, '');
this.setState({
text,
@ -294,7 +294,7 @@ class ShareListView extends React.Component<IShareListViewProps, IState> {
}
};
askForPermission = async (data: IFile[]) => {
askForPermission = async (data: IDataFromShare[]) => {
const mediaIndex = data.findIndex(item => item.type === 'media');
if (mediaIndex !== -1) {
const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, permission);

View File

@ -39,7 +39,7 @@ interface IShareViewState {
room: ISubscription;
thread: any; // change
maxFileSize: number;
mediaAllowList: number;
mediaAllowList: string;
}
interface IShareViewProps {
@ -52,7 +52,7 @@ interface IShareViewProps {
token: string;
};
server: string;
FileUpload_MediaTypeWhiteList?: number;
FileUpload_MediaTypeWhiteList?: string;
FileUpload_MaxFileSize?: number;
}

View File

@ -11,17 +11,18 @@ import { supportSystemTheme } from '../utils/deviceInfo';
import SafeAreaView from '../containers/SafeAreaView';
import UserPreferences from '../lib/userPreferences';
import { events, logEvent } from '../utils/log';
import { IThemePreference, TThemeMode, TDarkLevel } from '../definitions/ITheme';
const THEME_GROUP = 'THEME_GROUP';
const DARK_GROUP = 'DARK_GROUP';
const SYSTEM_THEME = {
const SYSTEM_THEME: ITheme = {
label: 'Automatic',
value: 'automatic',
group: THEME_GROUP
};
const THEMES = [
const THEMES: ITheme[] = [
{
label: 'Light',
value: 'light',
@ -53,15 +54,10 @@ const darkGroup = THEMES.filter(item => item.group === DARK_GROUP);
interface ITheme {
label: string;
value: string;
value: TThemeMode | TDarkLevel;
group: string;
}
interface IThemePreference {
currentTheme?: string;
darkLevel?: string;
}
interface IThemeViewProps {
theme: string;
themePreferences: IThemePreference;
@ -89,19 +85,19 @@ class ThemeView extends React.Component<IThemeViewProps> {
const { themePreferences } = this.props;
const { darkLevel, currentTheme } = themePreferences;
const { value, group } = item;
let changes: IThemePreference = {};
let changes: Partial<IThemePreference> = {};
if (group === THEME_GROUP && currentTheme !== value) {
logEvent(events.THEME_SET_THEME_GROUP, { theme_group: value });
changes = { currentTheme: value };
changes = { currentTheme: value as TThemeMode };
}
if (group === DARK_GROUP && darkLevel !== value) {
logEvent(events.THEME_SET_DARK_LEVEL, { dark_level: value });
changes = { darkLevel: value };
changes = { darkLevel: value as TDarkLevel };
}
this.setTheme(changes);
};
setTheme = async (theme: IThemePreference) => {
setTheme = async (theme: Partial<IThemePreference>) => {
const { setTheme, themePreferences } = this.props;
const newTheme = { ...themePreferences, ...theme };
setTheme(newTheme);