Skip to content

Commit

Permalink
Fix/mobile init redirection (#1956)
Browse files Browse the repository at this point in the history
* fix: move extra responsibility away from redirection saga

* chore: display errors during on-the-go-events

* test: correct rntl tests

* fix: navigation methods

* test: duplicated username warning

* fix: lint
  • Loading branch information
siepra authored Oct 11, 2023
1 parent d6302b6 commit 9aa44b8
Show file tree
Hide file tree
Showing 14 changed files with 144 additions and 71 deletions.
7 changes: 7 additions & 0 deletions packages/mobile/src/RootNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@ export const replaceScreen = <Params extends Record<string, unknown>>(screen: Sc
navigationRef.dispatch(StackActions.replace(screen, params))
}
}

export const pop = (): void => {
if (navigationRef.isReady()) {
// @ts-ignore
navigationRef.dispatch(StackActions.pop())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const PossibleImpersonationAttackComponent: React.FC<PossibleImpersonationAttack
return (
<View
style={{ flex: 1, backgroundColor: defaultTheme.palette.background.white }}
data-testid={'possible-impersonation-attack-component'}
testID={'possible-impersonation-attack-component'}
>
<Appbar title={'Warning!'} back={handleBackButton} crossBackIcon />
<View style={classes.mainWrapper}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ describe('PossibleImpersonationAttack component', () => {

expect(toJSON()).toMatchInlineSnapshot(`
<View
data-testid="possible-impersonation-attack-component"
style={
{
"backgroundColor": "#ffffff",
"flex": 1,
}
}
testID="possible-impersonation-attack-component"
>
<View
style={
Expand Down
29 changes: 26 additions & 3 deletions packages/mobile/src/screens/ChannelList/ChannelList.screen.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { FC, useCallback } from 'react'
import React, { FC, useCallback, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'

import { communities, publicChannels } from '@quiet/state-manager'
import { communities, identity, users, publicChannels } from '@quiet/state-manager'
import { getChannelNameFromChannelId } from '@quiet/common'

import { ChannelList as ChannelListComponent } from '../../components/ChannelList/ChannelList.component'
import { ChannelTileProps } from '../../components/ChannelTile/ChannelTile.types'
Expand All @@ -12,11 +13,31 @@ import { formatMessageDisplayDate } from '../../utils/functions/formatMessageDis

import { useContextMenu } from '../../hooks/useContextMenu'
import { MenuName } from '../../const/MenuNames.enum'
import { getChannelNameFromChannelId } from '@quiet/common'

export const ChannelListScreen: FC = () => {
const dispatch = useDispatch()

const usernameTaken = useSelector(identity.selectors.usernameTaken)
const duplicateCerts = useSelector(users.selectors.duplicateCerts)

useEffect(() => {
if (usernameTaken) {
dispatch(
navigationActions.navigation({
screen: ScreenNames.UsernameTakenScreen,
})
)
}

if (duplicateCerts) {
dispatch(
navigationActions.navigation({
screen: ScreenNames.PossibleImpersonationAttackScreen,
})
)
}
}, [dispatch, usernameTaken, duplicateCerts])

const redirect = useCallback(
(id: string) => {
dispatch(
Expand All @@ -34,10 +55,12 @@ export const ChannelListScreen: FC = () => {
)

const community = useSelector(communities.selectors.currentCommunity)

const channelsStatusSorted = useSelector(publicChannels.selectors.channelsStatusSorted)

const tiles = channelsStatusSorted.map(status => {
const newestMessage = status.newestMessage

const message = newestMessage?.message || '...'
const date = newestMessage?.createdAt ? formatMessageDisplayDate(newestMessage.createdAt) : undefined

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ import { navigationActions } from '../../store/navigation/navigation.slice'

const UsernameTakenScreen: React.FC<UsernameTakenScreenProps> = () => {
const dispatch = useDispatch()

const currentIdentity = useSelector(identity.selectors.currentIdentity)

const usernameRegistered = currentIdentity?.userCertificate != null
const error = useSelector(errors.selectors.registrarErrors)

const registeredUsers = useSelector(users.selectors.certificatesMapping)

const error = useSelector(errors.selectors.registrarErrors)

const handleBackButton = useCallback(() => {
dispatch(
navigationActions.replaceScreen({
screen: ScreenNames.ChannelListScreen,
})
)
dispatch(navigationActions.pop())
}, [dispatch])

const handleAction = (nickname: string) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ describe('showNotificationSaga', () => {
},
[StoreKeys.Navigation]: {
...new NavigationState(),
currentScreen: ScreenNames.ChannelScreen,
backStack: [ScreenNames.ChannelScreen],
},
}
)
Expand Down Expand Up @@ -174,7 +174,7 @@ describe('showNotificationSaga', () => {
},
[StoreKeys.Navigation]: {
...new NavigationState(),
currentScreen: ScreenNames.ChannelScreen,
backStack: [ScreenNames.ChannelScreen],
},
}
)
Expand Down Expand Up @@ -213,7 +213,7 @@ describe('showNotificationSaga', () => {
},
[StoreKeys.Navigation]: {
...new NavigationState(),
currentScreen: ScreenNames.ChannelScreen,
backStack: [ScreenNames.ChannelScreen],
},
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { navigationActions } from './navigation.slice'
import { redirectionSaga } from './redirection/redirection.saga'
import { navigationSaga } from './navigation/navigation.saga'
import { replaceScreenSaga } from './replaceScreen/replaceScreen.saga'
import { popSaga } from './pop/pop.saga'

export function* navigationMasterSaga(): Generator {
yield all([
takeEvery(navigationActions.redirection.type, redirectionSaga),
takeEvery(navigationActions.navigation.type, navigationSaga),
takeEvery(navigationActions.replaceScreen.type, replaceScreenSaga),
takeEvery(navigationActions.pop.type, popSaga),
])
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CreatedSelectors, StoreState } from '../store.types'

const navigationSlice: CreatedSelectors[StoreKeys.Navigation] = (state: StoreState) => state[StoreKeys.Navigation]

export const currentScreen = createSelector(navigationSlice, reducerState => reducerState.currentScreen)
export const currentScreen = createSelector(navigationSlice, reducerState => reducerState.backStack.slice(-1)[0])

export const contextMenuVisibility = (menu: MenuName) =>
createSelector(navigationSlice, reducerState => {
Expand Down
11 changes: 7 additions & 4 deletions packages/mobile/src/store/navigation/navigation.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ScreenNames } from '../../const/ScreenNames.enum'
import { MenuName } from '../../const/MenuNames.enum'

export class NavigationState {
public currentScreen: ScreenNames = ScreenNames.SplashScreen
public backStack: ScreenNames[] = [ScreenNames.SplashScreen]
public confirmationBox: ConfirmationBox = {
open: false,
args: {},
Expand Down Expand Up @@ -53,16 +53,19 @@ export const navigationSlice = createSlice({
redirection: state => state,
navigation: (state, action: PayloadAction<NavigationPayload>) => {
const { screen } = action.payload
state.currentScreen = screen
state.backStack.push(screen)
},
// Replace screen overrides last screen in backstack
replaceScreen: (state, action: PayloadAction<NavigationPayload>) => {
const { screen } = action.payload
state.currentScreen = screen
state.backStack.pop()
state.backStack.push(screen)
},
pop: state => {
state.backStack.pop()
},
setPendingNavigation: (state, action: PayloadAction<PendingNavigationPayload>) => {
const { screen } = action.payload
state.currentScreen = screen
state.pendingNavigation = screen
},
clearPendingNavigation: state => {
Expand Down
8 changes: 8 additions & 0 deletions packages/mobile/src/store/navigation/pop/pop.saga.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { PayloadAction } from '@reduxjs/toolkit'
import { call } from 'typed-redux-saga'
import { navigationActions } from '../navigation.slice'
import { pop } from '../../../RootNavigation'

export function* popSaga(_action: PayloadAction<ReturnType<typeof navigationActions.pop>['payload']>): Generator {
yield* call(pop)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { initSelectors } from '../../init/init.selectors'
import { navigationSelectors } from '../navigation.selectors'
import { navigationActions } from '../navigation.slice'
import { ScreenNames } from '../../../const/ScreenNames.enum'
import { identity, publicChannels, users } from '@quiet/state-manager'
import { identity } from '@quiet/state-manager'
import { initActions } from '../../init/init.slice'

export function* redirectionSaga(): Generator {
Expand All @@ -25,34 +25,10 @@ export function* redirectionSaga(): Generator {
return
}

const isUsernameTaken = yield* select(identity.selectors.usernameTaken)

if (isUsernameTaken) {
yield* put(
navigationActions.replaceScreen({
screen: ScreenNames.UsernameTakenScreen,
})
)
}

const duplicateCerts = yield* select(users.selectors.duplicateCerts)

if (duplicateCerts) {
yield* put(
navigationActions.replaceScreen({
screen: ScreenNames.PossibleImpersonationAttackScreen,
})
)
return
}

// If user belongs to a community, let him directly into the app
// Check while QA session!
const currentChannelDisplayableMessages = yield* select(publicChannels.selectors.currentChannelMessagesMergedBySender)
const areMessagesLoaded = Object.values(currentChannelDisplayableMessages).length > 0
const communityMembership = yield* select(identity.selectors.communityMembership)

if (communityMembership && areMessagesLoaded) {
if (communityMembership) {
yield* put(
navigationActions.replaceScreen({
screen: ScreenNames.ChannelListScreen,
Expand Down
68 changes: 68 additions & 0 deletions packages/mobile/src/tests/duplicateUsername.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react'
import '@testing-library/jest-native/extend-expect'
import MockedSocket from 'socket.io-mock'
import { ioMock } from '../setupTests'
import { prepareStore } from './utils/prepareStore'
import { renderComponent } from './utils/renderComponent'
import { FactoryGirl } from 'factory-girl'
import { getFactory, communities, identity, users } from '@quiet/state-manager'
import { ScreenNames } from '../const/ScreenNames.enum'
import { initActions } from '../store/init/init.slice'
import { ChannelListScreen } from '../screens/ChannelList/ChannelList.screen'
import { navigationSelectors } from '../store/navigation/navigation.selectors'
import { navigationActions } from '../store/navigation/navigation.slice'

describe('Duplicate username warning', () => {
let socket: MockedSocket

let factory: FactoryGirl

beforeEach(async () => {
socket = new MockedSocket()
ioMock.mockImplementation(() => socket)
})

it("Display prompt for username change if it's already taken", async () => {
const { store, root } = await prepareStore({}, socket)

store.dispatch(initActions.setStoreReady())

factory = await getFactory(store)

const community = await factory.create<ReturnType<typeof communities.actions.addNewCommunity>['payload']>(
'Community'
)

const alice = (
await factory.build<typeof identity.actions.addNewIdentity>('Identity', {
id: community.id,
nickname: 'alice',
})
).payload

store.dispatch(
users.actions.storeUserCertificate({
certificate: alice.userCertificate || 'certificate_alice',
})
)

await factory.create<ReturnType<typeof identity.actions.addNewIdentity>['payload']>('Identity', {
id: community.id,
nickname: 'alice',
userCertificate: null,
})

store.dispatch(navigationActions.navigation({ screen: ScreenNames.ChannelListScreen }))

renderComponent(<ChannelListScreen />, store)

// Confirm there's duplication of usernames
const usernameTaken = identity.selectors.usernameTaken(store.getState())
expect(usernameTaken).toBe(true)

const currentScreen = navigationSelectors.currentScreen(store.getState())
expect(currentScreen).toBe(ScreenNames.UsernameTakenScreen)

root?.cancel()
})
})
32 changes: 9 additions & 23 deletions packages/mobile/src/tests/possibleImpersonationAttack.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import React from 'react'
import '@testing-library/jest-native/extend-expect'
import { act } from '@testing-library/react-native'
import MockedSocket from 'socket.io-mock'
import { ioMock } from '../setupTests'
import { prepareStore } from './utils/prepareStore'
import { renderComponent } from './utils/renderComponent'
import { FactoryGirl } from 'factory-girl'
import { getFactory, communities, identity, users } from '@quiet/state-manager'
import { navigationActions } from '../store/navigation/navigation.slice'
import { PossibleImpersonationAttackScreen } from '../screens/PossibleImpersonationAttack/PossibleImpersonationAttack.screen'
import { ScreenNames } from '../const/ScreenNames.enum'
import { navigationSelectors } from '../store/navigation/navigation.selectors'
import { ChannelListScreen } from '../screens/ChannelList/ChannelList.screen'
import { initActions } from '../store/init/init.slice'
import { navigationSelectors } from '../store/navigation/navigation.selectors'

describe('Possible Impersonation Attack', () => {
let socket: MockedSocket
Expand All @@ -23,9 +21,11 @@ describe('Possible Impersonation Attack', () => {
ioMock.mockImplementation(() => socket)
})

it('Open modal when certifcates are duplicated', async () => {
it('Display warning when certifcates are duplicated', async () => {
const { store, root } = await prepareStore({}, socket)

store.dispatch(initActions.setStoreReady())

factory = await getFactory(store)

const community = await factory.create<ReturnType<typeof communities.actions.addNewCommunity>['payload']>(
Expand All @@ -37,17 +37,6 @@ describe('Possible Impersonation Attack', () => {
nickname: 'alice',
})

const route: { key: string; name: ScreenNames.PossibleImpersonationAttackScreen; path?: string | undefined } = {
key: '',
name: ScreenNames.PossibleImpersonationAttackScreen,
}
renderComponent(
<>
<PossibleImpersonationAttackScreen route={route} />
</>,
store
)

const cert1 =
'MIIDeDCCAx6gAwIBAgIGAYr6Jw3hMAoGCCqGSM49BAMCMA8xDTALBgNVBAMTBGZyZmQwHhcNMjMxMDA0MTAwNjE4WhcNMzAwMTMxMjMwMDAwWjBJMUcwRQYDVQQDEz5tenhydWhyNWJzdGt3dmp3eWJnZ2Y2M2Jma2dreWw0aWs0bG5lanN1YnFlaG9td3Vpbm43d3JxZC5vbmlvbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGcKhXLUNjkS9+xd0hYfJBOA7bXB5LwZojhzgBQps3SW/CSR6ABiAuirdP0x/byxTXSkZY23lBkvc5CqMjWe3lWjggIqMIICJjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIAgDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwggFHBgkqhkiG9w0BCQwEggE4BIIBNAXhwXxmLy7Gg5uonlWXiqRUimGLj2cPbAoK9DnKHkcohqdLvEzyz6rM7KBewO068fag0d/PR0uh37Oyb7d/JAbBjhJmf8wOl2HfLTThPEEH8isy3bxHXx4Ir5prVVk1zx8UiXtPAu6gK41FY5Oin6SpV07MBewqQGcbCovcbBSwkp6EXmLXPOGgmpFlQf5CNGIs3YqPD+Ll1vn8Lq5QIGCa210Pq/T65mrPsXVAw2vJO6DFRIAGrAF5VxDS8G2dSwnDnje+bD2NO8qlfwFdO3bkDeheOqZXCSxlPA6q1bY34qYR2zrwSiQCjRiCQjifRCmF2Jg4ojzLGUL0pKdvi+8fDQXollmazh5boJWN9GRy+1sDLTk01cW2kF7esew5PlDi8kX0v2hY+XsR5eQga1j3MkXkMBgGCisGAQQBg4wbAgEEChMIdXNlcm5hbWUwPQYJKwYBAgEPAwEBBDATLlFtUHF6THFheFk1UmI4VlpjM1VuZmlXZXRxVDZKWkp6SnRjWVFXNmhXTENvRXIwSQYDVR0RBEIwQII+bXp4cnVocjVic3Rrd3Zqd3liZ2dmNjNiZmtna3lsNGlrNGxuZWpzdWJxZWhvbXd1aW5uN3dycWQub25pb24wCgYIKoZIzj0EAwIDSAAwRQIhAJx4YX8KtOA4WGAWzW0M7FvuoblNOb370521GsfuHfbMAiBEYQ4l074oUEF2DTVK1agJlhMR5USRxav5xEpx2ujMeA=='

Expand All @@ -66,17 +55,14 @@ describe('Possible Impersonation Attack', () => {
})
)

await act(async () => {})

store.dispatch(navigationActions.redirection())

await act(async () => {})
renderComponent(<ChannelListScreen />, store)

const duplicateCerts = users.selectors.duplicateCerts(store.getState())
const currentScreen = navigationSelectors.currentScreen(store.getState())
expect(duplicateCerts).toBe(true)

const currentScreen = navigationSelectors.currentScreen(store.getState())
expect(currentScreen).toBe(ScreenNames.PossibleImpersonationAttackScreen)
expect(duplicateCerts).toBeTruthy()

root?.cancel()
})
})
Loading

0 comments on commit 9aa44b8

Please sign in to comment.