diff --git a/app/containers/Avatar/index.tsx b/app/containers/Avatar/index.tsx index 637596410..5b14fe154 100644 --- a/app/containers/Avatar/index.tsx +++ b/app/containers/Avatar/index.tsx @@ -37,6 +37,21 @@ class AvatarContainer extends React.Component { } } + shouldComponentUpdate(nextProps: IAvatar, nextState: { avatarETag: string }) { + const { avatarETag } = this.state; + const { text, type } = this.props; + if (nextState.avatarETag !== avatarETag) { + return true; + } + if (nextProps.text !== text) { + return true; + } + if (nextProps.type !== type) { + return true; + } + return false; + } + componentWillUnmount() { if (this.subscription?.unsubscribe) { this.subscription.unsubscribe(); diff --git a/app/definitions/ICredentials.ts b/app/definitions/ICredentials.ts index 072b8470e..17d03a8b2 100644 --- a/app/definitions/ICredentials.ts +++ b/app/definitions/ICredentials.ts @@ -1,4 +1,5 @@ export interface ICredentials { + resume?: string; user?: string; password?: string; username?: string; diff --git a/app/lib/database/index.ts b/app/lib/database/index.ts index 1b8df955b..d85e826d6 100644 --- a/app/lib/database/index.ts +++ b/app/lib/database/index.ts @@ -66,7 +66,7 @@ export const getDatabase = (database = ''): Database => { }; interface IDatabases { - shareDB?: TAppDatabase; + shareDB?: TAppDatabase | null; serversDB: TServerDatabase; activeDB?: TAppDatabase; } diff --git a/app/lib/rocketchat/rocketchat.js b/app/lib/rocketchat/rocketchat.js index 5968d5d45..639a42494 100644 --- a/app/lib/rocketchat/rocketchat.js +++ b/app/lib/rocketchat/rocketchat.js @@ -2,13 +2,10 @@ import { Q } from '@nozbe/watermelondb'; import AsyncStorage from '@react-native-community/async-storage'; import { InteractionManager } from 'react-native'; import { setActiveUsers } from '../../actions/activeUsers'; -import { encryptionInit } from '../../actions/encryption'; import { setUser } from '../../actions/login'; -import { shareSelectServer, shareSetSettings, shareSetUser } from '../../actions/share'; import defaultSettings from '../../constants/settings'; import { getDeviceToken } from '../../notifications/push'; import log from '../../utils/log'; -import SSLPinning from '../../utils/sslPinning'; import database from '../database'; import triggerBlockAction, { triggerCancel, triggerSubmitView } from '../methods/actions'; import callJitsi, { callJitsiWithoutServer } from '../methods/callJitsi'; @@ -65,7 +62,8 @@ import { stopListener, connect } from './services/connect'; -import * as restAPis from './services/restApi'; +import { shareExtensionInit, closeShareExtension } from './services/shareExtension'; +import * as restApis from './services/restApi'; const TOKEN_KEY = 'reactnativemeteor_usertoken'; const CURRENT_SERVER = 'currentServer'; @@ -81,7 +79,7 @@ const RocketChat = { TOKEN_KEY, CURRENT_SERVER, CERTIFICATE_KEY, - ...restAPis, + ...restApis, ...search, callJitsi, callJitsiWithoutServer, @@ -109,81 +107,11 @@ const RocketChat = { checkAndReopen, disconnect, connect, - async shareExtensionInit(server) { - database.setShareDB(server); - - try { - const certificate = UserPreferences.getString(`${RocketChat.CERTIFICATE_KEY}-${server}`); - SSLPinning.setCertificate(certificate, server); - } catch { - // Do nothing - } - - this.shareSDK = sdk.disconnect(); - this.shareSDK = sdk.initialize(server); - - // set Server - const currentServer = { server }; - const serversDB = database.servers; - const serversCollection = serversDB.get('servers'); - try { - const serverRecord = await serversCollection.find(server); - currentServer.version = serverRecord.version; - } catch { - // Record not found - } - reduxStore.dispatch(shareSelectServer(currentServer)); - - RocketChat.setCustomEmojis(); - - try { - // set Settings - const settings = ['Accounts_AvatarBlockUnauthenticatedAccess']; - const db = database.active; - const settingsCollection = db.get('settings'); - const settingsRecords = await settingsCollection.query(Q.where('id', Q.oneOf(settings))).fetch(); - const parsed = Object.values(settingsRecords).map(item => ({ - _id: item.id, - valueAsString: item.valueAsString, - valueAsBoolean: item.valueAsBoolean, - valueAsNumber: item.valueAsNumber, - valueAsArray: item.valueAsArray, - _updatedAt: item._updatedAt - })); - reduxStore.dispatch(shareSetSettings(this.parseSettings(parsed))); - - // set User info - const userId = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${server}`); - const userCollections = serversDB.get('users'); - let user = null; - if (userId) { - const userRecord = await userCollections.find(userId); - user = { - id: userRecord.id, - token: userRecord.token, - username: userRecord.username, - roles: userRecord.roles - }; - } - reduxStore.dispatch(shareSetUser(user)); - await RocketChat.login({ resume: user.token }); - reduxStore.dispatch(encryptionInit()); - } catch (e) { - log(e); - } - }, - closeShareExtension() { - this.shareSDK = sdk.disconnect(); - database.share = null; - - reduxStore.dispatch(shareSelectServer({})); - reduxStore.dispatch(shareSetUser({})); - reduxStore.dispatch(shareSetSettings({})); - }, + shareExtensionInit, + closeShareExtension, async e2eFetchMyKeys() { // RC 0.70.0 - const sdk = this.shareSDK || this.sdk; const result = await sdk.get('e2e.fetchMyKeys'); // snake_case -> camelCase if (result.success) { diff --git a/app/lib/rocketchat/services/sdk.ts b/app/lib/rocketchat/services/sdk.ts index 41c124b11..d5aa3cead 100644 --- a/app/lib/rocketchat/services/sdk.ts +++ b/app/lib/rocketchat/services/sdk.ts @@ -9,19 +9,27 @@ import { Serialized, MatchPathPattern, OperationParams, PathFor, ResultFor } fro class Sdk { private sdk: typeof Rocketchat; + private shareSdk?: typeof Rocketchat; private code: any; + private initializeSdk(server: string): typeof Rocketchat { + // The app can't reconnect if reopen interval is 5s while in development + return new Rocketchat({ host: server, protocol: 'ddp', useSsl: useSsl(server), reopen: __DEV__ ? 20000 : 5000 }); + } + // TODO: We need to stop returning the SDK after all methods are dehydrated initialize(server: string) { this.code = null; - - // The app can't reconnect if reopen interval is 5s while in development - this.sdk = new Rocketchat({ host: server, protocol: 'ddp', useSsl: useSsl(server), reopen: __DEV__ ? 20000 : 5000 }); + this.sdk = this.initializeSdk(server); return this.sdk; } + initializeShareExtension(server: string) { + this.shareSdk = this.initializeSdk(server); + } + get current() { - return this.sdk; + return this.shareSdk || this.sdk; } /** @@ -29,6 +37,11 @@ class Sdk { * I'm returning "null" because we need to remove both instances of this.sdk here and on rocketchat.js */ disconnect() { + if (this.shareSdk) { + this.shareSdk.disconnect(); + this.shareSdk = null; + return null; + } if (this.sdk) { this.sdk.disconnect(); this.sdk = null; @@ -47,7 +60,7 @@ class Sdk { ? void : Serialized>> ): Promise>>> { - return this.sdk.get(endpoint, params); + return this.current.get(endpoint, params); } post>( @@ -64,7 +77,7 @@ class Sdk { return new Promise(async (resolve, reject) => { const isMethodCall = endpoint?.startsWith('method.call/'); try { - const result = await this.sdk.post(endpoint, params); + const result = await this.current.post(endpoint, params); /** * if API_Use_REST_For_DDP_Calls is enabled and it's a method call, @@ -101,7 +114,7 @@ class Sdk { methodCall(...args: any[]): Promise { return new Promise(async (resolve, reject) => { try { - const result = await this.sdk?.methodCall(...args, this.code || ''); + const result = await this.current.methodCall(...args, this.code || ''); return resolve(result); } catch (e: any) { if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) { @@ -140,23 +153,23 @@ class Sdk { } subscribe(...args: any[]) { - return this.sdk.subscribe(...args); + return this.current.subscribe(...args); } subscribeRaw(...args: any[]) { - return this.sdk.subscribeRaw(...args); + return this.current.subscribeRaw(...args); } subscribeRoom(...args: any[]) { - return this.sdk.subscribeRoom(...args); + return this.current.subscribeRoom(...args); } unsubscribe(subscription: any[]) { - return this.sdk.unsubscribe(subscription); + return this.current.unsubscribe(subscription); } onStreamData(...args: any[]) { - return this.sdk.onStreamData(...args); + return this.current.onStreamData(...args); } } diff --git a/app/lib/rocketchat/services/shareExtension.ts b/app/lib/rocketchat/services/shareExtension.ts new file mode 100644 index 000000000..ad83eb989 --- /dev/null +++ b/app/lib/rocketchat/services/shareExtension.ts @@ -0,0 +1,92 @@ +import { Q } from '@nozbe/watermelondb'; + +import { shareSetSettings, shareSelectServer, shareSetUser } from '../../../actions/share'; +import SSLPinning from '../../../utils/sslPinning'; +import log from '../../../utils/log'; +import { IShareServer, IShareUser } from '../../../reducers/share'; +import UserPreferences from '../../userPreferences'; +import database from '../../database'; +import RocketChat from '../rocketchat'; +import { encryptionInit } from '../../../actions/encryption'; +import { store } from '../../auxStore'; +import sdk from './sdk'; + +export async function shareExtensionInit(server: string) { + database.setShareDB(server); + + try { + const certificate = UserPreferences.getString(`${RocketChat.CERTIFICATE_KEY}-${server}`); + if (SSLPinning && certificate) { + await SSLPinning.setCertificate(certificate, server); + } + } catch { + // Do nothing + } + + // sdk.current.disconnect(); + sdk.initializeShareExtension(server); + + // set Server + const currentServer: IShareServer = { + server, + version: '' + }; + const serversDB = database.servers; + const serversCollection = serversDB.get('servers'); + try { + const serverRecord = await serversCollection.find(server); + currentServer.version = serverRecord.version; + } catch { + // Record not found + } + store.dispatch(shareSelectServer(currentServer)); + + RocketChat.setCustomEmojis(); + + try { + // set Settings + const settings = ['Accounts_AvatarBlockUnauthenticatedAccess']; + const db = database.active; + const settingsCollection = db.get('settings'); + const settingsRecords = await settingsCollection.query(Q.where('id', Q.oneOf(settings))).fetch(); + const parsed = Object.values(settingsRecords).map(item => ({ + _id: item.id, + valueAsString: item.valueAsString, + valueAsBoolean: item.valueAsBoolean, + valueAsNumber: item.valueAsNumber, + valueAsArray: item.valueAsArray, + _updatedAt: item._updatedAt + })); + store.dispatch(shareSetSettings(RocketChat.parseSettings(parsed))); + + // set User info + const userId = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${server}`); + const userCollections = serversDB.get('users'); + let user = null; + if (userId) { + const userRecord = await userCollections.find(userId); + user = { + id: userRecord.id, + token: userRecord.token, + username: userRecord.username, + roles: userRecord.roles + }; + } + store.dispatch(shareSetUser(user as IShareUser)); + if (user) { + await RocketChat.login({ resume: user.token }); + } + store.dispatch(encryptionInit()); + } catch (e) { + log(e); + } +} + +export function closeShareExtension() { + sdk.disconnect(); + database.share = null; + + store.dispatch(shareSelectServer({})); + store.dispatch(shareSetUser({})); + store.dispatch(shareSetSettings({})); +} diff --git a/app/reducers/share.ts b/app/reducers/share.ts index 03904ec46..d62f9c584 100644 --- a/app/reducers/share.ts +++ b/app/reducers/share.ts @@ -2,17 +2,17 @@ import { TActionsShare } from '../actions/share'; import { SHARE } from '../actions/actionsTypes'; export interface IShareServer { - server: string; - version: string; + server?: string; + version?: string; } export type TShareSettings = Record; export interface IShareUser { - id: string; - token: string; - username: string; - roles: string[]; + id?: string; + token?: string; + username?: string; + roles?: string[]; } export interface IShare { @@ -22,8 +22,8 @@ export interface IShare { } export const initialState: IShare = { - user: {} as IShareUser, - server: {} as IShareServer, + user: {}, + server: {}, settings: {} }; diff --git a/app/share.tsx b/app/share.tsx index 1dddbe874..a518a1eba 100644 --- a/app/share.tsx +++ b/app/share.tsx @@ -10,6 +10,7 @@ import UserPreferences from './lib/userPreferences'; import Navigation from './lib/ShareNavigation'; import store from './lib/createStore'; import { initStore } from './lib/auxStore'; +import { closeShareExtension, shareExtensionInit } from './lib/rocketchat/services/shareExtension'; import { defaultHeader, getActiveRouteName, navigationTheme, themedHeader } from './utils/navigation'; import RocketChat from './lib/rocketchat'; import { ThemeContext } from './theme'; @@ -107,7 +108,7 @@ class Root extends React.Component<{}, IState> { } componentWillUnmount(): void { - RocketChat.closeShareExtension(); + closeShareExtension(); unsubscribeTheme(); } @@ -117,7 +118,7 @@ class Root extends React.Component<{}, IState> { if (currentServer) { await localAuthenticate(currentServer); this.setState({ root: 'inside' }); - await RocketChat.shareExtensionInit(currentServer); + await shareExtensionInit(currentServer); } else { this.setState({ root: 'outside' }); } diff --git a/app/views/SelectServerView.tsx b/app/views/SelectServerView.tsx index ac1f329d1..fcc4307ed 100644 --- a/app/views/SelectServerView.tsx +++ b/app/views/SelectServerView.tsx @@ -7,7 +7,7 @@ import { Q, Model } from '@nozbe/watermelondb'; import I18n from '../i18n'; import StatusBar from '../containers/StatusBar'; import ServerItem, { ROW_HEIGHT } from '../presentation/ServerItem'; -import RocketChat from '../lib/rocketchat'; +import { shareExtensionInit } from '../lib/rocketchat/services/shareExtension'; import database from '../lib/database'; import SafeAreaView from '../containers/SafeAreaView'; import * as List from '../containers/List'; @@ -50,7 +50,7 @@ class SelectServerView extends React.Component