From 28672643b4e91d4782cbd3b7aae04b769000d4c7 Mon Sep 17 00:00:00 2001 From: Wiktor Sieprawski Date: Tue, 30 Jan 2024 15:04:32 +0100 Subject: [PATCH] fix: deep linking issues (#2154) * fix: mark redux store ready after leaving community #1970 * test: fix deepLink tests * chore: fix lint * chore: cleanup * chore: update CHANGELOG.md * chore: add debugging logs * fix: reset deep link flag * chore: cleanup iOS * fix: correct redux cleanup order * chore: comment out persistor flushing * chore: add debug log for launching application * chore: log socket id on its lifecycle callbacks * fix: deep link redirection * fix: undo comment out persistor cleanup * fix: tests * fix: lint * chore: undo CHANGELOG.md changes * chore: update CHANGELOG.md * chore: cleanup * chore: cleanup --- .DS_Store | Bin 6148 -> 6148 bytes CHANGELOG.md | 6 + packages/.DS_Store | Bin 8196 -> 6148 bytes packages/mobile/ios/Podfile.lock | 2 +- packages/mobile/ios/Quiet/AppDelegate.m | 4 +- packages/mobile/ios/TorHandler.swift | 4 - packages/mobile/src/App.tsx | 6 +- .../blindConnection/blindConnection.saga.ts | 9 +- .../store/init/deepLink/deepLink.saga.test.ts | 132 ++++++++++-------- .../src/store/init/deepLink/deepLink.saga.ts | 96 +++++++++---- packages/mobile/src/store/init/init.slice.ts | 1 + .../restoreConnection.saga.ts | 3 + .../startConnection/startConnection.saga.ts | 16 ++- .../leaveCommunity/leaveCommunity.saga.ts | 7 +- .../mobile/src/tests/deep.linking.test.tsx | 4 +- .../areObjectsEqual/areObjectsEqual.ts | 3 + 16 files changed, 192 insertions(+), 101 deletions(-) create mode 100644 packages/mobile/src/utils/functions/areObjectsEqual/areObjectsEqual.ts diff --git a/.DS_Store b/.DS_Store index 31ef813956be0676b361b18778c58f9025623f7e..13659620daf080d8b26750f60d9594ce9f9fc0e7 100644 GIT binary patch delta 31 ncmZoMXfc@J&&azmU^g=(?_?g9?9E$Qf|w^Z*llL#_{$Ffo~#NF delta 80 zcmZoMXfc@J&&aniU^g=(-(((^Y-4_g9EMbeB8EJMLw#Era}G-^^JaFAzx)9H=@O3s diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f583ad97b..0c3816eb24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +[2.1.1] - unreleased + +# Fixes: + +* Opening the mobile app with joining links has been corrected. + [2.1.0] # New features: diff --git a/packages/.DS_Store b/packages/.DS_Store index bfe08eb44a46166782f3c12869e7a444a3efa7ed..0d635ae46e83e031e99b7dd60f7cff65bc5f4775 100644 GIT binary patch delta 103 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{Mvv5r;6q~50$jG}fU^g=(?`9r>rOXq{!WOf0 pa0oI36##(%H;`}zsohxkoq009j3-DZ0~5p)kiiU_<9X&V0{~}Z5D5SP delta 202 zcmZoMXmOBWU|?W$DortDU;r^WfEYvza8E20o2aMA$hR?IH}hr%jz7$c**Q2SHn1@A zZRTNF${er6P{NSPPz)rK84?-NfNWicB!(h}e1>wMXeyAU%TNI3moa2Aq(DVd8FGNE zWS}mP$~@1U{N$vZ{3MW#KO9Yxxg@xOg03JJZ5HJC&ODi4#B;Jg R4+jS$#IX#U<9X&V0{{SuElL0Y diff --git a/packages/mobile/ios/Podfile.lock b/packages/mobile/ios/Podfile.lock index 201e5c9a3b..6d0a4dac61 100644 --- a/packages/mobile/ios/Podfile.lock +++ b/packages/mobile/ios/Podfile.lock @@ -615,4 +615,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 986ea3b4bace0fe04a792347f3dc6f060797e84d -COCOAPODS: 1.13.0 +COCOAPODS: 1.14.3 diff --git a/packages/mobile/ios/Quiet/AppDelegate.m b/packages/mobile/ios/Quiet/AppDelegate.m index 7706f61020..032ea7a268 100644 --- a/packages/mobile/ios/Quiet/AppDelegate.m +++ b/packages/mobile/ios/Quiet/AppDelegate.m @@ -113,14 +113,16 @@ - (void) spinupBackend:(BOOL)init { // (1/6) Find ports to use in tor and backend configuration - FindFreePort *findFreePort = [FindFreePort new]; Utils *utils = [Utils new]; if (self.socketIOSecret == nil) { self.socketIOSecret = [utils generateSecretWithLength:(20)]; } + FindFreePort *findFreePort = [FindFreePort new]; + self.dataPort = [findFreePort getFirstStartingFromPort:11000]; + uint16_t socksPort = [findFreePort getFirstStartingFromPort:12000]; uint16_t controlPort = [findFreePort getFirstStartingFromPort:14000]; uint16_t httpTunnelPort = [findFreePort getFirstStartingFromPort:16000]; diff --git a/packages/mobile/ios/TorHandler.swift b/packages/mobile/ios/TorHandler.swift index 801284c106..1a62ade0f4 100644 --- a/packages/mobile/ios/TorHandler.swift +++ b/packages/mobile/ios/TorHandler.swift @@ -129,8 +129,6 @@ class TorHandler: NSObject { } guard let cookie = auth else { - print("[\(String(describing: type(of: self)))] Could not connect to Tor - cookie unreadable!") - return nil } @@ -148,8 +146,6 @@ class TorHandler: NSObject { } guard let cookie = auth else { - print("[\(String(describing: type(of: self)))] Could not connect to Tor - cookie unreadable!") - return nil } diff --git a/packages/mobile/src/App.tsx b/packages/mobile/src/App.tsx index eb97b611f3..b3c64c879b 100644 --- a/packages/mobile/src/App.tsx +++ b/packages/mobile/src/App.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import { useDispatch } from 'react-redux' import { LogBox, StatusBar } from 'react-native' @@ -74,6 +74,10 @@ function App(): JSX.Element { const confirmationBox = useConfirmationBox() + useEffect(() => { + console.log('LAUNCHED APPLICATION: ', (Math.random() + 1).toString(36).substring(7)) + }, []) + return ( diff --git a/packages/mobile/src/store/init/blindConnection/blindConnection.saga.ts b/packages/mobile/src/store/init/blindConnection/blindConnection.saga.ts index 0da964a9e7..44571067c0 100644 --- a/packages/mobile/src/store/init/blindConnection/blindConnection.saga.ts +++ b/packages/mobile/src/store/init/blindConnection/blindConnection.saga.ts @@ -3,6 +3,13 @@ import { initSelectors } from '../init.selectors' import { initActions } from '../init.slice' export function* blindConnectionSaga(): Generator { + const isWebsocketConnected = yield* select(initSelectors.isWebsocketConnected) const lastKnownSocketIOData = yield* select(initSelectors.lastKnownSocketIOData) - yield* put(initActions.startWebsocketConnection(lastKnownSocketIOData)) + + console.log('WEBSOCKET', 'Entered blind connection saga', isWebsocketConnected, lastKnownSocketIOData) + + if (!isWebsocketConnected && lastKnownSocketIOData.dataPort !== 0) { + console.log('WEBSOCKET', 'Hooking up blindly at last known data port: ', lastKnownSocketIOData.dataPort) + yield* put(initActions.startWebsocketConnection(lastKnownSocketIOData)) + } } diff --git a/packages/mobile/src/store/init/deepLink/deepLink.saga.test.ts b/packages/mobile/src/store/init/deepLink/deepLink.saga.test.ts index 8c32be8dba..b019eb86c7 100644 --- a/packages/mobile/src/store/init/deepLink/deepLink.saga.test.ts +++ b/packages/mobile/src/store/init/deepLink/deepLink.saga.test.ts @@ -3,7 +3,7 @@ import { combineReducers } from '@reduxjs/toolkit' import { reducers } from '../../root.reducer' import { Store } from '../../store.types' import { prepareStore } from '../../../tests/utils/prepareStore' -import { communities, connection, identity } from '@quiet/state-manager' +import { communities, connection, getInvitationCodes, identity } from '@quiet/state-manager' import { initActions } from '../init.slice' import { navigationActions } from '../../navigation/navigation.slice' import { ScreenNames } from '../../../const/ScreenNames.enum' @@ -59,14 +59,7 @@ describe('deepLinkSaga', () => { await expectSaga(deepLinkSaga, initActions.deepLink(validCode)) .withReducer(reducer) .withState(store.getState()) - .put( - navigationActions.replaceScreen({ - screen: ScreenNames.JoinCommunityScreen, - params: { - code: validCode, - }, - }) - ) + .put(initActions.resetDeepLink()) .put( communities.actions.createNetwork({ ownership: CommunityOwnership.User, @@ -75,9 +68,15 @@ describe('deepLinkSaga', () => { ownerOrbitDbIdentity: validData.ownerOrbitDbIdentity, }) ) + .put( + navigationActions.replaceScreen({ + screen: ScreenNames.UsernameRegistrationScreen, + }) + ) .run() }) + // FIXME: Currently there's no way to actually check whether the redirection destionation is correct test.skip('opens channel list screen if the same url has been used', async () => { store.dispatch( initActions.setWebsocketConnected({ @@ -86,7 +85,13 @@ describe('deepLinkSaga', () => { }) ) - store.dispatch(communities.actions.addNewCommunity(community)) + store.dispatch(communities.actions.setInvitationCodes(validData.pairs)) + store.dispatch( + communities.actions.addNewCommunity({ + ...community, + name: 'rockets', + }) + ) store.dispatch( // @ts-expect-error @@ -99,11 +104,6 @@ describe('deepLinkSaga', () => { await expectSaga(deepLinkSaga, initActions.deepLink(validCode)) .withReducer(reducer) .withState(store.getState()) - .put( - navigationActions.replaceScreen({ - screen: ScreenNames.ChannelListScreen, - }) - ) .not.put( communities.actions.createNetwork({ ownership: CommunityOwnership.User, @@ -123,9 +123,19 @@ describe('deepLinkSaga', () => { }) ) - store.dispatch(communities.actions.addNewCommunity(community)) + // Store other communitys' invitation data in redux + const invitationData = getValidInvitationUrlTestData(validInvitationCodeTestData[1]) + store.dispatch(communities.actions.setInvitationCodes(invitationData.data.pairs)) + + store.dispatch( + communities.actions.addNewCommunity({ + ...community, + name: 'rockets', + }) + ) store.dispatch(communities.actions.setCurrentCommunity(community.id)) + const reducer = combineReducers(reducers) await expectSaga(deepLinkSaga, initActions.deepLink(validCode)) .withReducer(reducer) @@ -135,10 +145,6 @@ describe('deepLinkSaga', () => { type: navigationActions.replaceScreen.type, payload: { screen: ScreenNames.ErrorScreen, - params: { - title: 'You already belong to a community', - message: "We're sorry but for now you can only be a member of a single community at a time", - }, }, }, }) @@ -153,6 +159,50 @@ describe('deepLinkSaga', () => { .run() }) + test("doesn't display error if user is connecting with the same community", async () => { + store.dispatch( + initActions.setWebsocketConnected({ + dataPort: 5001, + socketIOSecret: 'secret', + }) + ) + + store.dispatch(communities.actions.addNewCommunity(community)) + + store.dispatch(communities.actions.setCurrentCommunity(community.id)) + + const invitationCodes = getInvitationCodes(validCode) + store.dispatch(communities.actions.setInvitationCodes(invitationCodes.pairs)) + + const reducer = combineReducers(reducers) + await expectSaga(deepLinkSaga, initActions.deepLink(validCode)) + .withReducer(reducer) + .withState(store.getState()) + .not.put.like({ + action: { + type: navigationActions.replaceScreen.type, + payload: { + screen: ScreenNames.ErrorScreen, + params: { + title: 'You already belong to a community', + message: "We're sorry but for now you can only be a member of a single community at a time", + }, + }, + }, + }) + .put.like({ + action: { + type: communities.actions.createNetwork.type, + payload: { + ownership: CommunityOwnership.User, + peers: validData.pairs, + psk: validData.psk, + }, + }, + }) + .run() + }) + test('displays error if invitation code is invalid', async () => { const invalidData: InvitationData = { pairs: [ @@ -197,46 +247,4 @@ describe('deepLinkSaga', () => { ) .run() }) - - test.todo('continues if link used mid registration') - - test.skip('continues if link used mid registration and locks input while waiting for server response', async () => { - store.dispatch( - initActions.setWebsocketConnected({ - dataPort: 5001, - socketIOSecret: 'secret', - }) - ) - - store.dispatch(communities.actions.addNewCommunity(community)) - - store.dispatch( - // @ts-expect-error - identity.actions.addNewIdentity({ ..._identity, userCertificate: null }) - ) - - store.dispatch(communities.actions.setCurrentCommunity(community.id)) - - store.dispatch(connection.actions.setConnectionProcess(ConnectionProcessInfo.REGISTERING_USER_CERTIFICATE)) - - const reducer = combineReducers(reducers) - await expectSaga(deepLinkSaga, initActions.deepLink(validCode)) - .withReducer(reducer) - .withState(store.getState()) - .put( - navigationActions.replaceScreen({ - screen: ScreenNames.UsernameRegistrationScreen, - params: { fetching: true }, - }) - ) - .not.put( - communities.actions.createNetwork({ - ownership: CommunityOwnership.User, - peers: validData.pairs, - psk: validData.psk, - ownerOrbitDbIdentity: validData.ownerOrbitDbIdentity, - }) - ) - .run() - }) }) diff --git a/packages/mobile/src/store/init/deepLink/deepLink.saga.ts b/packages/mobile/src/store/init/deepLink/deepLink.saga.ts index 067dd961c8..4c98593786 100644 --- a/packages/mobile/src/store/init/deepLink/deepLink.saga.ts +++ b/packages/mobile/src/store/init/deepLink/deepLink.saga.ts @@ -8,6 +8,7 @@ import { initActions } from '../init.slice' import { appImages } from '../../../assets' import { replaceScreen } from '../../../RootNavigation' import { CommunityOwnership, CreateNetworkPayload, InvitationData } from '@quiet/types' +import { areObjectsEqual } from '../../../utils/functions/areObjectsEqual/areObjectsEqual' export function* deepLinkSaga(action: PayloadAction['payload']>): Generator { const code = action.payload @@ -27,14 +28,72 @@ export function* deepLinkSaga(action: PayloadAction replaceScreen(ScreenNames.JoinCommunityScreen), + icon: appImages.quiet_icon_round, + title: 'Invalid invitation link', + message: 'Please check your invitation link and try again', + }, + }) + ) + return + } + const community = yield* select(communities.selectors.currentCommunity) - // Link opened mid registration - // TODO: Check if csr is already saved to db (redux store) + const storedInvitationCodes = yield* select(communities.selectors.invitationCodes) + const currentInvitationCodes = data.pairs + + console.log('Stored invitation codes', storedInvitationCodes) + console.log('Current invitation codes', currentInvitationCodes) + + let isInvitationDataValid = false + + if (storedInvitationCodes.length === 0) { + isInvitationDataValid = true + } else { + isInvitationDataValid = storedInvitationCodes.some(storedCode => + currentInvitationCodes.some(currentCode => areObjectsEqual(storedCode, currentCode)) + ) + } + + console.log('Is invitation data valid', isInvitationDataValid) + + const isAlreadyConnected = Boolean(community?.name) + + const alreadyBelongsWithAnotherCommunity = !isInvitationDataValid && isAlreadyConnected + const connectingWithAnotherCommunity = !isInvitationDataValid && !isAlreadyConnected + const alreadyBelongsWithCurrentCommunity = isInvitationDataValid && isAlreadyConnected + const connectingWithCurrentCommunity = isInvitationDataValid && !isAlreadyConnected + + if (alreadyBelongsWithAnotherCommunity) { + console.log('INIT_NAVIGATION: ABORTING: Already belongs with another community.') + } + + if (connectingWithAnotherCommunity) { + console.log('INIT_NAVIGATION: ABORTING: Proceeding with connection to another community.') + } + + if (alreadyBelongsWithCurrentCommunity) { + console.log('INIT_NAVIGATION: ABORTING: Already connected with the current community.') + } + + if (connectingWithCurrentCommunity) { + console.log('INIT_NAVIGATION: Proceeding with connection to the community.') + } // User already belongs to a community - if (community) { + if (alreadyBelongsWithAnotherCommunity || alreadyBelongsWithCurrentCommunity) { console.log('INIT_NAVIGATION: Displaying error (user already belongs to a community).') + yield* put( navigationActions.replaceScreen({ screen: ScreenNames.ErrorScreen, @@ -46,39 +105,28 @@ export function* deepLinkSaga(action: PayloadAction replaceScreen(ScreenNames.JoinCommunityScreen), + onPress: () => replaceScreen(ScreenNames.UsernameRegistrationScreen), icon: appImages.quiet_icon_round, - title: 'Invalid invitation link', - message: 'Please check your invitation link and try again', + title: 'You already started to connect to another community', + message: "We're sorry but for now you can only be a member of a single community at a time", }, }) ) + return } - console.log('INIT_NAVIGATION: Switching to the join community screen.') - - yield* put( - navigationActions.replaceScreen({ - screen: ScreenNames.JoinCommunityScreen, - params: { - code, - }, - }) - ) - const payload: CreateNetworkPayload = { ownership: CommunityOwnership.User, peers: data.pairs, @@ -88,10 +136,8 @@ export function* deepLinkSaga(action: PayloadAction { state.deepLinking = false }, + canceledRootTask: state => state, }, }) diff --git a/packages/mobile/src/store/init/startConnection/restoreConnection/restoreConnection.saga.ts b/packages/mobile/src/store/init/startConnection/restoreConnection/restoreConnection.saga.ts index b7dde652ff..7b7195b2be 100644 --- a/packages/mobile/src/store/init/startConnection/restoreConnection/restoreConnection.saga.ts +++ b/packages/mobile/src/store/init/startConnection/restoreConnection/restoreConnection.saga.ts @@ -11,7 +11,10 @@ export function* restoreConnectionSaga(): Generator { const isWebsocketConnected = yield* select(initSelectors.isWebsocketConnected) const socketIOData = yield* select(initSelectors.lastKnownSocketIOData) + console.log('WEBSOCKET', 'Entered restore connection saga', isWebsocketConnected, socketIOData) + if (!isWebsocketConnected && socketIOData.dataPort !== 0) { + console.log('WEBSOCKET', 'Restoring connection with data port: ', socketIOData.dataPort) yield* put(initActions.startWebsocketConnection(socketIOData)) } } diff --git a/packages/mobile/src/store/init/startConnection/startConnection.saga.ts b/packages/mobile/src/store/init/startConnection/startConnection.saga.ts index 7911de1d62..3b45ea7dd9 100644 --- a/packages/mobile/src/store/init/startConnection/startConnection.saga.ts +++ b/packages/mobile/src/store/init/startConnection/startConnection.saga.ts @@ -1,14 +1,18 @@ import { io, Socket } from 'socket.io-client' -import { put, call, cancel, fork, takeEvery, FixedTask } from 'typed-redux-saga' +import { select, put, call, cancel, fork, takeEvery, FixedTask } from 'typed-redux-saga' import { PayloadAction } from '@reduxjs/toolkit' import { socket as stateManager } from '@quiet/state-manager' import { encodeSecret } from '@quiet/common' +import { initSelectors } from '../init.selectors' import { initActions, WebsocketConnectionPayload } from '../init.slice' import { eventChannel } from 'redux-saga' export function* startConnectionSaga( action: PayloadAction['payload']> ): Generator { + const isWebsocketConnected = yield* select(initSelectors.isWebsocketConnected) + console.log('WEBSOCKET', 'Entered start connection saga', isWebsocketConnected) + const { dataPort, socketIOSecret } = action.payload let _dataPort = dataPort @@ -33,6 +37,7 @@ export function* startConnectionSaga( function* setConnectedSaga(socket: Socket): Generator { const task = yield* fork(stateManager.useIO, socket) + console.log('WEBSOCKET', 'Forking state-manager sagas', task) // Handle suspending current connection yield* takeEvery(initActions.suspendWebsocketConnection, cancelRootTaskSaga, task) } @@ -45,15 +50,17 @@ function* handleSocketLifecycleActions(socket: Socket, socketIOData: WebsocketCo } function subscribeSocketLifecycle(socket: Socket, socketIOData: WebsocketConnectionPayload) { + let socket_id: string return eventChannel< ReturnType | ReturnType >(emit => { socket.on('connect', async () => { - console.log('websocket connected') + socket_id = socket.id + console.log('client: Websocket connected', socket_id) emit(initActions.setWebsocketConnected(socketIOData)) }) socket.on('disconnect', () => { - console.log('closing socket connection') + console.log('client: Closing socket connection', socket_id) emit(initActions.suspendWebsocketConnection()) }) return () => {} @@ -61,6 +68,7 @@ function subscribeSocketLifecycle(socket: Socket, socketIOData: WebsocketConnect } function* cancelRootTaskSaga(task: FixedTask): Generator { - console.log('canceling root task') + console.log('Canceling root task') yield* cancel(task) + yield* put(initActions.canceledRootTask()) } diff --git a/packages/mobile/src/store/nativeServices/leaveCommunity/leaveCommunity.saga.ts b/packages/mobile/src/store/nativeServices/leaveCommunity/leaveCommunity.saga.ts index ca5c669fc3..33df360946 100644 --- a/packages/mobile/src/store/nativeServices/leaveCommunity/leaveCommunity.saga.ts +++ b/packages/mobile/src/store/nativeServices/leaveCommunity/leaveCommunity.saga.ts @@ -8,10 +8,12 @@ import { navigationActions } from '../../navigation/navigation.slice' import { ScreenNames } from '../../../../src/const/ScreenNames.enum' export function* leaveCommunitySaga(): Generator { + console.log('Leaving community') + // Restart backend yield* put(app.actions.closeServices()) - yield takeLeading(initActions.suspendWebsocketConnection.type, clearReduxStore) + yield takeLeading(initActions.canceledRootTask.type, clearReduxStore) } export function* clearReduxStore(): Generator { @@ -31,5 +33,8 @@ export function* clearReduxStore(): Generator { // Resume persistor yield* call(persistor.persist) + // Restarting persistor doesn't mark store as ready automatically + yield* put(initActions.setStoreReady()) + yield* put(navigationActions.replaceScreen({ screen: ScreenNames.JoinCommunityScreen })) } diff --git a/packages/mobile/src/tests/deep.linking.test.tsx b/packages/mobile/src/tests/deep.linking.test.tsx index f1dde19c5e..c4825e4f48 100644 --- a/packages/mobile/src/tests/deep.linking.test.tsx +++ b/packages/mobile/src/tests/deep.linking.test.tsx @@ -50,13 +50,15 @@ describe('Deep linking', () => { [ "Init/deepLink", "Init/resetDeepLink", - "Navigation/replaceScreen", "Communities/createNetwork", "Communities/setInvitationCodes", + "Navigation/replaceScreen", "Communities/savePSK", "Communities/addNewCommunity", "Communities/setCurrentCommunity", "Init/deepLink", + "Init/resetDeepLink", + "Navigation/replaceScreen", ] `) diff --git a/packages/mobile/src/utils/functions/areObjectsEqual/areObjectsEqual.ts b/packages/mobile/src/utils/functions/areObjectsEqual/areObjectsEqual.ts new file mode 100644 index 0000000000..d2f9899e4f --- /dev/null +++ b/packages/mobile/src/utils/functions/areObjectsEqual/areObjectsEqual.ts @@ -0,0 +1,3 @@ +export const areObjectsEqual = (obj1: any, obj2: any): boolean => { + return JSON.stringify(obj1) === JSON.stringify(obj2) +}