diff --git a/packages/legacy/core/App/screens/Connection.tsx b/packages/legacy/core/App/screens/Connection.tsx index 926749fc95..81d95c0205 100644 --- a/packages/legacy/core/App/screens/Connection.tsx +++ b/packages/legacy/core/App/screens/Connection.tsx @@ -16,6 +16,8 @@ import { useNotifications } from '../hooks/notifications' import { DeliveryStackParams, Screens, Stacks, TabStacks } from '../types/navigators' import { testIdWithKey } from '../utils/testable' +import { useContainer, TOKENS } from './../container-api' + type ConnectionProps = StackScreenProps type MergeFunction = (current: LocalState, next: Partial) => LocalState @@ -40,8 +42,10 @@ const Connection: React.FC = ({ navigation, route }) => { const { notifications } = useNotifications() const { ColorPallet, TextTheme } = useTheme() const { ConnectionLoading } = useAnimatedComponents() + const container = useContainer() + const logger = container.resolve(TOKENS.UTIL_LOGGER) const connection = connectionId ? useConnectionById(connectionId) : undefined - const oobRecord = useOutOfBandByConnectionId(connectionId ?? '') + const oobRecord = connectionId ? useOutOfBandByConnectionId(connectionId) : undefined const goalCode = oobRecord?.outOfBandInvitation.goalCode const merge: MergeFunction = (current, next) => ({ ...current, ...next }) const [state, dispatch] = useReducer(merge, { @@ -80,9 +84,14 @@ const Connection: React.FC = ({ navigation, route }) => { const goalCodeAction = (goalCode: string): (() => void) => { const codes: { [key: string]: undefined | (() => void) } = { - 'aries.vc.verify': () => navigation.navigate(Screens.ProofRequest, { proofId: state.notificationRecord.id }), - 'aries.vc.issue': () => - navigation.navigate(Screens.CredentialOffer, { credentialId: state.notificationRecord.id }), + 'aries.vc.verify': () => { + logger?.info('Connection: Handling aries.vc.verify goal code, navigate to ProofRequest') + navigation.navigate(Screens.ProofRequest, { proofId: state.notificationRecord.id }) + }, + 'aries.vc.issue': () => { + logger?.info('Connection: Handling aries.vc.issue goal code, navigate to CredentialOffer') + navigation.navigate(Screens.CredentialOffer, { credentialId: state.notificationRecord.id }) + }, } let action = codes[goalCode] @@ -140,6 +149,8 @@ const Connection: React.FC = ({ navigation, route }) => { useEffect(() => { // for non-connectionless, invalid connections stay in the below state if (connection && connection.state === DidExchangeState.RequestSent) { + logger?.info('Connection: Skipping invalid connection state') + return } @@ -150,6 +161,8 @@ const Connection: React.FC = ({ navigation, route }) => { state.notificationRecord && state.notificationRecord.state === 'request-received' ) { + logger?.info('Connection: Handling connectionless proof request') + navigation.replace(Screens.ProofRequest, { proofId: state.notificationRecord.id }) dispatch({ isVisible: false }) @@ -163,6 +176,8 @@ const Connection: React.FC = ({ navigation, route }) => { ) { // No goal code, we don't know what to expect next, // navigate to the chat screen and set home screen as only history + logger?.info('Connection: Handling connection with OOB, without goal recognized code') + navigation.getParent()?.dispatch( CommonActions.reset({ index: 1, @@ -175,6 +190,8 @@ const Connection: React.FC = ({ navigation, route }) => { } if (state.notificationRecord && goalCode) { + logger?.info('Connection: Handling valid goal code') + goalCodeAction(goalCode)() } }, [connection, connection?.state, oobRecord, goalCode, state.notificationRecord]) @@ -192,35 +209,56 @@ const Connection: React.FC = ({ navigation, route }) => { ) useEffect(() => { - if (state.isVisible) { - for (const notification of notifications) { - // no action taken, we're already processing a notification - if (state.notificationRecord) { - break - } - - // no action taken for BasicMessageRecords - if (notification.type === 'BasicMessageRecord') { - continue - } - - // Connection based, we need to match the connectionId. - if (connection && notification.connectionId === connection.id) { - dispatch({ notificationRecord: notification, isVisible: false }) - break - } - - // Connectionless, we need to match the threadId or parentThreadId. - if (threadId && (notification.threadId === threadId || notification.parentThreadId == threadId)) { - dispatch({ notificationRecord: notification, isVisible: false }) - break - } - - // OOB with `goalCode` will be checked in another `useEffect`. - if (oobRecord && goalCode) { - dispatch({ notificationRecord: notification, isVisible: false }) - break - } + if (!state.isVisible) { + return + } + + for (const notification of notifications) { + // no action taken, we're already processing a notification + if (state.notificationRecord) { + logger?.info('Connection: Already processing a notification') + break + } + + // no action taken for BasicMessageRecords + if (notification.type === 'BasicMessageRecord') { + logger?.info('Connection: BasicMessageRecord, skipping') + continue + } + + // Connection based, we need to match the connectionId. + if ( + connection && + notification.connectionId === connection.id && + (!oobRecord || connection.outOfBandId !== oobRecord.id) + ) { + logger?.info('Connection: Handling connection based') + + dispatch({ notificationRecord: notification, isVisible: false }) + break + } + + // Connectionless, we need to match the threadId or parentThreadId. + if (threadId && (notification.threadId === threadId || notification.parentThreadId == threadId)) { + logger?.info('Connection: Handling connectionless') + + dispatch({ notificationRecord: notification, isVisible: false }) + break + } + + // OOB with `goalCode` will be checked in another `useEffect`. Expected + // as part of a OOB. + if ( + goalCode && + oobRecord && + connection && + connection.outOfBandId === oobRecord.id && + connection.id === notification.connectionId + ) { + logger?.info('Connection: Handling OOB with goalCode') + + dispatch({ notificationRecord: notification, isVisible: false }) + break } } }, [notifications]) diff --git a/packages/legacy/core/__tests__/screens/Connection.test.tsx b/packages/legacy/core/__tests__/screens/Connection.test.tsx index f2d9bfb913..2a1965f6e0 100644 --- a/packages/legacy/core/__tests__/screens/Connection.test.tsx +++ b/packages/legacy/core/__tests__/screens/Connection.test.tsx @@ -19,6 +19,8 @@ import timeTravel from '../helpers/timetravel' const proofNotifPath = path.join(__dirname, '../fixtures/proof-notif.json') const proofNotif = JSON.parse(fs.readFileSync(proofNotifPath, 'utf8')) +const offerNotifPath = path.join(__dirname, '../fixtures/offer-notif.json') +const offerNotif = JSON.parse(fs.readFileSync(offerNotifPath, 'utf8')) const connectionPath = path.join(__dirname, '../fixtures/connection-v1.json') const connection = JSON.parse(fs.readFileSync(connectionPath, 'utf8')) const connectionResponseReceivedPath = path.join(__dirname, '../fixtures/connection-v1-response-received.json') @@ -28,6 +30,7 @@ const props = { params: { connectionId: connection.id } } jest.useFakeTimers({ legacyFakeTimers: true }) jest.spyOn(global, 'setTimeout') +jest.mock('../../App/container-api') jest.mock('@react-navigation/core', () => { return require('../../__mocks__/custom/@react-navigation/core') }) @@ -211,17 +214,18 @@ describe('ConnectionModal Component', () => { expect(navigation.replace).toBeCalledWith('Proof Request', { proofId: proofNotif.id }) }) - test('Goal code extracted and navigation to Chat', async () => { + test('Goal code extracted, navigation to accept offer', async () => { const connectionId = 'abc123' + const oobId = 'def456' const navigation = useNavigation() // @ts-ignore-next-line - useNotifications.mockReturnValue({ total: 1, notifications: [proofNotif] }) + useNotifications.mockReturnValue({ total: 1, notifications: [{ ...offerNotif, connectionId }] }) // @ts-ignore-next-line - useOutOfBandByConnectionId.mockReturnValue({ outOfBandInvitation }) + useOutOfBandByConnectionId.mockReturnValue({ id: oobId, outOfBandInvitation: { goalCode: 'aries.vc.issue' } }) // @ts-ignore-next-line - useConnectionById.mockReturnValue(undefined) + useConnectionById.mockReturnValue({ ...connection, id: connectionId, outOfBandId: oobId, state: 'offer-received' }) // @ts-ignore-next-line - useProofById.mockReturnValue(proofNotif) + useProofById.mockReturnValue(offerNotif) const element = ( @@ -233,7 +237,9 @@ describe('ConnectionModal Component', () => { expect(tree).toMatchSnapshot() expect(navigation.navigate).toBeCalledTimes(1) - expect(navigation.navigate).toBeCalledWith('Proof Request', { proofId: proofNotif.id }) + expect(navigation.navigate).toBeCalledWith('Credential Offer', { + credentialId: offerNotif.id, + }) }) test('Dismiss navigates Home', async () => { diff --git a/packages/legacy/core/__tests__/screens/__snapshots__/Connection.test.tsx.snap b/packages/legacy/core/__tests__/screens/__snapshots__/Connection.test.tsx.snap index cac910e9d6..2294ae4443 100644 --- a/packages/legacy/core/__tests__/screens/__snapshots__/Connection.test.tsx.snap +++ b/packages/legacy/core/__tests__/screens/__snapshots__/Connection.test.tsx.snap @@ -406,7 +406,7 @@ exports[`ConnectionModal Component Dismiss on demand 1`] = ` `; -exports[`ConnectionModal Component Goal code extracted and navigation to Chat 1`] = ` +exports[`ConnectionModal Component Goal code extracted, navigation to accept offer 1`] = `