Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-17 11:19:08 +03:00
parent 8c792a2ce8
commit 131079f463
28 changed files with 104 additions and 342 deletions

View file

@ -9,14 +9,34 @@ plugins {
id("configuration")
}
//conflict of dependencies when adding WalletConnectV2.0 library
configurations {
all {
exclude(group = "org.bouncycastle", module = "bcprov-jdk15to18")
resolutionStrategy {
force("org.bouncycastle:bcpkix-jdk15on:1.70")
}
exclude(group = "com.github.komputing.kethereum")
resolutionStrategy {
dependencySubstitution {
substitute(module("com.facebook.react:react-native"))
.using(module("com.facebook.react:react-android:0.72.4"))
.because(
"The current version of SPR Client (3.6.2) is not compatible with the latest " +
"React Native version"
)
substitute(module("com.facebook.react:hermes-engine"))
.using(module("com.facebook.react:hermes-android:0.72.4"))
.because(
"The current version of SPR Client (3.6.2) is not compatible with the latest " +
"Hermes Engine version"
)
}
force(
"org.bouncycastle:bcpkix-jdk15on:1.70",
"com.facebook.react:react-android:0.72.4",
"com.facebook.react:hermes-android:0.72.4",
)
}
}
}
@ -159,6 +179,9 @@ dependencies {
implementation(deps.walletConnectCore)
implementation(deps.walletConnectWeb3)
implementation(deps.prettyLogger)
implementation(deps.sprClient) {
exclude(group = "com.github.stephenc.jcip")
}
/** Testing libraries */
testImplementation(deps.test.junit)

View file

@ -12,6 +12,16 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
@ -39,7 +49,7 @@
android:extractNativeLibs="true"
android:supportsRtl="true"
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:allowBackup, android:fullBackupContent">
tools:replace="android:allowBackup, android:fullBackupContent, android:label">
<meta-data
android:name="com.google.android.gms.wallet.api.enabled"
@ -135,10 +145,6 @@
android:name="zendesk.messaging.MessagingActivity"
android:theme="@style/ZendeskTheme" />
<activity
android:name="com.tangem.tap.features.sprinklr.ui.SprinklrActivity"
android:theme="@style/AppTheme" />
<activity
android:name="com.tangem.feature.learn2earn.presentation.webView.Learn2earnWebViewActivity"
android:theme="@style/AppTheme" />

View file

@ -336,7 +336,7 @@ class TapApplication : Application(), ImageLoaderFactory {
val feedbackManager = FeedbackManager(
infoHolder = additionalFeedbackInfo,
logCollector = tangemLogCollector,
chatManager = ChatManager(preferencesStorage, foregroundActivityObserver, store),
chatManager = ChatManager(preferencesStorage, foregroundActivityObserver),
)
store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager))
}

View file

@ -2,37 +2,31 @@ package com.tangem.tap.common.chat
import android.content.Context
import android.os.Build
import com.tangem.data.source.preferences.PreferencesDataSource
import com.tangem.datasource.config.models.ChatConfig
import com.tangem.datasource.config.models.SprinklrConfig
import com.tangem.datasource.config.models.ZendeskConfig
import com.tangem.data.source.preferences.PreferencesDataSource
import com.tangem.tap.ForegroundActivityObserver
import com.tangem.tap.common.chat.opener.ChatOpener
import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener
import com.tangem.tap.common.chat.opener.implementation.ZendeskChatOpener
import com.tangem.tap.common.redux.AppState
import org.rekotlin.Store
import java.io.File
class ChatManager(
private val preferencesStorage: PreferencesDataSource,
private val foregroundActivityObserver: ForegroundActivityObserver,
private val store: Store<AppState>,
) {
private val openers = mutableMapOf<ChatConfig, ChatOpener>()
fun open(config: ChatConfig, createLogsFile: (Context) -> File?, createFeedbackFile: (Context) -> File?) {
val opener = openers.getOrPut(config) {
when (config) {
is SprinklrConfig -> SprinklrChatOpener(getSprinklrUserId(), config, store, foregroundActivityObserver)
is SprinklrConfig -> SprinklrChatOpener(config, foregroundActivityObserver)
is ZendeskConfig -> ZendeskChatOpener(getZendeskUserId(), config, foregroundActivityObserver)
}
}
opener.open(
createFeedbackFile = createFeedbackFile,
createLogsFile = createLogsFile,
)
opener.open(createFeedbackFile, createLogsFile)
}
private fun getZendeskUserId(): String {
@ -40,18 +34,6 @@ class ChatManager(
preferencesStorage.zendeskFirstLaunchTime = System.currentTimeMillis()
}
return getChatUserId(preferencesStorage.zendeskFirstLaunchTime!!)
}
private fun getSprinklrUserId(): String {
if (preferencesStorage.sprinklrFirstLaunchTime == null) {
preferencesStorage.sprinklrFirstLaunchTime = System.currentTimeMillis()
}
return getChatUserId(preferencesStorage.sprinklrFirstLaunchTime!!)
}
private fun getChatUserId(firstLaunchTimeMillis: Long): String {
return "${firstLaunchTimeMillis}${Build.MODEL}".hashCode().toString()
return "${preferencesStorage.zendeskFirstLaunchTime}${Build.MODEL}".hashCode().toString()
}
}

View file

@ -1,28 +1,57 @@
package com.tangem.tap.common.chat.opener.implementation
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.content.Intent
import android.provider.Settings
import com.spr.messengerclient.config.SPRMessenger
import com.spr.messengerclient.config.bean.SPRMessengerConfig
import com.tangem.common.extensions.guard
import com.tangem.datasource.config.models.SprinklrConfig
import com.tangem.tap.ForegroundActivityObserver
import com.tangem.tap.common.chat.opener.ChatOpener
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.sprinklr.redux.SprinklrAction
import com.tangem.tap.features.sprinklr.ui.SprinklrActivity
import com.tangem.tap.withForegroundActivity
import org.rekotlin.Store
import timber.log.Timber
import java.io.File
import java.util.Locale
internal class SprinklrChatOpener(
private val userId: String,
private val config: SprinklrConfig,
private val store: Store<AppState>,
private val foregroundActivityObserver: ForegroundActivityObserver,
) : ChatOpener {
override fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) {
store.dispatch(SprinklrAction.Init(userId, config))
foregroundActivityObserver.withForegroundActivity { activity ->
val intent = Intent(activity, SprinklrActivity::class.java)
activity.startActivity(intent)
val messenger = SPRMessenger.shared()
if (messenger.config == null) {
initSprConfig(messenger)
}
messenger.startApplication()
}
private fun initSprConfig(messenger: SPRMessenger) {
val application = foregroundActivityObserver.foregroundActivity?.application.guard {
Timber.e("The SPR chat cannot be opened because there are no activities in foreground")
return
}
messenger.takeOff(application, createSprConfig(application, config))
}
@SuppressLint("HardwareIds")
private fun createSprConfig(application: Application, config: SprinklrConfig): SPRMessengerConfig {
return SPRMessengerConfig().apply {
appId = config.appId
appKey = CHAT_APP_KEY
deviceId = Settings.Secure.getString(application.contentResolver, Settings.Secure.ANDROID_ID)
environment = config.environment
skin = CHAT_SKIN
locale = Locale.getDefault().language
}
}
private companion object {
const val CHAT_APP_KEY = "com.sprinklr.messenger.release"
const val CHAT_SKIN = "MODERN"
}
}

View file

@ -14,7 +14,6 @@ import com.tangem.tap.features.saveWallet.redux.SaveWalletReducer
import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
import com.tangem.tap.features.shop.redux.ShopReducer
import com.tangem.tap.features.signin.redux.SignInReducer
import com.tangem.tap.features.sprinklr.redux.SprinklrReducer
import com.tangem.tap.features.tokens.legacy.redux.TokensReducer
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
import com.tangem.tap.features.walletSelector.redux.WalletSelectorReducer
@ -45,7 +44,6 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
welcomeState = WelcomeReducer.reduce(action, state),
saveWalletState = SaveWalletReducer.reduce(action, state),
walletSelectorState = WalletSelectorReducer.reduce(action, state),
sprinklrState = SprinklrReducer.reduce(action, state),
signInState = SignInReducer.reduce(action, state),
daggerGraphState = DaggerGraphReducer.reduce(action, state),
)

View file

@ -32,8 +32,6 @@ import com.tangem.tap.features.shop.redux.ShopMiddleware
import com.tangem.tap.features.shop.redux.ShopState
import com.tangem.tap.features.signin.redux.SignInMiddleware
import com.tangem.tap.features.signin.redux.SignInState
import com.tangem.tap.features.sprinklr.redux.SprinklrMiddleware
import com.tangem.tap.features.sprinklr.redux.SprinklrState
import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware
import com.tangem.tap.features.tokens.legacy.redux.TokensState
import com.tangem.tap.features.wallet.redux.WalletState
@ -66,7 +64,6 @@ data class AppState(
val welcomeState: WelcomeState = WelcomeState(),
val saveWalletState: SaveWalletState = SaveWalletState(),
val walletSelectorState: WalletSelectorState = WalletSelectorState(),
val sprinklrState: SprinklrState = SprinklrState(),
val signInState: SignInState = SignInState(),
val daggerGraphState: DaggerGraphState = DaggerGraphState(),
) : StateType {
@ -108,7 +105,6 @@ data class AppState(
WalletSelectorMiddleware().middleware,
LockUserWalletsTimerMiddleware().middleware,
AccessCodeRequestPolicyMiddleware().middleware,
SprinklrMiddleware().middleware,
SignInMiddleware.middleware,
DaggerGraphMiddleware.daggerGraphMiddleware,
)

View file

@ -100,10 +100,10 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
}
// if config not set -> try to get it based on a scanResponse.productType
val unsafeChatConfig = action.chatConfig ?: config.zendesk
val unsafeChatConfig = action.chatConfig ?: config.sprinklr
val chatConfig = unsafeChatConfig.guard {
store.dispatchDebugErrorNotification("ZendeskConfig not initialized")
store.dispatchDebugErrorNotification("The chat config is not initialized")
return
}
feedbackManager.openChat(chatConfig, action.feedbackData)

View file

@ -52,7 +52,9 @@ object OnboardingHelper {
fun whereToNavigate(scanResponse: ScanResponse): AppScreen {
return when (scanResponse.productType) {
ProductType.Note -> AppScreen.OnboardingNote
ProductType.Wallet -> if (scanResponse.card.settings.isBackupAllowed) {
ProductType.Wallet,
ProductType.Wallet2,
-> if (scanResponse.card.settings.isBackupAllowed) {
AppScreen.OnboardingWallet
} else {
AppScreen.OnboardingOther

View file

@ -1,10 +0,0 @@
package com.tangem.tap.features.sprinklr.redux
import com.tangem.datasource.config.models.SprinklrConfig
import org.rekotlin.Action
sealed interface SprinklrAction : Action {
data class Init(val userId: String, val config: SprinklrConfig) : SprinklrAction
data class UpdateUrl(val url: String) : SprinklrAction
data class UpdateSprinklrDomains(val domains: List<String>) : SprinklrAction
}

View file

@ -1,42 +0,0 @@
package com.tangem.tap.features.sprinklr.redux
import com.tangem.datasource.config.models.SprinklrConfig
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.sprinklr.redux.model.SprinklrUrl
import com.tangem.tap.store
import org.rekotlin.Middleware
internal class SprinklrMiddleware {
val middleware: Middleware<AppState> = { _, appStateProvider ->
{ next ->
{ action ->
val appState = appStateProvider()
if (action is SprinklrAction && appState != null) {
handleAction(action)
}
next(action)
}
}
}
private fun handleAction(action: SprinklrAction) {
when (action) {
is SprinklrAction.Init -> updateUrl(action.userId, action.config)
is SprinklrAction.UpdateUrl,
is SprinklrAction.UpdateSprinklrDomains,
-> Unit
}
}
private fun updateUrl(userId: String, config: SprinklrConfig) {
updateSprinklrDomains(config.baseUrl)
val url = SprinklrUrl.Prod(userId, config.baseUrl, config.appId).url
store.dispatchOnMain(SprinklrAction.UpdateUrl(url))
}
private fun updateSprinklrDomains(baseUrl: String) {
val domains = listOf(baseUrl, SprinklrUrl.Static.url)
store.dispatchOnMain(SprinklrAction.UpdateSprinklrDomains(domains))
}
}

View file

@ -1,22 +0,0 @@
package com.tangem.tap.features.sprinklr.redux
import com.tangem.tap.common.redux.AppState
import org.rekotlin.Action
internal object SprinklrReducer {
fun reduce(action: Action, state: AppState): SprinklrState {
return if (action is SprinklrAction) {
internalReduce(action, state.sprinklrState)
} else {
state.sprinklrState
}
}
private fun internalReduce(action: SprinklrAction, state: SprinklrState): SprinklrState {
return when (action) {
is SprinklrAction.Init -> state
is SprinklrAction.UpdateUrl -> state.copy(url = action.url)
is SprinklrAction.UpdateSprinklrDomains -> state.copy(sprinklrDomains = action.domains)
}
}
}

View file

@ -1,6 +0,0 @@
package com.tangem.tap.features.sprinklr.redux
data class SprinklrState(
val url: String = "",
val sprinklrDomains: List<String> = emptyList(),
)

View file

@ -1,27 +0,0 @@
package com.tangem.tap.features.sprinklr.redux.model
internal sealed class SprinklrUrl {
abstract val url: String
class Prod(userId: String, baseUrl: String, appId: String) : SprinklrUrl() {
private val locale = java.util.Locale.getDefault().language
override val url: String = "$baseUrl/page" +
"?appId=$appId" +
"&device=$Device" +
"&enableClose=$CloseEnabled" +
"&zoom=$ZoomEnabled" +
"&locale=$locale" +
"&user_id=$userId"
companion object {
private const val Device = "MOBILE"
private const val CloseEnabled = true
private const val ZoomEnabled = false
}
}
object Static : SprinklrUrl() {
override val url: String = "live-chat-static.sprinklr.com"
}
}

View file

@ -1,54 +0,0 @@
package com.tangem.tap.features.sprinklr.ui
import android.os.Bundle
import androidx.activity.compose.BackHandler
import androidx.activity.viewModels
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeActivity
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.tap.common.analytics.events.Chat
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
internal class SprinklrActivity : ComposeActivity() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
private val viewModel by viewModels<SprinklrViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.send(Chat.ScreenOpened())
viewModel.setNavigateBackCallback {
this.finish()
}
}
@Composable
override fun ScreenContent(modifier: Modifier) {
val state by viewModel.state.collectAsStateWithLifecycle()
val systemBarsColor = TangemTheme.colors.background.primary
SystemBarsEffect {
setSystemBarsColor(systemBarsColor)
}
BackHandler(onBack = state.onNavigateBack)
SprinklrScreenContent(
modifier = modifier
.systemBarsPadding()
.imePadding(),
state = state,
)
}
}

View file

@ -1,25 +0,0 @@
package com.tangem.tap.features.sprinklr.ui
import android.annotation.SuppressLint
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.google.accompanist.web.WebView
import com.google.accompanist.web.rememberWebViewState
import com.tangem.tap.features.sprinklr.ui.webview.SprinklrWebViewClient
@SuppressLint("SetJavaScriptEnabled")
@Composable
internal fun SprinklrScreenContent(state: SprinklrScreenState, modifier: Modifier = Modifier) {
WebView(
modifier = modifier,
state = rememberWebViewState(url = state.initialUrl),
onCreated = { webView ->
with(webView.settings) {
javaScriptEnabled = true
domStorageEnabled = true
}
},
client = remember { SprinklrWebViewClient(state.onNewUrl) },
)
}

View file

@ -1,10 +0,0 @@
package com.tangem.tap.features.sprinklr.ui
import com.google.accompanist.web.WebContent
internal data class SprinklrScreenState(
val initialUrl: String = "",
val sprinklrDomains: List<String> = emptyList(),
val onNavigateBack: () -> Unit = {},
val onNewUrl: WebContent.(String) -> WebContent = { this },
)

View file

@ -1,66 +0,0 @@
package com.tangem.tap.features.sprinklr.ui
import androidx.lifecycle.ViewModel
import com.google.accompanist.web.WebContent
import com.tangem.core.navigation.NavigationAction
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.features.sprinklr.redux.SprinklrState
import com.tangem.tap.store
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import org.rekotlin.StoreSubscriber
internal class SprinklrViewModel : ViewModel(), StoreSubscriber<SprinklrState> {
private val stateInternal = MutableStateFlow(SprinklrScreenState())
val state = stateInternal.asStateFlow()
init {
subscribeToStoreChanges()
}
override fun newState(state: SprinklrState) {
stateInternal.update { prevState ->
prevState.copy(
initialUrl = state.url,
sprinklrDomains = state.sprinklrDomains,
onNewUrl = { updateWebContentOrOpenExternalUrl(it) },
)
}
}
override fun onCleared() {
store.unsubscribe(this)
}
fun setNavigateBackCallback(callback: () -> Unit) {
stateInternal.update { prevState ->
prevState.copy(
onNavigateBack = callback,
)
}
}
private fun subscribeToStoreChanges() {
store.subscribe(this) { appState ->
appState.skip { old, new -> old.sprinklrState == new.sprinklrState }
.select { it.sprinklrState }
}
}
private fun WebContent.updateWebContentOrOpenExternalUrl(url: String): WebContent {
return if (isExternalUrl(url)) {
store.dispatchOnMain(NavigationAction.OpenUrl(url))
this
} else {
when (this) {
is WebContent.Url -> copy(url = url)
else -> WebContent.Url(url)
}
}
}
private fun isExternalUrl(url: String): Boolean {
return state.value.sprinklrDomains.none { url.contains(it) }
}
}

View file

@ -1,24 +0,0 @@
package com.tangem.tap.features.sprinklr.ui.webview
import android.webkit.WebResourceRequest
import android.webkit.WebView
import com.google.accompanist.web.AccompanistWebViewClient
import com.google.accompanist.web.WebContent
internal class SprinklrWebViewClient(
private val onNewUrl: WebContent.(String) -> WebContent,
) : AccompanistWebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
// If the url hasn't changed, this is probably an internal event like
// a javascript reload. We should let it happen.
if (view?.url == request?.url.toString()) {
return false
}
request?.let {
state.content = onNewUrl(state.content, it.url.toString())
}
return true
}
}

View file

@ -103,6 +103,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
amplitudeApiKey = configValues.amplitudeApiKey,
shopify = configValues.shopifyShop,
zendesk = configValues.zendesk,
sprinklr = configValues.sprinklr,
swapReferrerAccount = configValues.swapReferrerAccount,
walletConnectProjectId = configValues.walletConnectProjectId,
tangemComAuthorization = configValues.tangemComAuthorization,

View file

@ -23,6 +23,8 @@ data class ZendeskConfig(
data class SprinklrConfig(
@Json(name = "appID")
val appId: String,
@Json(name = "baseURL")
val baseUrl: String,
@Json(name = "apiKey")
val apiKey: String,
@Json(name = "environment")
val environment: String,
) : ChatConfig

View file

@ -16,6 +16,7 @@ data class Config(
val isCreatingTwinCardsAllowed: Boolean = false,
val shopify: ShopifyShop? = null,
val zendesk: ZendeskConfig? = null,
val sprinklr: SprinklrConfig? = null,
val swapReferrerAccount: SwapReferrerAccount? = null,
val walletConnectProjectId: String = "",
val tangemComAuthorization: String? = null,

View file

@ -32,6 +32,7 @@ class ConfigValueModel(
val appsFlyer: AppsFlyer,
val shopifyShop: ShopifyShop?,
val zendesk: ZendeskConfig?,
val sprinklr: SprinklrConfig?,
val tronGridApiKey: String,
val amplitudeApiKey: String,
val swapReferrerAccount: SwapReferrerAccount?,

View file

@ -49,6 +49,7 @@ internal class DefaultCardSdkConfigRepository(
ProductType.Twins -> CardIdDisplayFormat.LastLuhn(numbers = 4)
ProductType.Note,
ProductType.Wallet,
ProductType.Wallet2,
ProductType.Start2Coin,
-> CardIdDisplayFormat.Full
}

View file

@ -20,7 +20,9 @@ class UserWalletBuilder(
ProductType.Note -> "Note"
ProductType.Twins -> "Twin"
ProductType.Start2Coin -> "Start2Coin"
ProductType.Wallet -> when {
ProductType.Wallet,
ProductType.Wallet2,
-> when {
card.isBackupNotAllowed -> "Tangem card"
cardTypesResolver.isStart2Coin() -> "Start2Coin"
else -> "Wallet"

View file

@ -60,6 +60,7 @@ class UserWalletIdBuilder private constructor(
ProductType.Twins -> scanResponse.secondTwinPublicKey?.hexToBytes()
ProductType.Note,
ProductType.Wallet,
ProductType.Wallet2,
ProductType.Start2Coin,
-> null
},

View file

@ -78,6 +78,7 @@ walletConnectCore = "1.18.0"
walletConnectWeb3 = "1.11.0"
prettyLogger = "2.2.0"
okHttp-prettyLogging = "3.1.0"
spr-client = "3.6.2"
# endregion Other libraries
# region Tangem
@ -227,4 +228,5 @@ reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", versio
walletConnectCore = { module = "com.walletconnect:android-core", version.ref = "walletConnectCore" }
walletConnectWeb3 = { module = "com.walletconnect:web3wallet", version.ref = "walletConnectWeb3" }
prettyLogger = { module = "com.orhanobut:logger", version.ref = "prettyLogger" }
sprClient = { module = "com.spr:messengerclient", version.ref = "spr-client" }
# endregion Other

View file

@ -40,6 +40,7 @@ dependencyResolutionManagement {
}
maven("https://jitpack.io")
maven("https://zendesk.jfrog.io/zendesk/repo")
maven("https://clients-nexus.sprinklr.com/")
}
versionCatalogs {