Skip to content

Commit

Permalink
fix(deeplinks): get url from the correct field for CT events (#6356)
Browse files Browse the repository at this point in the history
### Description

Per this
[update](https://github.com/CleverTap/clevertap-react-native/blob/2c2ab9664b9e83cde936d14b511896d7edd5ecdc/docs/callbackPayloadFormat.md?plain=1#L12)
in clever tap docs, this likely slipped through on a clevertap package
upgrade

### Test plan

Can't easily test CT push notifications with a dev build, but this has
been working on Android, where the url was extracted correctly, and also
confirmed with logging on iOS that the deeplink field is present at the
top level of the event.

Also tested the 2 callbacks being called scenario by updating the code
to call handleOpenUrl twice (one with isSecureOrigin: false and the
other with true) in the Linking event listener, the 2nd call mimics the
call that would've been done by the CT push notification listener
callback, and also introducing a delay in the saga (to mimic slow sagas,
which can be killed by `takeLatest` if another event of the same action
type is fired). On opening a deeplink on the browser with an openScreen
deeplink and the app backgrounded, confirmed that the navigation to the
correct screen happens.

### Related issues

- Fixes ACT-1469

### Backwards compatibility

Yes

### Network scalability

N/A
  • Loading branch information
satish-ravi authored Dec 12, 2024
1 parent 9350ff8 commit f52bc0f
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 6 deletions.
2 changes: 2 additions & 0 deletions __mocks__/@react-native-firebase/dynamic-links.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
const getInitialLink = jest.fn()
const buildShortLink = jest.fn()
const buildLink = jest.fn()
const onLink = jest.fn()

export default function links() {
return {
getInitialLink,
buildShortLink,
buildLink,
onLink,
}
}
3 changes: 3 additions & 0 deletions __mocks__/clevertap-react-native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ export default {
setPushToken: jest.fn(),
createNotificationChannel: jest.fn(),
registerForPush: jest.fn(),
getInitialUrl: jest.fn(),
addListener: jest.fn(),
removeListener: jest.fn(),
}
6 changes: 5 additions & 1 deletion src/app/saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,11 @@ export function* handleDeepLink(action: OpenDeepLink) {
}

function* watchDeepLinks() {
yield* takeLatest(Actions.OPEN_DEEP_LINK, safely(handleDeepLink))
// using takeEvery over takeLatest because openScreen deep links could be
// fired by multiple handlers (one with isSecureOrigin and one without), and
// if takeLatest kills the call to the handler with isSecureOrigin, the deep
// link won't work.
yield* takeEvery(Actions.OPEN_DEEP_LINK, safely(handleDeepLink))
}

export function* handleOpenUrl(action: OpenUrlAction) {
Expand Down
86 changes: 86 additions & 0 deletions src/app/useDeepLinks.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { act, renderHook } from '@testing-library/react-native'
import CleverTap from 'clevertap-react-native'
import React from 'react'
import { Linking } from 'react-native'
import { Provider } from 'react-redux'
import { useDeepLinks } from 'src/app/useDeepLinks'
import { createMockStore } from 'test/utils'

describe('useDeepLinks', () => {
let cleverTapListenerCallback: Function
let linkingListenerCallback: Function

beforeEach(() => {
jest.clearAllMocks()
jest.mocked(CleverTap.addListener).mockImplementation((event, callback) => {
if (event === 'CleverTapPushNotificationClicked') {
cleverTapListenerCallback = callback
}
})
jest.mocked(Linking.addEventListener).mockImplementation((event, callback) => {
if (event === 'url') {
linkingListenerCallback = callback
}
return {
remove: jest.fn(),
} as any
})
})

it('should handle clevertap push notifications with deep links', async () => {
const store = createMockStore()
renderHook(() => useDeepLinks(), {
wrapper: (component) => (
<Provider store={store}>{component?.children ? component.children : component}</Provider>
),
})

await act(() => {
cleverTapListenerCallback({ wzrk_dl: 'some-link' })
})

expect(store.getActions()).toEqual([
{
deepLink: 'some-link',
isSecureOrigin: true,
type: 'APP/OPEN_DEEP_LINK',
},
])
})

it('should not open deeplink if clevertap event does not have a deep link', async () => {
const store = createMockStore()
renderHook(() => useDeepLinks(), {
wrapper: (component) => (
<Provider store={store}>{component?.children ? component.children : component}</Provider>
),
})

await act(() => {
cleverTapListenerCallback({})
})

expect(store.getActions()).toEqual([])
})

it('should handle linking events with deep links', async () => {
const store = createMockStore()
renderHook(() => useDeepLinks(), {
wrapper: (component) => (
<Provider store={store}>{component?.children ? component.children : component}</Provider>
),
})

await act(() => {
linkingListenerCallback({ url: 'some-link' })
})

expect(store.getActions()).toEqual([
{
deepLink: 'some-link',
isSecureOrigin: false,
type: 'APP/OPEN_DEEP_LINK',
},
])
})
})
12 changes: 7 additions & 5 deletions src/app/useDeepLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import dynamicLinks from '@react-native-firebase/dynamic-links'
import CleverTap from 'clevertap-react-native'
import { useEffect, useState } from 'react'
import { useAsync } from 'react-async-hook'
import { Linking, Platform } from 'react-native'
import { Linking } from 'react-native'
import { deepLinkDeferred, openDeepLink } from 'src/app/actions'
import { pendingDeepLinkSelector } from 'src/app/selectors'
import { DYNAMIC_LINK_DOMAIN_URI_PREFIX, FIREBASE_ENABLED } from 'src/config'
Expand Down Expand Up @@ -62,7 +62,7 @@ export const useDeepLinks = () => {
}

useAsync(async () => {
// Handles opening Clevertap deeplinks when app is closed / in background
// Handles opening Clevertap deeplinks when app is closed
// @ts-expect-error the clevertap ts definition has url as an object, but it
// is a string!
CleverTap.getInitialUrl(async (err: any, url: string) => {
Expand Down Expand Up @@ -94,17 +94,19 @@ export const useDeepLinks = () => {
}, [])

useEffect(() => {
// Handles opening Clevertap deeplinks when app is open
// Handles opening Clevertap deeplinks when app is open.
CleverTap.addListener('CleverTapPushNotificationClicked', async (event: any) => {
Logger.debug('useDeepLinks/useEffect', 'CleverTapPushNotificationClicked', event)
// Url location differs for iOS and Android
const url = Platform.OS === 'ios' ? event.customExtras['wzrk_dl'] : event['wzrk_dl']
const url = event['wzrk_dl']
if (url) {
Logger.debug('useDeepLinks/useEffect', 'CleverTapPushNotificationClicked, opening url', url)
handleOpenURL({ url }, true)
}
})

// Handles opening any deep links, this listener is also triggered when a
// its a clevertap push notification or when the app is closed, so the
// openDeepLink action could be dispatched multiple times in those cases.
const linkingEventListener = Linking.addEventListener('url', (event) => {
Logger.debug('useDeepLinks/useEffect', 'Linking url event', event)
handleOpenURL(event)
Expand Down

0 comments on commit f52bc0f

Please sign in to comment.