From 7cfa7a6a85e7f287b756a57237811c25856b4ddd Mon Sep 17 00:00:00 2001 From: Tarek Sabry Date: Mon, 10 Nov 2025 23:33:32 +0200 Subject: [PATCH 1/4] Revamp parameter mapping --- .../kmm/appsflyer/OneLinkService.android.kt | 198 +++++---- .../kmm/appsflyer/model/DeepLinkEnums.kt | 5 - .../kmm/appsflyer/model/DeepLinkHelpers.kt | 249 ++++++++++++ .../kmm/appsflyer/model/DeepLinkMetadata.kt | 108 ----- .../kmm/appsflyer/model/DeepLinkResult.kt | 379 +++++++++++++++++- .../kmm/appsflyer/util/AppsFlyerConstants.kt | 143 ++++++- .../metacto/kmm/appsflyer/util/Extensions.kt | 2 +- .../appsflyer/model/DeepLinkMetadataTest.kt | 91 ----- .../appsflyer/model/HelperFunctionsTest.kt | 120 ------ .../kmm/appsflyer/OneLinkService.ios.kt | 138 ++++--- 10 files changed, 946 insertions(+), 487 deletions(-) create mode 100644 appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkHelpers.kt delete mode 100644 appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkMetadata.kt delete mode 100644 appsflyer/src/commonTest/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkMetadataTest.kt delete mode 100644 appsflyer/src/commonTest/kotlin/com/metacto/kmm/appsflyer/model/HelperFunctionsTest.kt diff --git a/appsflyer/src/androidMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.android.kt b/appsflyer/src/androidMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.android.kt index 582554b..f27441b 100644 --- a/appsflyer/src/androidMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.android.kt +++ b/appsflyer/src/androidMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.android.kt @@ -6,17 +6,7 @@ import com.appsflyer.AppsFlyerLib import com.appsflyer.attribution.AppsFlyerRequestListener import com.appsflyer.deeplink.DeepLinkListener import com.appsflyer.deeplink.DeepLinkResult -import com.metacto.kmm.appsflyer.model.getDeepLinkValue -import com.metacto.kmm.appsflyer.model.getDeepLinkMetadata -import com.metacto.kmm.appsflyer.model.getDestinationPath -import com.metacto.kmm.appsflyer.model.toError -import com.metacto.kmm.appsflyer.model.hasDescopeToken -import com.metacto.kmm.appsflyer.model.hasLoginType -import com.metacto.kmm.appsflyer.model.getLoginType -import com.metacto.kmm.appsflyer.model.getAfSub1 -import com.metacto.kmm.appsflyer.model.DeeplinkSource -import com.metacto.kmm.appsflyer.model.UdlStatus -import com.metacto.kmm.appsflyer.model.GcdAfStatus +import com.metacto.kmm.appsflyer.model.* import com.metacto.kmm.appsflyer.util.getAppAttributionResult import com.metacto.kmm.appsflyer.model.DeepLinkResult as KMMDeepLinkResult @@ -33,6 +23,9 @@ actual class OneLinkService actual constructor( private val conversionListener = object : AppsFlyerConversionListener { override fun onConversionDataSuccess(p0: MutableMap?) { if (p0 != null) { + println("🔗 DEEPLINK [KMM-GCD] onConversionDataSuccess called") + println("🔗 DEEPLINK [KMM-GCD] conversionInfo: $p0") + val appConversionResult = this@OneLinkService.getAppAttributionResult(p0) options.listener.onAppAttribution( appConversionResult.isOrganic, @@ -44,28 +37,59 @@ actual class OneLinkService actual constructor( @Suppress("UNCHECKED_CAST") val extras = appConversionResult.extras as Map + println("🔗 DEEPLINK [KMM-GCD] extras: $extras") + println("🔗 DEEPLINK [KMM-GCD] gcdMediaSource: $gcdMediaSource") + println("🔗 DEEPLINK [KMM-GCD] gcdCampaign: $gcdCampaign") + + val isFirstLaunch = (extras["is_first_launch"] as? String)?.toBoolean() + ?: (extras["is_first_launch"] as? Boolean) + ?: false + println("🔗 DEEPLINK [KMM-GCD] isFirstLaunch: $isFirstLaunch") + + val deepLinkValue = extras.getDeepLinkValue() + println("🔗 DEEPLINK [KMM-GCD] deepLinkValue: $deepLinkValue") + + val destination = deepLinkValue?.parseDestination() + println("🔗 DEEPLINK [KMM-GCD] destination: $destination") + val result = KMMDeepLinkResult( - destination = null, - campaign = gcdCampaign, - campaignId = null, - clickHttpReferrer = null, - isDeferred = false, - mediaSource = gcdMediaSource, - matchType = null, - clickEventJson = null, - metadata = null, - deeplinkSource = DeeplinkSource.GCD, - hasDescopeToken = hasDescopeToken(extras), - hasLoginType = hasLoginType(extras), - loginType = getLoginType(extras), - udlStatus = null, - udlMatchType = null, - gcdAfStatus = if (appConversionResult.isOrganic) GcdAfStatus.ORGANIC else GcdAfStatus.NON_ORGANIC, - gcdMediaSource = gcdMediaSource, - gcdCampaign = gcdCampaign, - afSub1 = getAfSub1(extras), - extraLink = null + origin = extras.buildOrigin( + source = DeeplinkSource.GCD, + udlStatus = null, + gcdStatus = if (appConversionResult.isOrganic) GcdAfStatus.ORGANIC else GcdAfStatus.NON_ORGANIC, + isDeferred = isFirstLaunch + ), + campaign = extras.buildCampaign(), + advertisement = extras.buildAdvertisement(), + deepLink = extras.buildDeepLink(destination), + timestamps = extras.buildTimestamps(), + cost = extras.buildCost(), + device = extras.buildDevice(), + retargeting = extras.buildRetargeting(), + engagement = extras.buildEngagement(), + viewability = extras.buildViewability(), + network = extras.buildNetwork(), + customParameters = extras.buildCustomParameters(), + socialPreview = extras.buildSocialPreview(), + system = extras.buildSystem(), + clickEvent = extras, + conversion = p0 as? Map ) + + println("🔗 DEEPLINK [KMM-GCD] ===== DeepLinkResult Summary (onConversionDataSuccess) =====") + println("🔗 DEEPLINK [KMM-GCD] origin.source: ${result.origin.source}") + println("🔗 DEEPLINK [KMM-GCD] origin.status: ${result.origin.status}") + println("🔗 DEEPLINK [KMM-GCD] origin.isDeferred: ${result.origin.isDeferred}") + println("🔗 DEEPLINK [KMM-GCD] deepLink.destination: ${result.deepLink.destination}") + println("🔗 DEEPLINK [KMM-GCD] deepLink.value: ${result.deepLink.value}") + println("🔗 DEEPLINK [KMM-GCD] deepLink.scheme: ${result.deepLink.scheme}") + println("🔗 DEEPLINK [KMM-GCD] campaign.mediaSource: ${result.campaign.mediaSource}") + println("🔗 DEEPLINK [KMM-GCD] campaign.name: ${result.campaign.name}") + println("🔗 DEEPLINK [KMM-GCD] customParameters.sub1: ${result.customParameters.sub1}") + println("🔗 DEEPLINK [KMM-GCD] customParameters.sub2: ${result.customParameters.sub2}") + println("🔗 DEEPLINK [KMM-GCD] customParameters.sub3: ${result.customParameters.sub3}") + println("🔗 DEEPLINK [KMM-GCD] =====================================") + println("🔗 DEEPLINK [KMM-GCD] Calling listener.onDeepLinkingResult with GCD result") options.listener.onDeepLinkingResult(result) } } @@ -76,39 +100,49 @@ actual class OneLinkService actual constructor( override fun onAppOpenAttribution(p0: MutableMap?) { if (p0 != null) { + println("🔗 DEEPLINK [KMM-GCD] onAppOpenAttribution called") + println("🔗 DEEPLINK [KMM-GCD] attributionData: $p0") + val appConversionResult = this@OneLinkService.getAppAttributionResult(p0) options.listener.onAppAttribution( appConversionResult.isOrganic, appConversionResult.extras ) - val gcdMediaSource = appConversionResult.extras["media_source"]?.toString() - val gcdCampaign = appConversionResult.extras["campaign"]?.toString() @Suppress("UNCHECKED_CAST") val extras = appConversionResult.extras as Map + val deepLinkValue = extras.getDeepLinkValue() + val destination = deepLinkValue?.parseDestination() + val result = KMMDeepLinkResult( - destination = null, - campaign = gcdCampaign, - campaignId = null, - clickHttpReferrer = null, - isDeferred = false, - mediaSource = gcdMediaSource, - matchType = null, - clickEventJson = null, - metadata = null, - deeplinkSource = DeeplinkSource.GCD, - hasDescopeToken = hasDescopeToken(extras), - hasLoginType = hasLoginType(extras), - loginType = getLoginType(extras), - udlStatus = null, - udlMatchType = null, - gcdAfStatus = if (appConversionResult.isOrganic) GcdAfStatus.ORGANIC else GcdAfStatus.NON_ORGANIC, - gcdMediaSource = gcdMediaSource, - gcdCampaign = gcdCampaign, - afSub1 = getAfSub1(extras), - extraLink = null + origin = extras.buildOrigin( + source = DeeplinkSource.GCD, + udlStatus = null, + gcdStatus = if (appConversionResult.isOrganic) GcdAfStatus.ORGANIC else GcdAfStatus.NON_ORGANIC, + isDeferred = false + ), + campaign = extras.buildCampaign(), + advertisement = extras.buildAdvertisement(), + deepLink = extras.buildDeepLink(destination), + timestamps = extras.buildTimestamps(), + cost = extras.buildCost(), + device = extras.buildDevice(), + retargeting = extras.buildRetargeting(), + engagement = extras.buildEngagement(), + viewability = extras.buildViewability(), + network = extras.buildNetwork(), + customParameters = extras.buildCustomParameters(), + socialPreview = extras.buildSocialPreview(), + system = extras.buildSystem(), + clickEvent = extras, + conversion = p0 as? Map ) + + println("🔗 DEEPLINK [KMM-GCD] ===== DeepLinkResult =====") + println("🔗 DEEPLINK [KMM-GCD] $result") + println("🔗 DEEPLINK [KMM-GCD] =====================================") + println("🔗 DEEPLINK [KMM-GCD] Calling listener.onDeepLinkingResult") options.listener.onDeepLinkingResult(result) } } @@ -121,41 +155,55 @@ actual class OneLinkService actual constructor( private val deepLinkListener = DeepLinkListener { deepLinkResult -> when (deepLinkResult.status) { DeepLinkResult.Status.FOUND -> { + println("🔗 DEEPLINK [KMM-UDL] Status: FOUND") val deepLink = deepLinkResult.deepLink val clickEventValues = deepLink.clickEvent.toMap() - val fullDeepLinkValue = deepLink.deepLinkValue ?: this.getDeepLinkValue(clickEventValues) - val metadata = this.getDeepLinkMetadata(fullDeepLinkValue, clickEventValues) + println("🔗 DEEPLINK [KMM-UDL] clickEventValues: $clickEventValues") + + val deepLinkValue = deepLink.deepLinkValue ?: clickEventValues.getDeepLinkValue() + println("🔗 DEEPLINK [KMM-UDL] deepLinkValue: $deepLinkValue") + + val destination = deepLinkValue?.parseDestination() + println("🔗 DEEPLINK [KMM-UDL] destination: $destination") val result = KMMDeepLinkResult( - destination = fullDeepLinkValue?.let { this.getDestinationPath(fullDeepLinkValue) }, - campaign = deepLink.campaign, - campaignId = deepLink.campaignId, - clickHttpReferrer = deepLink.clickHttpReferrer, - isDeferred = deepLink.isDeferred, - mediaSource = deepLink.mediaSource, - matchType = deepLink.matchType, - clickEventJson = deepLink.clickEvent.toString(), - metadata = metadata, - deeplinkSource = DeeplinkSource.UDL, - hasDescopeToken = hasDescopeToken(metadata.extras), - hasLoginType = hasLoginType(metadata.extras), - loginType = getLoginType(metadata.extras), - udlStatus = UdlStatus.FOUND, - udlMatchType = deepLink.matchType, - gcdAfStatus = null, - gcdMediaSource = null, - gcdCampaign = null, - afSub1 = getAfSub1(clickEventValues), - extraLink = deepLink.clickEvent.getString("link") + origin = clickEventValues.buildOrigin( + source = DeeplinkSource.UDL, + udlStatus = UdlStatus.FOUND, + gcdStatus = null, + isDeferred = deepLink.isDeferred + ), + campaign = clickEventValues.buildCampaign(), + advertisement = clickEventValues.buildAdvertisement(), + deepLink = clickEventValues.buildDeepLink(destination), + timestamps = clickEventValues.buildTimestamps(), + cost = clickEventValues.buildCost(), + device = clickEventValues.buildDevice(), + retargeting = clickEventValues.buildRetargeting(), + engagement = clickEventValues.buildEngagement(), + viewability = clickEventValues.buildViewability(), + network = clickEventValues.buildNetwork(), + customParameters = clickEventValues.buildCustomParameters(), + socialPreview = clickEventValues.buildSocialPreview(), + system = clickEventValues.buildSystem(), + clickEvent = clickEventValues, + conversion = null ) + + println("🔗 DEEPLINK [KMM-UDL] ===== DeepLinkResult =====") + println("🔗 DEEPLINK [KMM-UDL] $result") + println("🔗 DEEPLINK [KMM-UDL] =====================================") + println("🔗 DEEPLINK [KMM-UDL] Calling listener.onDeepLinkingResult") options.listener.onDeepLinkingResult(result) } DeepLinkResult.Status.NOT_FOUND -> { + println("🔗 DEEPLINK [KMM-UDL] Status: NOT_FOUND") options.listener.onDeepLinkingResult(null) } else -> { + println("🔗 DEEPLINK [KMM-UDL] Status: ERROR") options.listener.onDeepLinkingError(deepLinkResult.error.toError()) } } diff --git a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkEnums.kt b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkEnums.kt index 93be2aa..b510f32 100644 --- a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkEnums.kt +++ b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkEnums.kt @@ -5,11 +5,6 @@ enum class DeeplinkSource { GCD } -enum class LoginType { - EMAIL, - PHONE -} - enum class UdlStatus { FOUND, NOT_FOUND diff --git a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkHelpers.kt b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkHelpers.kt new file mode 100644 index 0000000..a5cea7e --- /dev/null +++ b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkHelpers.kt @@ -0,0 +1,249 @@ +package com.metacto.kmm.appsflyer.model + +import com.metacto.kmm.appsflyer.util.AppsFlyerConstants +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + +fun Map.getDeepLinkValue(): String? { + return this[AppsFlyerConstants.DEEP_LINK_VALUE]?.toString() +} + +fun String.parseDestination(): String? { + return parseJsonDestination() ?: parseNormalDestination() +} + +internal fun String.parseJsonDestination(): String? { + return try { + val destinationValue = Json.parseToJsonElement(this.trim()).let { + it.jsonObject[AppsFlyerConstants.DEEP_LINK_DESTINATION]?.jsonPrimitive?.content + } + + val values = destinationValue?.split("__") + return values?.firstOrNull() + } catch (_: Throwable) { + null + } +} + +internal fun String.parseNormalDestination(): String? { + val values = this.split("__") + return values.find { it.startsWith(AppsFlyerConstants.DEEP_LINK_DESTINATION) }?.substringAfter("=") +} + +internal fun Map.getString(key: String): String? = + this[key]?.toString()?.takeIf { it != "null" && it.isNotEmpty() } + +internal fun Map.getBoolean(key: String): Boolean? { + return when (val value = this[key]) { + is Boolean -> value + is String -> value.toBoolean() + is Number -> value.toInt() != 0 + else -> null + } +} + +fun Map.buildOrigin(source: DeeplinkSource, udlStatus: UdlStatus?, gcdStatus: GcdAfStatus?, isDeferred: Boolean?): Origin { + return Origin( + source = source, + udlStatus = udlStatus, + status = gcdStatus, + matchType = getString(AppsFlyerConstants.MATCH_TYPE), + isDeferred = isDeferred + ) +} + +fun Map.buildCampaign(): Campaign { + return Campaign( + mediaSource = getString(AppsFlyerConstants.MEDIA_SOURCE) ?: getString(AppsFlyerConstants.PID), + name = getString(AppsFlyerConstants.CAMPAIGN) ?: getString(AppsFlyerConstants.C), + id = getString(AppsFlyerConstants.CAMPAIGN_ID) ?: getString(AppsFlyerConstants.AF_C_ID), + channel = getString(AppsFlyerConstants.AF_CHANNEL), + siteId = getString(AppsFlyerConstants.AF_SITEID), + subSiteId = getString(AppsFlyerConstants.AF_SUB_SITEID), + partner = getString(AppsFlyerConstants.AF_PRT), + marketingPartner = getString(AppsFlyerConstants.AF_MP), + agency = getString(AppsFlyerConstants.AGENCY), + accountId = getString(AppsFlyerConstants.AF_PARTNER_ACCOUNT_ID), + keywords = getString(AppsFlyerConstants.AF_KEYWORDS) + ) +} + +fun Map.buildAdvertisement(): Advertisement { + return Advertisement( + adset = getString(AppsFlyerConstants.ADSET) ?: getString(AppsFlyerConstants.AF_ADSET), + adsetId = getString(AppsFlyerConstants.ADSET_ID) ?: getString(AppsFlyerConstants.AF_ADSET_ID), + adgroup = getString(AppsFlyerConstants.ADGROUP), + adgroupId = getString(AppsFlyerConstants.ADGROUP_ID), + adgroupName = getString(AppsFlyerConstants.ADGROUP_NAME), + ad = getString(AppsFlyerConstants.AF_AD), + adId = getString(AppsFlyerConstants.AD_ID) ?: getString(AppsFlyerConstants.AF_AD_ID), + adType = getString(AppsFlyerConstants.AF_AD_TYPE), + adFormat = getString(AppsFlyerConstants.AF_AD_FORMAT), + adEventId = getString(AppsFlyerConstants.AD_EVENT_ID) + ) +} + +fun Map.buildDeepLink(destination: String?): DeepLink { + return DeepLink( + destination = destination, + value = getDeepLinkValue(), + sub1 = getString(AppsFlyerConstants.DEEP_LINK_SUB1), + sub2 = getString(AppsFlyerConstants.DEEP_LINK_SUB2), + sub3 = getString(AppsFlyerConstants.DEEP_LINK_SUB3), + sub4 = getString(AppsFlyerConstants.DEEP_LINK_SUB4), + sub5 = getString(AppsFlyerConstants.DEEP_LINK_SUB5), + sub6 = getString(AppsFlyerConstants.DEEP_LINK_SUB6), + sub7 = getString(AppsFlyerConstants.DEEP_LINK_SUB7), + sub8 = getString(AppsFlyerConstants.DEEP_LINK_SUB8), + sub9 = getString(AppsFlyerConstants.DEEP_LINK_SUB9), + sub10 = getString(AppsFlyerConstants.DEEP_LINK_SUB10), + scheme = getString(AppsFlyerConstants.BASE_DEEP_LINK), + forceDeeplink = getBoolean(AppsFlyerConstants.AF_FORCE_DEEPLINK), + redirect = getString(AppsFlyerConstants.AF_R), + webRedirect = getString(AppsFlyerConstants.AF_WEB_DP), + androidUrl = getString(AppsFlyerConstants.AF_ANDROID_URL), + iosUrl = getString(AppsFlyerConstants.AF_IOS_URL) + ) +} + +fun Map.buildTimestamps(): Timestamps { + return Timestamps( + clickTime = getString(AppsFlyerConstants.CLICK_TIME), + installTime = getString(AppsFlyerConstants.INSTALL_TIME), + isFirstLaunch = getBoolean(AppsFlyerConstants.IS_FIRST_LAUNCH), + clickLookback = getString(AppsFlyerConstants.AF_CLICK_LOOKBACK), + viewthroughLookback = getString(AppsFlyerConstants.AF_VIEWTHROUGH_LOOKBACK), + reengagementWindow = getString(AppsFlyerConstants.AF_REENGAGEMENT_WINDOW) + ) +} + +fun Map.buildCost(): Cost { + return Cost( + original = getString(AppsFlyerConstants.ORIG_COST), + centsUsd = getString(AppsFlyerConstants.COST_CENTS_USD), + currency = getString(AppsFlyerConstants.AF_COST_CURRENCY), + value = getString(AppsFlyerConstants.AF_COST_VALUE), + model = getString(AppsFlyerConstants.AF_COST_MODEL), + perInstall = getString(AppsFlyerConstants.AF_CPI) + ) +} + +fun Map.buildDevice(): Device { + return Device( + os = getString(AppsFlyerConstants.AF_OS), + osVersion = getString(AppsFlyerConstants.AF_OS_VERSION), + model = getString(AppsFlyerConstants.AF_MODEL), + userAgent = getString(AppsFlyerConstants.AF_UA), + ipAddress = getString(AppsFlyerConstants.AF_IP), + mediaType = getString(AppsFlyerConstants.AF_MEDIA_TYPE), + android = buildAndroidIdentifiers(), + ios = buildIosIdentifiers() + ) +} + +fun Map.buildAndroidIdentifiers(): AndroidIdentifiers { + return AndroidIdentifiers( + advertisingId = getString(AppsFlyerConstants.ADVERTISING_ID), + advertisingIdSha1 = getString(AppsFlyerConstants.SHA1_ADVERTISING_ID), + advertisingIdMd5 = getString(AppsFlyerConstants.MD5_ADVERTISING_ID), + androidId = getString(AppsFlyerConstants.ANDROID_ID), + androidIdSha1 = getString(AppsFlyerConstants.SHA1_ANDROID_ID), + androidIdMd5 = getString(AppsFlyerConstants.MD5_ANDROID_ID), + imei = getString(AppsFlyerConstants.IMEI), + imeiSha1 = getString(AppsFlyerConstants.SHA1_IMEI), + imeiMd5 = getString(AppsFlyerConstants.MD5_IMEI), + oaid = getString(AppsFlyerConstants.OAID), + oaidSha1 = getString(AppsFlyerConstants.SHA1_OAID), + oaidMd5 = getString(AppsFlyerConstants.MD5_OAID), + fireAdvertisingId = getString(AppsFlyerConstants.FIRE_ADVERTISING_ID), + emailSha1 = getString(AppsFlyerConstants.SHA1_EL), + storeListing = getString(AppsFlyerConstants.AF_ANDROID_STORE_CSL) + ) +} + +fun Map.buildIosIdentifiers(): IosIdentifiers { + return IosIdentifiers( + idfa = getString(AppsFlyerConstants.IDFA), + idfaSha1 = getString(AppsFlyerConstants.SHA1_IDFA), + idfaMd5 = getString(AppsFlyerConstants.MD5_IDFA), + idfv = getString(AppsFlyerConstants.IDFV), + idfvSha1 = getString(AppsFlyerConstants.SHA1_IDFV), + idfvMd5 = getString(AppsFlyerConstants.MD5_IDFV), + mac = getString(AppsFlyerConstants.MAC), + macSha1 = getString(AppsFlyerConstants.SHA1_MAC), + productPage = getString(AppsFlyerConstants.AF_IOS_STORE_CPP) + ) +} + +fun Map.buildRetargeting(): Retargeting { + return Retargeting( + isRetargeting = getBoolean(AppsFlyerConstants.IS_RETARGETING), + conversionType = getString(AppsFlyerConstants.RETARGETING_CONVERSION_TYPE) + ) +} + +fun Map.buildEngagement(): Engagement { + return Engagement( + isIncentivized = getBoolean(AppsFlyerConstants.IS_INCENTIVIZED), + clickId = getString(AppsFlyerConstants.CLICKID), + referrer = getString(AppsFlyerConstants.HTTP_REFERRER) + ) +} + +fun Map.buildViewability(): Viewability { + return Viewability( + videoTotalLength = getString(AppsFlyerConstants.AF_VIDEO_TOTAL_LENGTH), + videoPlayedLength = getString(AppsFlyerConstants.AF_VIDEO_PLAYED_LENGTH), + playablePlayedLength = getString(AppsFlyerConstants.AF_PLAYABLE_PLAYED_LENGTH), + adTimeViewed = getString(AppsFlyerConstants.AF_AD_TIME_VIEWED), + adDisplayedPercent = getString(AppsFlyerConstants.AF_AD_DISPLAYED_PERCENT), + audioTotalLength = getString(AppsFlyerConstants.AF_AUDIO_TOTAL_LENGTH), + audioPlayedLength = getString(AppsFlyerConstants.AF_AUDIO_PLAYED_LENGTH) + ) +} + +fun Map.buildNetwork(): Network { + return Network( + meta = MetaParameters( + isFacebook = getBoolean(AppsFlyerConstants.IS_FB), + isPaid = getBoolean(AppsFlyerConstants.IS_PAID), + isInstagram = getBoolean(AppsFlyerConstants.IS_INSTAGRAM) + ), + google = GoogleParameters( + accountId = getString(AppsFlyerConstants.EXTERNAL_ACCOUNT_ID), + clickId = getString(AppsFlyerConstants.GCLID), + network = getString(AppsFlyerConstants.NETWORK), + clickUrl = getString(AppsFlyerConstants.CLICK_URL), + latitude = getString(AppsFlyerConstants.LAT), + videoId = getString(AppsFlyerConstants.VIDEO_ID) + ), + campaignType = getString(AppsFlyerConstants.CAMPAIGN_TYPE) + ) +} + +fun Map.buildCustomParameters(): CustomParameters { + return CustomParameters( + sub1 = getString(AppsFlyerConstants.AF_SUB1), + sub2 = getString(AppsFlyerConstants.AF_SUB2), + sub3 = getString(AppsFlyerConstants.AF_SUB3), + sub4 = getString(AppsFlyerConstants.AF_SUB4), + sub5 = getString(AppsFlyerConstants.AF_SUB5) + ) +} + +fun Map.buildSocialPreview(): SocialPreview { + return SocialPreview( + title = getString(AppsFlyerConstants.AF_OG_TITLE), + description = getString(AppsFlyerConstants.AF_OG_DESCRIPTION), + image = getString(AppsFlyerConstants.AF_OG_IMAGE) + ) +} + +fun Map.buildSystem(): System { + return System( + referralCode = getString(AppsFlyerConstants.AF_REF), + parameterForwarding = getBoolean(AppsFlyerConstants.AF_PARAM_FORWARDING), + baseParametersForward = getBoolean(AppsFlyerConstants.AF_BASE_PARAMS_FORWARD) + ) +} \ No newline at end of file diff --git a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkMetadata.kt b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkMetadata.kt deleted file mode 100644 index aed2094..0000000 --- a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkMetadata.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.metacto.kmm.appsflyer.model - -import com.metacto.kmm.appsflyer.OneLinkService -import com.metacto.kmm.appsflyer.util.AppsFlyerConstants -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive - -data class DeepLinkMetadata( - val referrerName: String?, - val baseDeepLinkPath: String?, - val channel: String?, - val campaign: String?, - val referrerCustomerId: String?, - val referrerUID: String?, - val referrerImageURL: String?, - val extras: Map? -) - -fun OneLinkService.getDeepLinkValue(values: Map): String? { - return values[AppsFlyerConstants.DEEP_LINK_VALUE]?.toString() -} - -fun OneLinkService.getDestinationPath(fullDeepLinkValue: String): String? { - return fullDeepLinkValue.parseJsonDestination() ?: fullDeepLinkValue.parseNormalDestination() -} - -internal fun String.parseJsonDestination(): String? { - return try { - val destinationValue = Json.parseToJsonElement(this.trim()).let { - it.jsonObject[AppsFlyerConstants.DEEP_LINK_DESTINATION]?.jsonPrimitive?.content - } - - val values = destinationValue?.split("__") - return values?.firstOrNull() - } catch (_: Throwable) { - null - } -} - -internal fun String.parseNormalDestination(): String? { - val values = this.split("__") - return values.find { it.startsWith(AppsFlyerConstants.DEEP_LINK_DESTINATION) }?.substringAfter("=") -} - -fun hasDescopeToken(extras: Map?): Boolean { - return extras?.containsKey("dt") == true -} - -fun hasLoginType(extras: Map?): Boolean { - return extras?.containsKey("lt") == true -} - -fun getLoginType(extras: Map?): LoginType? { - val value = extras?.get("lt")?.toString() - return when (value) { - "email" -> LoginType.EMAIL - "phone" -> LoginType.PHONE - else -> null - } -} - -fun getAfSub1(extras: Map?): String? { - return extras?.get("af_sub1")?.toString() -} - -fun OneLinkService.getDeepLinkMetadata(deepLinkValue: String?, extraValues: Map): DeepLinkMetadata { - val values = deepLinkValue?.split("__").orEmpty() - val referrerName = - values.find { it.startsWith(AppsFlyerConstants.REFERRER_NAME) }?.substringAfter("=") - val baseDeepLinkPath = - values.find { it.startsWith(AppsFlyerConstants.BASE_DEEP_LINK) }?.substringAfter("=") - val channel = values.find { it.startsWith(AppsFlyerConstants.CHANNEL) }?.substringAfter("=") - val campaign = values.find { it.startsWith(AppsFlyerConstants.CAMPAIGN) }?.substringAfter("=") - val referrerCustomerId = - values.find { it.startsWith(AppsFlyerConstants.REFERRER_CUSTOMER_ID) }?.substringAfter("=") - val referrerUID = - values.find { it.startsWith(AppsFlyerConstants.REFERRER_UID) }?.substringAfter("=") - val referrerImageURL = - values.find { it.startsWith(AppsFlyerConstants.REFERRER_IMAGE_URL) }?.substringAfter("=") - val extras = values.filter { - !it.startsWith(AppsFlyerConstants.REFERRER_NAME) - && !it.startsWith(AppsFlyerConstants.DEEP_LINK_VALUE) - && !it.startsWith(AppsFlyerConstants.BASE_DEEP_LINK) - && !it.startsWith(AppsFlyerConstants.CHANNEL) - && !it.startsWith(AppsFlyerConstants.CAMPAIGN) - && !it.startsWith(AppsFlyerConstants.REFERRER_CUSTOMER_ID) - && !it.startsWith(AppsFlyerConstants.DEEP_LINK_DESTINATION) - && !it.startsWith(AppsFlyerConstants.REFERRER_UID) - && !it.startsWith(AppsFlyerConstants.REFERRER_IMAGE_URL) - }.map { - it.substringBefore("=") to it.substringAfter("=") - } - - val extraData = (extras.toMap() as Map).toMutableMap() - extraData.putAll(extraValues) - - return DeepLinkMetadata( - referrerName = referrerName, - baseDeepLinkPath = baseDeepLinkPath, - channel = channel, - campaign = campaign, - referrerCustomerId = referrerCustomerId, - referrerUID = referrerUID, - referrerImageURL = referrerImageURL, - extras = extraData - ) -} \ No newline at end of file diff --git a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkResult.kt b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkResult.kt index 674e977..eed614e 100644 --- a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkResult.kt +++ b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkResult.kt @@ -1,24 +1,361 @@ package com.metacto.kmm.appsflyer.model -data class DeepLinkResult( - val destination: String?, - val campaign: String?, - val campaignId: String?, - val clickHttpReferrer: String?, - val isDeferred: Boolean?, - val mediaSource: String?, - val matchType: String?, - val clickEventJson: String?, - val metadata: DeepLinkMetadata?, - val deeplinkSource: DeeplinkSource, - val hasDescopeToken: Boolean, - val hasLoginType: Boolean, - val loginType: LoginType?, +data class Origin( + val source: DeeplinkSource, val udlStatus: UdlStatus?, - val udlMatchType: String?, - val gcdAfStatus: GcdAfStatus?, - val gcdMediaSource: String?, - val gcdCampaign: String?, - val afSub1: String?, - val extraLink: String? -) \ No newline at end of file + val status: GcdAfStatus?, + val matchType: String?, + val isDeferred: Boolean? +) + +data class Campaign( + val mediaSource: String?, + val name: String?, + val id: String?, + val channel: String?, + val siteId: String?, + val subSiteId: String?, + val partner: String?, + val marketingPartner: String?, + val agency: String?, + val accountId: String?, + val keywords: String? +) + +data class Advertisement( + val adset: String?, + val adsetId: String?, + val adgroup: String?, + val adgroupId: String?, + val adgroupName: String?, + val ad: String?, + val adId: String?, + val adType: String?, + val adFormat: String?, + val adEventId: String? +) + +data class DeepLink( + val destination: String?, + val value: String?, + val sub1: String?, + val sub2: String?, + val sub3: String?, + val sub4: String?, + val sub5: String?, + val sub6: String?, + val sub7: String?, + val sub8: String?, + val sub9: String?, + val sub10: String?, + val scheme: String?, + val forceDeeplink: Boolean?, + val redirect: String?, + val webRedirect: String?, + val androidUrl: String?, + val iosUrl: String? +) + +data class Timestamps( + val clickTime: String?, + val installTime: String?, + val isFirstLaunch: Boolean?, + val clickLookback: String?, + val viewthroughLookback: String?, + val reengagementWindow: String? +) + +data class Cost( + val original: String?, + val centsUsd: String?, + val currency: String?, + val value: String?, + val model: String?, + val perInstall: String? +) + +data class Device( + val os: String?, + val osVersion: String?, + val model: String?, + val userAgent: String?, + val ipAddress: String?, + val mediaType: String?, + val android: AndroidIdentifiers, + val ios: IosIdentifiers +) + +data class AndroidIdentifiers( + val advertisingId: String?, + val advertisingIdSha1: String?, + val advertisingIdMd5: String?, + val androidId: String?, + val androidIdSha1: String?, + val androidIdMd5: String?, + val imei: String?, + val imeiSha1: String?, + val imeiMd5: String?, + val oaid: String?, + val oaidSha1: String?, + val oaidMd5: String?, + val fireAdvertisingId: String?, + val emailSha1: String?, + val storeListing: String? +) + +data class IosIdentifiers( + val idfa: String?, + val idfaSha1: String?, + val idfaMd5: String?, + val idfv: String?, + val idfvSha1: String?, + val idfvMd5: String?, + val mac: String?, + val macSha1: String?, + val productPage: String? +) + +data class Retargeting( + val isRetargeting: Boolean?, + val conversionType: String? +) + +data class Engagement( + val isIncentivized: Boolean?, + val clickId: String?, + val referrer: String? +) + +data class Viewability( + val videoTotalLength: String?, + val videoPlayedLength: String?, + val playablePlayedLength: String?, + val adTimeViewed: String?, + val adDisplayedPercent: String?, + val audioTotalLength: String?, + val audioPlayedLength: String? +) + +data class Network( + val meta: MetaParameters?, + val google: GoogleParameters?, + val campaignType: String? +) + +data class MetaParameters( + val isFacebook: Boolean?, + val isPaid: Boolean?, + val isInstagram: Boolean? +) + +data class GoogleParameters( + val accountId: String?, + val clickId: String?, + val network: String?, + val clickUrl: String?, + val latitude: String?, + val videoId: String? +) + +data class CustomParameters( + val sub1: String?, + val sub2: String?, + val sub3: String?, + val sub4: String?, + val sub5: String? +) + +data class SocialPreview( + val title: String?, + val description: String?, + val image: String? +) + +data class System( + val referralCode: String?, + val parameterForwarding: Boolean?, + val baseParametersForward: Boolean? +) + +data class DeepLinkResult( + val origin: Origin, + val campaign: Campaign, + val advertisement: Advertisement, + val deepLink: DeepLink, + val timestamps: Timestamps, + val cost: Cost, + val device: Device, + val retargeting: Retargeting, + val engagement: Engagement, + val viewability: Viewability, + val network: Network, + val customParameters: CustomParameters, + val socialPreview: SocialPreview, + val system: System, + val clickEvent: Map?, + val conversion: Map? +) { + companion object { + fun notFound(): DeepLinkResult { + return DeepLinkResult( + origin = Origin( + source = DeeplinkSource.UDL, + udlStatus = UdlStatus.NOT_FOUND, + status = null, + matchType = null, + isDeferred = null + ), + campaign = Campaign( + mediaSource = null, + name = null, + id = null, + channel = null, + siteId = null, + subSiteId = null, + partner = null, + marketingPartner = null, + agency = null, + accountId = null, + keywords = null + ), + advertisement = Advertisement( + adset = null, + adsetId = null, + adgroup = null, + adgroupId = null, + adgroupName = null, + ad = null, + adId = null, + adType = null, + adFormat = null, + adEventId = null + ), + deepLink = DeepLink( + destination = null, + value = null, + sub1 = null, + sub2 = null, + sub3 = null, + sub4 = null, + sub5 = null, + sub6 = null, + sub7 = null, + sub8 = null, + sub9 = null, + sub10 = null, + scheme = null, + forceDeeplink = null, + redirect = null, + webRedirect = null, + androidUrl = null, + iosUrl = null + ), + timestamps = Timestamps( + clickTime = null, + installTime = null, + isFirstLaunch = null, + clickLookback = null, + viewthroughLookback = null, + reengagementWindow = null + ), + cost = Cost( + original = null, + centsUsd = null, + currency = null, + value = null, + model = null, + perInstall = null + ), + device = Device( + os = null, + osVersion = null, + model = null, + userAgent = null, + ipAddress = null, + mediaType = null, + android = AndroidIdentifiers( + advertisingId = null, + advertisingIdSha1 = null, + advertisingIdMd5 = null, + androidId = null, + androidIdSha1 = null, + androidIdMd5 = null, + imei = null, + imeiSha1 = null, + imeiMd5 = null, + oaid = null, + oaidSha1 = null, + oaidMd5 = null, + fireAdvertisingId = null, + emailSha1 = null, + storeListing = null + ), + ios = IosIdentifiers( + idfa = null, + idfaSha1 = null, + idfaMd5 = null, + idfv = null, + idfvSha1 = null, + idfvMd5 = null, + mac = null, + macSha1 = null, + productPage = null + ) + ), + retargeting = Retargeting( + isRetargeting = null, + conversionType = null + ), + engagement = Engagement( + isIncentivized = null, + clickId = null, + referrer = null + ), + viewability = Viewability( + videoTotalLength = null, + videoPlayedLength = null, + playablePlayedLength = null, + adTimeViewed = null, + adDisplayedPercent = null, + audioTotalLength = null, + audioPlayedLength = null + ), + network = Network( + meta = MetaParameters( + isFacebook = null, + isPaid = null, + isInstagram = null + ), + google = GoogleParameters( + accountId = null, + clickId = null, + network = null, + clickUrl = null, + latitude = null, + videoId = null + ), + campaignType = null + ), + customParameters = CustomParameters( + sub1 = null, + sub2 = null, + sub3 = null, + sub4 = null, + sub5 = null + ), + socialPreview = SocialPreview( + title = null, + description = null, + image = null + ), + system = System( + referralCode = null, + parameterForwarding = null, + baseParametersForward = null + ), + clickEvent = null, + conversion = null + ) + } + } +} \ No newline at end of file diff --git a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/util/AppsFlyerConstants.kt b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/util/AppsFlyerConstants.kt index 5f6c606..b6a7653 100644 --- a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/util/AppsFlyerConstants.kt +++ b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/util/AppsFlyerConstants.kt @@ -1,20 +1,143 @@ package com.metacto.kmm.appsflyer.util object AppsFlyerConstants { + const val PID = "pid" + const val C = "c" + const val AF_PRT = "af_prt" + const val AF_MP = "af_mp" + const val CLICKID = "clickid" + const val AF_SITEID = "af_siteid" + const val AF_SUB_SITEID = "af_sub_siteid" + const val AF_C_ID = "af_c_id" + const val AF_ADSET = "af_adset" + const val AF_ADSET_ID = "af_adset_id" + const val AF_AD = "af_ad" + const val AF_AD_ID = "af_ad_id" + const val AF_AD_TYPE = "af_ad_type" + const val AF_AD_FORMAT = "af_ad_format" + const val AF_CLICK_LOOKBACK = "af_click_lookback" + const val AF_VIEWTHROUGH_LOOKBACK = "af_viewthrough_lookback" + const val AF_CHANNEL = "af_channel" + const val AF_KEYWORDS = "af_keywords" + const val AF_COST_MODEL = "af_cost_model" + const val AF_COST_CURRENCY = "af_cost_currency" + const val AF_COST_VALUE = "af_cost_value" + + const val AF_SUB1 = "af_sub1" + const val AF_SUB2 = "af_sub2" + const val AF_SUB3 = "af_sub3" + const val AF_SUB4 = "af_sub4" + const val AF_SUB5 = "af_sub5" + + const val DEEP_LINK_SUB1 = "deep_link_sub1" + const val DEEP_LINK_SUB2 = "deep_link_sub2" + const val DEEP_LINK_SUB3 = "deep_link_sub3" + const val DEEP_LINK_SUB4 = "deep_link_sub4" + const val DEEP_LINK_SUB5 = "deep_link_sub5" + const val DEEP_LINK_SUB6 = "deep_link_sub6" + const val DEEP_LINK_SUB7 = "deep_link_sub7" + const val DEEP_LINK_SUB8 = "deep_link_sub8" + const val DEEP_LINK_SUB9 = "deep_link_sub9" + const val DEEP_LINK_SUB10 = "deep_link_sub10" + + const val AF_R = "af_r" + const val AF_WEB_DP = "af_web_dp" + const val BASE_DEEP_LINK = "af_dp" + const val AF_FORCE_DEEPLINK = "af_force_deeplink" + const val AF_REF = "af_ref" + const val IS_INCENTIVIZED = "is_incentivized" + const val AF_PARAM_FORWARDING = "af_param_forwarding" + const val AF_BASE_PARAMS_FORWARD = "af_base_params_forward" + const val AF_PARTNER_ACCOUNT_ID = "af_partner_account_id" + const val AF_UA = "af_ua" + const val AF_IP = "af_ip" + const val AF_OS = "af_os" + const val AF_OS_VERSION = "af_os_version" + const val AF_MODEL = "af_model" + const val AF_MEDIA_TYPE = "af_media_type" + const val DEEP_LINK_VALUE = "deep_link_value" + const val AF_OG_TITLE = "af_og_title" + const val AF_OG_DESCRIPTION = "af_og_description" + const val AF_OG_IMAGE = "af_og_image" + const val IS_RETARGETING = "is_retargeting" + const val AF_REENGAGEMENT_WINDOW = "af_reengagement_window" + + const val AF_VIDEO_TOTAL_LENGTH = "af_video_total_length" + const val AF_VIDEO_PLAYED_LENGTH = "af_video_played_length" + const val AF_PLAYABLE_PLAYED_LENGTH = "af_playable_played_length" + const val AF_AD_TIME_VIEWED = "af_ad_time_viewed" + const val AF_AD_DISPLAYED_PERCENT = "af_ad_displayed_percent" + const val AF_AUDIO_TOTAL_LENGTH = "af_audio_total_length" + const val AF_AUDIO_PLAYED_LENGTH = "af_audio_played_length" + + const val ADVERTISING_ID = "advertising_id" + const val SHA1_ADVERTISING_ID = "sha1_advertising_id" + const val MD5_ADVERTISING_ID = "md5_advertising_id" + const val ANDROID_ID = "android_id" + const val SHA1_ANDROID_ID = "sha1_android_id" + const val MD5_ANDROID_ID = "md5_android_id" + const val IMEI = "imei" + const val SHA1_IMEI = "sha1_imei" + const val MD5_IMEI = "md5_imei" + const val OAID = "oaid" + const val SHA1_OAID = "sha1_oaid" + const val MD5_OAID = "md5_oaid" + const val AF_ANDROID_URL = "af_android_url" + const val SHA1_EL = "sha1_el" + const val FIRE_ADVERTISING_ID = "fire_advertising_id" + const val AF_ANDROID_STORE_CSL = "af_android_store_csl" + + const val IDFA = "idfa" + const val IDFV = "idfv" + const val AF_IOS_URL = "af_ios_url" + const val AF_IOS_STORE_CPP = "af_ios_store_cpp" + const val SHA1_IDFA = "sha1_idfa" + const val SHA1_IDFV = "sha1_idfv" + const val MAC = "mac" + const val MD5_IDFA = "md5_idfa" + const val MD5_IDFV = "md5_idfv" + const val SHA1_MAC = "sha1_mac" + + const val MEDIA_SOURCE = "media_source" + const val INSTALL_TIME = "install_time" + const val CLICK_TIME = "click_time" + const val IS_FIRST_LAUNCH = "is_first_launch" const val AF_STATUS = "af_status" + const val ADGROUP_ID = "adgroup_id" + const val ADSET = "adset" + const val ADSET_ID = "adset_id" + const val AGENCY = "agency" + const val CAMPAIGN = "campaign" + const val CAMPAIGN_ID = "campaign_id" + const val HTTP_REFERRER = "http_referrer" + const val RETARGETING_CONVERSION_TYPE = "retargeting_conversion_type" + const val ORIG_COST = "orig_cost" + const val COST_CENTS_USD = "cost_cents_USD" + const val AF_CPI = "af_cpi" + + const val MATCH_TYPE = "match_type" + const val AD_ID = "ad_id" + const val IS_FB = "is_fb" + const val IS_PAID = "is_paid" + const val ADGROUP = "adgroup" + const val ADGROUP_NAME = "adgroup_name" + const val IS_INSTAGRAM = "is_instagram" + + const val EXTERNAL_ACCOUNT_ID = "external_account_id" + const val GCLID = "gclid" + const val LAT = "lat" + const val NETWORK = "network" + const val CLICK_URL = "click_url" + const val VIDEO_ID = "video_id" + const val AD_EVENT_ID = "ad_event_id" + + const val CAMPAIGN_TYPE = "campaign_type" + const val AF_ORGANIC_STATUS = "Organic" + const val DEEP_LINK_DESTINATION = "deep_link_destination" - const val DEEP_LINK_VALUE = "deep_link_value" const val REFERRER_NAME = "af_referrer_name" - const val BASE_DEEP_LINK = "af_dp" - const val CHANNEL = "af_channel" - const val CAMPAIGN = "campaign" const val REFERRER_CUSTOMER_ID = "af_referrer_customer_id" const val REFERRER_UID = "af_referrer_uid" const val REFERRER_IMAGE_URL = "af_referrer_image_url" - - const val LINK = "link" - const val SCHEME = "scheme" - const val HOST = "host" - const val MEDIA_SOURCE = "media_source" -} \ No newline at end of file +} diff --git a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/util/Extensions.kt b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/util/Extensions.kt index 6855a22..4ca4aaf 100644 --- a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/util/Extensions.kt +++ b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/util/Extensions.kt @@ -34,7 +34,7 @@ fun ShareLinkGenerator.generateDeepLinkValue( val stringBuilder = StringBuilder() stringBuilder.append("${AppsFlyerConstants.DEEP_LINK_DESTINATION}=$destination") channel?.let { - stringBuilder.append("__${AppsFlyerConstants.CHANNEL}=$it") + stringBuilder.append("__${AppsFlyerConstants.AF_CHANNEL}=$it") } referrerCustomerId?.let { stringBuilder.append("__${AppsFlyerConstants.REFERRER_CUSTOMER_ID}=$it") diff --git a/appsflyer/src/commonTest/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkMetadataTest.kt b/appsflyer/src/commonTest/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkMetadataTest.kt deleted file mode 100644 index 150aed8..0000000 --- a/appsflyer/src/commonTest/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkMetadataTest.kt +++ /dev/null @@ -1,91 +0,0 @@ -package com.metacto.kmm.appsflyer.model - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNull - -class DeepLinkMetadataTest { - - @Test - fun testParseJsonDestination_withValidJSON() { - val jsonInput = """{"deep_link_destination":"/auth/install-preauth"}""" - val result = jsonInput.parseJsonDestination() - assertEquals("/auth/install-preauth", result) - } - - @Test - fun testParseJsonDestination_withJSONContainingPathWithParameters() { - val jsonInput = """{"deep_link_destination":"/property/tt1ysu65br0a1u0up45b4awx__af_channel=share__af_referrer_customer_id=gesiws1opky86sz97o6wvo9m"}""" - val result = jsonInput.parseJsonDestination() - assertEquals("/property/tt1ysu65br0a1u0up45b4awx", result) - } - - @Test - fun testParseJsonDestination_withJSONContainingWhitespace() { - val jsonInput = """ {"deep_link_destination":"/auth/install-preauth"} """ - val result = jsonInput.parseJsonDestination() - assertEquals("/auth/install-preauth", result) - } - - @Test - fun testParseJsonDestination_withInvalidJSON() { - val invalidJsonInput = """{"deep_link_destination":"/auth/install-preauth""" - val result = invalidJsonInput.parseJsonDestination() - assertNull(result) - } - - @Test - fun testParseJsonDestination_withEmptyDestinationValue() { - val jsonInput = """{"deep_link_destination":""}""" - val result = jsonInput.parseJsonDestination() - assertEquals("", result) - } - - @Test - fun testParseJsonDestination_withMissingDeepLinkDestinationKey() { - val jsonInput = """{"other_key":"value"}""" - val result = jsonInput.parseJsonDestination() - assertNull(result) - } - - @Test - fun testParseNormalDestination_withSimpleKeyValueFormat() { - val normalInput = "deep_link_destination=/property/tt1ysu65br0a1u0up45b4awx__af_channel=share" - val result = normalInput.parseNormalDestination() - assertEquals("/property/tt1ysu65br0a1u0up45b4awx", result) - } - - @Test - fun testParseNormalDestination_withFullParameterString() { - val normalInput = "deep_link_destination=/property/tt1ysu65br0a1u0up45b4awx__af_channel=share__af_referrer_customer_id=gesiws1opky86sz97o6wvo9m__af_referrer_name=Mahmoud Elshamy__username=shamyyoun" - val result = normalInput.parseNormalDestination() - assertEquals("/property/tt1ysu65br0a1u0up45b4awx", result) - } - - @Test - fun testParseNormalDestination_withOnlyDestinationValue() { - val normalInput = "deep_link_destination=/auth/install-preauth" - val result = normalInput.parseNormalDestination() - assertEquals("/auth/install-preauth", result) - } - - @Test - fun testParseNormalDestination_returnsNullWhenDestinationKeyNotFound() { - val normalInput = "af_channel=share__af_referrer_customer_id=gesiws1opky86sz97o6wvo9m" - val result = normalInput.parseNormalDestination() - assertNull(result) - } - - @Test - fun testParseNormalDestination_withEmptyString() { - val result = "".parseNormalDestination() - assertNull(result) - } - - @Test - fun testParseNormalDestination_withDestinationAtDifferentPosition() { - val normalInput = "af_channel=share__deep_link_destination=/home__af_referrer_customer_id=test" - val result = normalInput.parseNormalDestination() - assertEquals("/home", result) - } -} diff --git a/appsflyer/src/commonTest/kotlin/com/metacto/kmm/appsflyer/model/HelperFunctionsTest.kt b/appsflyer/src/commonTest/kotlin/com/metacto/kmm/appsflyer/model/HelperFunctionsTest.kt deleted file mode 100644 index 7658f13..0000000 --- a/appsflyer/src/commonTest/kotlin/com/metacto/kmm/appsflyer/model/HelperFunctionsTest.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.metacto.kmm.appsflyer.model - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNull -import kotlin.test.assertTrue - -class HelperFunctionsTest { - - @Test - fun testHasDescopeToken_withDtKey() { - val extras: Map = mapOf("dt" to "sometoken") - assertTrue(hasDescopeToken(extras)) - } - - @Test - fun testHasDescopeToken_withoutDtKey() { - val extras: Map = mapOf("other_key" to "value") - assertFalse(hasDescopeToken(extras)) - } - - @Test - fun testHasDescopeToken_withNullExtras() { - assertFalse(hasDescopeToken(null)) - } - - @Test - fun testHasDescopeToken_withEmptyExtras() { - val extras: Map = emptyMap() - assertFalse(hasDescopeToken(extras)) - } - - @Test - fun testHasLoginType_withLtKey() { - val extras: Map = mapOf("lt" to "email") - assertTrue(hasLoginType(extras)) - } - - @Test - fun testHasLoginType_withoutLtKey() { - val extras: Map = mapOf("other_key" to "value") - assertFalse(hasLoginType(extras)) - } - - @Test - fun testHasLoginType_withNullExtras() { - assertFalse(hasLoginType(null)) - } - - @Test - fun testHasLoginType_withEmptyExtras() { - val extras: Map = emptyMap() - assertFalse(hasLoginType(extras)) - } - - @Test - fun testGetLoginType_withEmail() { - val extras: Map = mapOf("lt" to "email") - assertEquals(LoginType.EMAIL, getLoginType(extras)) - } - - @Test - fun testGetLoginType_withPhone() { - val extras: Map = mapOf("lt" to "phone") - assertEquals(LoginType.PHONE, getLoginType(extras)) - } - - @Test - fun testGetLoginType_withInvalidValue() { - val extras: Map = mapOf("lt" to "invalid") - assertNull(getLoginType(extras)) - } - - @Test - fun testGetLoginType_withNullExtras() { - assertNull(getLoginType(null)) - } - - @Test - fun testGetLoginType_withEmptyExtras() { - val extras: Map = emptyMap() - assertNull(getLoginType(extras)) - } - - @Test - fun testGetLoginType_withMissingKey() { - val extras: Map = mapOf("other_key" to "value") - assertNull(getLoginType(extras)) - } - - @Test - fun testGetAfSub1_withValidValue() { - val extras: Map = mapOf("af_sub1" to "test_value") - assertEquals("test_value", getAfSub1(extras)) - } - - @Test - fun testGetAfSub1_withNullExtras() { - assertNull(getAfSub1(null)) - } - - @Test - fun testGetAfSub1_withEmptyExtras() { - val extras: Map = emptyMap() - assertNull(getAfSub1(extras)) - } - - @Test - fun testGetAfSub1_withMissingKey() { - val extras: Map = mapOf("other_key" to "value") - assertNull(getAfSub1(extras)) - } - - @Test - fun testGetAfSub1_withNumericValue() { - val extras: Map = mapOf("af_sub1" to 12345) - assertEquals("12345", getAfSub1(extras)) - } -} diff --git a/appsflyer/src/iosMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.ios.kt b/appsflyer/src/iosMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.ios.kt index 18b9935..4616c8b 100644 --- a/appsflyer/src/iosMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.ios.kt +++ b/appsflyer/src/iosMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.ios.kt @@ -7,18 +7,7 @@ import AppsFlyerLib.AppsFlyerDeepLinkDelegateProtocol import AppsFlyerLib.AppsFlyerDeepLinkResult import AppsFlyerLib.AppsFlyerLib import AppsFlyerLib.AppsFlyerLibDelegateProtocol -import com.metacto.kmm.appsflyer.model.DeepLinkError -import com.metacto.kmm.appsflyer.model.DeepLinkResult -import com.metacto.kmm.appsflyer.model.getDeepLinkValue -import com.metacto.kmm.appsflyer.model.getDeepLinkMetadata -import com.metacto.kmm.appsflyer.model.getDestinationPath -import com.metacto.kmm.appsflyer.model.hasDescopeToken -import com.metacto.kmm.appsflyer.model.hasLoginType -import com.metacto.kmm.appsflyer.model.getLoginType -import com.metacto.kmm.appsflyer.model.getAfSub1 -import com.metacto.kmm.appsflyer.model.DeeplinkSource -import com.metacto.kmm.appsflyer.model.UdlStatus -import com.metacto.kmm.appsflyer.model.GcdAfStatus +import com.metacto.kmm.appsflyer.model.* import com.metacto.kmm.appsflyer.util.getAppAttributionResult import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.convert @@ -68,59 +57,77 @@ actual class OneLinkService actual constructor( } override fun didResolveDeepLink(result: AppsFlyerDeepLinkResult) { + println("🔗 DEEPLINK [KMM-UDL] didResolveDeepLink called with status: ${result.status}") when (result.status) { AFSDKDeepLinkResultStatus.AFSDKDeepLinkResultStatusFound -> { + println("🔗 DEEPLINK [KMM-UDL] Status: FOUND") val deepLink = result.deepLink val clickEventValues = deepLink?.clickEvent?.toMap() ?: emptyMap() - val fullDeepLinkValue = - deepLink?.deeplinkValue ?: this.getDeepLinkValue(clickEventValues) - val metadata = this.getDeepLinkMetadata(fullDeepLinkValue, clickEventValues) + println("🔗 DEEPLINK [KMM-UDL] clickEventValues: $clickEventValues") + + val deepLinkValue = deepLink?.deeplinkValue ?: clickEventValues.getDeepLinkValue() + println("🔗 DEEPLINK [KMM-UDL] deepLinkValue: $deepLinkValue") + + val destination = deepLinkValue?.parseDestination() + println("🔗 DEEPLINK [KMM-UDL] destination: $destination") val deepLinkResult = DeepLinkResult( - destination = fullDeepLinkValue?.let { this.getDestinationPath(fullDeepLinkValue) }, - campaign = deepLink?.campaign, - campaignId = deepLink?.campaignId, - clickHttpReferrer = deepLink?.clickHTTPReferrer, - isDeferred = deepLink?.isDeferred, - mediaSource = deepLink?.mediaSource, - matchType = deepLink?.matchType, - clickEventJson = deepLink?.clickEvent.toString(), - metadata = metadata, - deeplinkSource = DeeplinkSource.UDL, - hasDescopeToken = hasDescopeToken(metadata.extras), - hasLoginType = hasLoginType(metadata.extras), - loginType = getLoginType(metadata.extras), - udlStatus = UdlStatus.FOUND, - udlMatchType = deepLink?.matchType, - gcdAfStatus = null, - gcdMediaSource = null, - gcdCampaign = null, - afSub1 = getAfSub1(clickEventValues), - extraLink = clickEventValues["link"] as? String + origin = clickEventValues.buildOrigin(DeeplinkSource.UDL, UdlStatus.FOUND, null, deepLink?.isDeferred), + campaign = clickEventValues.buildCampaign(), + advertisement = clickEventValues.buildAdvertisement(), + deepLink = clickEventValues.buildDeepLink(destination), + timestamps = clickEventValues.buildTimestamps(), + cost = clickEventValues.buildCost(), + device = clickEventValues.buildDevice(), + retargeting = clickEventValues.buildRetargeting(), + engagement = clickEventValues.buildEngagement(), + viewability = clickEventValues.buildViewability(), + network = clickEventValues.buildNetwork(), + customParameters = clickEventValues.buildCustomParameters(), + socialPreview = clickEventValues.buildSocialPreview(), + system = clickEventValues.buildSystem(), + clickEvent = clickEventValues, + conversion = null ) + println("🔗 DEEPLINK [KMM-UDL] ===== DeepLinkResult =====") + println("🔗 DEEPLINK [KMM-UDL] $deepLinkResult") + println("🔗 DEEPLINK [KMM-UDL] =====================================") + println("🔗 DEEPLINK [KMM-UDL] Calling listener.onDeepLinkingResult") options.listener.onDeepLinkingResult(deepLinkResult) } AFSDKDeepLinkResultStatus.AFSDKDeepLinkResultStatusNotFound -> { + println("🔗 DEEPLINK [KMM-UDL] Status: NOT_FOUND") options.listener.onDeepLinkingResult(null) } AFSDKDeepLinkResultStatus.AFSDKDeepLinkResultStatusFailure -> { + println("🔗 DEEPLINK [KMM-UDL] Status: FAILURE - error: ${result.error}") options.listener.onDeepLinkingError(DeepLinkError(result.error)) } else -> { + println("🔗 DEEPLINK [KMM-UDL] Status: UNKNOWN") options.listener.onDeepLinkingResult(null) } } } override fun onConversionDataFail(error: NSError) { - // No-op + println("🔗 DEEPLINK [KMM-GCD] onConversionDataFail: ${error.localizedDescription}") } override fun onConversionDataSuccess(conversionInfo: Map) { + println("🔗 DEEPLINK [KMM-GCD] onConversionDataSuccess called") + println("🔗 DEEPLINK [KMM-GCD] conversionInfo: $conversionInfo") + println("🔗 DEEPLINK [KMM-GCD] conversionInfo size: ${conversionInfo.size}") + println("🔗 DEEPLINK [KMM-GCD] conversionInfo keys: ${conversionInfo.keys}") + println("🔗 DEEPLINK [KMM-GCD] All conversionInfo entries:") + conversionInfo.forEach { (key, value) -> + println("🔗 DEEPLINK [KMM-GCD] $key = $value") + } + val appConversionResult = this.getAppAttributionResult(conversionInfo) options.listener.onAppAttribution(appConversionResult.isOrganic, appConversionResult.extras) @@ -129,28 +136,47 @@ actual class OneLinkService actual constructor( @Suppress("UNCHECKED_CAST") val extras = appConversionResult.extras as Map + println("🔗 DEEPLINK [KMM-GCD] extras: $extras") + println("🔗 DEEPLINK [KMM-GCD] gcdMediaSource: $gcdMediaSource") + println("🔗 DEEPLINK [KMM-GCD] gcdCampaign: $gcdCampaign") + + val isFirstLaunch = extras.getBoolean("is_first_launch") ?: false + println("🔗 DEEPLINK [KMM-GCD] isFirstLaunch: $isFirstLaunch") + + // Try to get deep_link_value from raw conversionInfo first, then from extras + val deepLinkValue = (conversionInfo["deep_link_value"] ?: extras["deep_link_value"])?.toString() + println("🔗 DEEPLINK [KMM-GCD] deep_link_value from conversionInfo: ${conversionInfo["deep_link_value"]}") + println("🔗 DEEPLINK [KMM-GCD] deep_link_value from extras: ${extras["deep_link_value"]}") + println("🔗 DEEPLINK [KMM-GCD] Final deepLinkValue: $deepLinkValue") + + val destination = deepLinkValue?.parseDestination() + println("🔗 DEEPLINK [KMM-GCD] destination: $destination") + + val gcdStatus = if (appConversionResult.isOrganic) GcdAfStatus.ORGANIC else GcdAfStatus.NON_ORGANIC + val deepLinkResult = DeepLinkResult( - destination = null, - campaign = gcdCampaign, - campaignId = null, - clickHttpReferrer = null, - isDeferred = false, - mediaSource = gcdMediaSource, - matchType = null, - clickEventJson = null, - metadata = null, - deeplinkSource = DeeplinkSource.GCD, - hasDescopeToken = hasDescopeToken(extras), - hasLoginType = hasLoginType(extras), - loginType = getLoginType(extras), - udlStatus = null, - udlMatchType = null, - gcdAfStatus = if (appConversionResult.isOrganic) GcdAfStatus.ORGANIC else GcdAfStatus.NON_ORGANIC, - gcdMediaSource = gcdMediaSource, - gcdCampaign = gcdCampaign, - afSub1 = getAfSub1(extras), - extraLink = null + origin = extras.buildOrigin(DeeplinkSource.GCD, null, gcdStatus, isFirstLaunch), + campaign = extras.buildCampaign(), + advertisement = extras.buildAdvertisement(), + deepLink = extras.buildDeepLink(destination), + timestamps = extras.buildTimestamps(), + cost = extras.buildCost(), + device = extras.buildDevice(), + retargeting = extras.buildRetargeting(), + engagement = extras.buildEngagement(), + viewability = extras.buildViewability(), + network = extras.buildNetwork(), + customParameters = extras.buildCustomParameters(), + socialPreview = extras.buildSocialPreview(), + system = extras.buildSystem(), + clickEvent = null, + conversion = conversionInfo ) + + println("🔗 DEEPLINK [KMM-GCD] ===== DeepLinkResult =====") + println("🔗 DEEPLINK [KMM-GCD] $deepLinkResult") + println("🔗 DEEPLINK [KMM-GCD] =====================================") + println("🔗 DEEPLINK [KMM-GCD] Calling listener.onDeepLinkingResult") options.listener.onDeepLinkingResult(deepLinkResult) } From c19339056897082a3ce2e094f75323d01f15b0e3 Mon Sep 17 00:00:00 2001 From: Tarek Sabry Date: Tue, 11 Nov 2025 16:57:55 +0200 Subject: [PATCH 2/4] Refactor and restructure OneLinkServices --- .github/workflows/deployRelease.yml | 2 +- .../kmm/appsflyer/OneLinkService.android.kt | 167 +++++------------- .../kmm/appsflyer/model/DeepLinkError.kt | 5 +- .../metacto/kmm/appsflyer/OneLinkListener.kt | 9 +- .../kmm/appsflyer/model/DeepLinkHelpers.kt | 49 +++-- .../kmm/appsflyer/OneLinkService.ios.kt | 104 +++-------- 6 files changed, 103 insertions(+), 233 deletions(-) diff --git a/.github/workflows/deployRelease.yml b/.github/workflows/deployRelease.yml index 405df46..62fb4f5 100644 --- a/.github/workflows/deployRelease.yml +++ b/.github/workflows/deployRelease.yml @@ -40,7 +40,7 @@ jobs: - name: Update library version env: - PUBLISH_VERSION: 0.0.${{ github.run_number }} + PUBLISH_VERSION: 1.0.${{ github.run_number }} run: echo PUBLISH_VERSION=$PUBLISH_VERSION > ./versions.properties - name: Update local properties diff --git a/appsflyer/src/androidMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.android.kt b/appsflyer/src/androidMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.android.kt index f27441b..00c8dae 100644 --- a/appsflyer/src/androidMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.android.kt +++ b/appsflyer/src/androidMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.android.kt @@ -23,187 +23,101 @@ actual class OneLinkService actual constructor( private val conversionListener = object : AppsFlyerConversionListener { override fun onConversionDataSuccess(p0: MutableMap?) { if (p0 != null) { - println("🔗 DEEPLINK [KMM-GCD] onConversionDataSuccess called") - println("🔗 DEEPLINK [KMM-GCD] conversionInfo: $p0") - val appConversionResult = this@OneLinkService.getAppAttributionResult(p0) - options.listener.onAppAttribution( - appConversionResult.isOrganic, - appConversionResult.extras - ) - - val gcdMediaSource = appConversionResult.extras["media_source"]?.toString() - val gcdCampaign = appConversionResult.extras["campaign"]?.toString() @Suppress("UNCHECKED_CAST") val extras = appConversionResult.extras as Map - - println("🔗 DEEPLINK [KMM-GCD] extras: $extras") - println("🔗 DEEPLINK [KMM-GCD] gcdMediaSource: $gcdMediaSource") - println("🔗 DEEPLINK [KMM-GCD] gcdCampaign: $gcdCampaign") + @Suppress("UNCHECKED_CAST") + val conversion = p0 as Map val isFirstLaunch = (extras["is_first_launch"] as? String)?.toBoolean() ?: (extras["is_first_launch"] as? Boolean) ?: false - println("🔗 DEEPLINK [KMM-GCD] isFirstLaunch: $isFirstLaunch") - val deepLinkValue = extras.getDeepLinkValue() - println("🔗 DEEPLINK [KMM-GCD] deepLinkValue: $deepLinkValue") + if (!isFirstLaunch) return + val deepLinkValue = extras.getDeepLinkValue() val destination = deepLinkValue?.parseDestination() - println("🔗 DEEPLINK [KMM-GCD] destination: $destination") - - val result = KMMDeepLinkResult( - origin = extras.buildOrigin( - source = DeeplinkSource.GCD, - udlStatus = null, - gcdStatus = if (appConversionResult.isOrganic) GcdAfStatus.ORGANIC else GcdAfStatus.NON_ORGANIC, - isDeferred = isFirstLaunch - ), - campaign = extras.buildCampaign(), - advertisement = extras.buildAdvertisement(), - deepLink = extras.buildDeepLink(destination), - timestamps = extras.buildTimestamps(), - cost = extras.buildCost(), - device = extras.buildDevice(), - retargeting = extras.buildRetargeting(), - engagement = extras.buildEngagement(), - viewability = extras.buildViewability(), - network = extras.buildNetwork(), - customParameters = extras.buildCustomParameters(), - socialPreview = extras.buildSocialPreview(), - system = extras.buildSystem(), + val gcdStatus = if (appConversionResult.isOrganic) GcdAfStatus.ORGANIC else GcdAfStatus.NON_ORGANIC + + val result = extras.buildDeepLinkResult( + source = DeeplinkSource.GCD, + udlStatus = null, + gcdStatus = gcdStatus, + isDeferred = true, + destination = destination, clickEvent = extras, - conversion = p0 as? Map + conversion = conversion ) - println("🔗 DEEPLINK [KMM-GCD] ===== DeepLinkResult Summary (onConversionDataSuccess) =====") - println("🔗 DEEPLINK [KMM-GCD] origin.source: ${result.origin.source}") - println("🔗 DEEPLINK [KMM-GCD] origin.status: ${result.origin.status}") - println("🔗 DEEPLINK [KMM-GCD] origin.isDeferred: ${result.origin.isDeferred}") - println("🔗 DEEPLINK [KMM-GCD] deepLink.destination: ${result.deepLink.destination}") - println("🔗 DEEPLINK [KMM-GCD] deepLink.value: ${result.deepLink.value}") - println("🔗 DEEPLINK [KMM-GCD] deepLink.scheme: ${result.deepLink.scheme}") - println("🔗 DEEPLINK [KMM-GCD] campaign.mediaSource: ${result.campaign.mediaSource}") - println("🔗 DEEPLINK [KMM-GCD] campaign.name: ${result.campaign.name}") - println("🔗 DEEPLINK [KMM-GCD] customParameters.sub1: ${result.customParameters.sub1}") - println("🔗 DEEPLINK [KMM-GCD] customParameters.sub2: ${result.customParameters.sub2}") - println("🔗 DEEPLINK [KMM-GCD] customParameters.sub3: ${result.customParameters.sub3}") - println("🔗 DEEPLINK [KMM-GCD] =====================================") - println("🔗 DEEPLINK [KMM-GCD] Calling listener.onDeepLinkingResult with GCD result") - options.listener.onDeepLinkingResult(result) + options.listener.onAttributionData(result) } } override fun onConversionDataFail(p0: String?) { - // No-op + if (p0 != null) { + options.listener.onDeepLinkingError(DeepLinkError.Generic(p0)) + } } override fun onAppOpenAttribution(p0: MutableMap?) { if (p0 != null) { - println("🔗 DEEPLINK [KMM-GCD] onAppOpenAttribution called") - println("🔗 DEEPLINK [KMM-GCD] attributionData: $p0") - val appConversionResult = this@OneLinkService.getAppAttributionResult(p0) - options.listener.onAppAttribution( - appConversionResult.isOrganic, - appConversionResult.extras - ) - @Suppress("UNCHECKED_CAST") val extras = appConversionResult.extras as Map + @Suppress("UNCHECKED_CAST") + val conversion = p0 as Map val deepLinkValue = extras.getDeepLinkValue() val destination = deepLinkValue?.parseDestination() - - val result = KMMDeepLinkResult( - origin = extras.buildOrigin( - source = DeeplinkSource.GCD, - udlStatus = null, - gcdStatus = if (appConversionResult.isOrganic) GcdAfStatus.ORGANIC else GcdAfStatus.NON_ORGANIC, - isDeferred = false - ), - campaign = extras.buildCampaign(), - advertisement = extras.buildAdvertisement(), - deepLink = extras.buildDeepLink(destination), - timestamps = extras.buildTimestamps(), - cost = extras.buildCost(), - device = extras.buildDevice(), - retargeting = extras.buildRetargeting(), - engagement = extras.buildEngagement(), - viewability = extras.buildViewability(), - network = extras.buildNetwork(), - customParameters = extras.buildCustomParameters(), - socialPreview = extras.buildSocialPreview(), - system = extras.buildSystem(), + val gcdStatus = if (appConversionResult.isOrganic) GcdAfStatus.ORGANIC else GcdAfStatus.NON_ORGANIC + + val result = extras.buildDeepLinkResult( + source = DeeplinkSource.GCD, + udlStatus = null, + gcdStatus = gcdStatus, + isDeferred = false, + destination = destination, clickEvent = extras, - conversion = p0 as? Map + conversion = conversion ) - println("🔗 DEEPLINK [KMM-GCD] ===== DeepLinkResult =====") - println("🔗 DEEPLINK [KMM-GCD] $result") - println("🔗 DEEPLINK [KMM-GCD] =====================================") - println("🔗 DEEPLINK [KMM-GCD] Calling listener.onDeepLinkingResult") - options.listener.onDeepLinkingResult(result) + options.listener.onAttributionData(result) } } override fun onAttributionFailure(p0: String?) { - // No-op + if (p0 != null) { + options.listener.onDeepLinkingError(DeepLinkError.Generic(p0)) + } } } private val deepLinkListener = DeepLinkListener { deepLinkResult -> when (deepLinkResult.status) { DeepLinkResult.Status.FOUND -> { - println("🔗 DEEPLINK [KMM-UDL] Status: FOUND") val deepLink = deepLinkResult.deepLink val clickEventValues = deepLink.clickEvent.toMap() - println("🔗 DEEPLINK [KMM-UDL] clickEventValues: $clickEventValues") - val deepLinkValue = deepLink.deepLinkValue ?: clickEventValues.getDeepLinkValue() - println("🔗 DEEPLINK [KMM-UDL] deepLinkValue: $deepLinkValue") - val destination = deepLinkValue?.parseDestination() - println("🔗 DEEPLINK [KMM-UDL] destination: $destination") - val result = KMMDeepLinkResult( - origin = clickEventValues.buildOrigin( - source = DeeplinkSource.UDL, - udlStatus = UdlStatus.FOUND, - gcdStatus = null, - isDeferred = deepLink.isDeferred - ), - campaign = clickEventValues.buildCampaign(), - advertisement = clickEventValues.buildAdvertisement(), - deepLink = clickEventValues.buildDeepLink(destination), - timestamps = clickEventValues.buildTimestamps(), - cost = clickEventValues.buildCost(), - device = clickEventValues.buildDevice(), - retargeting = clickEventValues.buildRetargeting(), - engagement = clickEventValues.buildEngagement(), - viewability = clickEventValues.buildViewability(), - network = clickEventValues.buildNetwork(), - customParameters = clickEventValues.buildCustomParameters(), - socialPreview = clickEventValues.buildSocialPreview(), - system = clickEventValues.buildSystem(), + val result = clickEventValues.buildDeepLinkResult( + source = DeeplinkSource.UDL, + udlStatus = UdlStatus.FOUND, + gcdStatus = null, + isDeferred = deepLink.isDeferred, + destination = destination, clickEvent = clickEventValues, conversion = null ) - println("🔗 DEEPLINK [KMM-UDL] ===== DeepLinkResult =====") - println("🔗 DEEPLINK [KMM-UDL] $result") - println("🔗 DEEPLINK [KMM-UDL] =====================================") - println("🔗 DEEPLINK [KMM-UDL] Calling listener.onDeepLinkingResult") options.listener.onDeepLinkingResult(result) } DeepLinkResult.Status.NOT_FOUND -> { - println("🔗 DEEPLINK [KMM-UDL] Status: NOT_FOUND") - options.listener.onDeepLinkingResult(null) + options.listener.onDeepLinkNotFound(KMMDeepLinkResult.notFound()) } else -> { - println("🔗 DEEPLINK [KMM-UDL] Status: ERROR") options.listener.onDeepLinkingError(deepLinkResult.error.toError()) } } @@ -214,9 +128,9 @@ actual class OneLinkService actual constructor( options.enableDebugLog?.let { setDebugLog(it) } options.minTimeBetweenSessions?.let { setMinTimeBetweenSessions(it) } if (options.appInviteOneLinkTemplateId != null) { - // Set the OneLink template id for share invite links setAppInviteOneLink(options.appInviteOneLinkTemplateId) } + subscribeForDeepLink(deepLinkListener) init(options.devAppKey, conversionListener, options.context as Context) } @@ -254,6 +168,7 @@ actual class OneLinkService actual constructor( *options.oneLinkCustomDomains?.toTypedArray().orEmpty() ) } + initialize() AppsFlyerLib.getInstance().start(options.context as Context, options.devAppKey) } diff --git a/appsflyer/src/androidMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkError.kt b/appsflyer/src/androidMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkError.kt index a31e248..fc4fd85 100644 --- a/appsflyer/src/androidMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkError.kt +++ b/appsflyer/src/androidMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkError.kt @@ -2,12 +2,15 @@ package com.metacto.kmm.appsflyer.model import com.appsflyer.deeplink.DeepLinkResult -actual open class DeepLinkError { +actual open class DeepLinkError( + open val message: String? = null +) { data object Timeout : DeepLinkError() data object Network : DeepLinkError() data object HttpStatusCode : DeepLinkError() data object Unexpected : DeepLinkError() data object DeveloperError : DeepLinkError() + data class Generic(override val message: String) : DeepLinkError(message) } diff --git a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/OneLinkListener.kt b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/OneLinkListener.kt index 5e4e33e..2b46dc3 100644 --- a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/OneLinkListener.kt +++ b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/OneLinkListener.kt @@ -4,11 +4,8 @@ import com.metacto.kmm.appsflyer.model.DeepLinkError import com.metacto.kmm.appsflyer.model.DeepLinkResult interface OneLinkListener { - fun onAppAttribution( - isOrganic: Boolean, - extras: Map? - ) - - fun onDeepLinkingResult(result: DeepLinkResult?) + fun onDeepLinkingResult(result: DeepLinkResult) + fun onDeepLinkNotFound(result: DeepLinkResult) + fun onAttributionData(result: DeepLinkResult) fun onDeepLinkingError(error: DeepLinkError) } \ No newline at end of file diff --git a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkHelpers.kt b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkHelpers.kt index a5cea7e..06d5564 100644 --- a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkHelpers.kt +++ b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkHelpers.kt @@ -1,32 +1,16 @@ package com.metacto.kmm.appsflyer.model import com.metacto.kmm.appsflyer.util.AppsFlyerConstants -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive fun Map.getDeepLinkValue(): String? { return this[AppsFlyerConstants.DEEP_LINK_VALUE]?.toString() } fun String.parseDestination(): String? { - return parseJsonDestination() ?: parseNormalDestination() -} - -internal fun String.parseJsonDestination(): String? { - return try { - val destinationValue = Json.parseToJsonElement(this.trim()).let { - it.jsonObject[AppsFlyerConstants.DEEP_LINK_DESTINATION]?.jsonPrimitive?.content - } - - val values = destinationValue?.split("__") - return values?.firstOrNull() - } catch (_: Throwable) { - null + if (!this.contains("__")) { + return this } -} -internal fun String.parseNormalDestination(): String? { val values = this.split("__") return values.find { it.startsWith(AppsFlyerConstants.DEEP_LINK_DESTINATION) }?.substringAfter("=") } @@ -246,4 +230,33 @@ fun Map.buildSystem(): System { parameterForwarding = getBoolean(AppsFlyerConstants.AF_PARAM_FORWARDING), baseParametersForward = getBoolean(AppsFlyerConstants.AF_BASE_PARAMS_FORWARD) ) +} + +fun Map.buildDeepLinkResult( + source: DeeplinkSource, + udlStatus: UdlStatus?, + gcdStatus: GcdAfStatus?, + isDeferred: Boolean?, + destination: String?, + clickEvent: Map?, + conversion: Map? +): DeepLinkResult { + return DeepLinkResult( + origin = buildOrigin(source, udlStatus, gcdStatus, isDeferred), + campaign = buildCampaign(), + advertisement = buildAdvertisement(), + deepLink = buildDeepLink(destination), + timestamps = buildTimestamps(), + cost = buildCost(), + device = buildDevice(), + retargeting = buildRetargeting(), + engagement = buildEngagement(), + viewability = buildViewability(), + network = buildNetwork(), + customParameters = buildCustomParameters(), + socialPreview = buildSocialPreview(), + system = buildSystem(), + clickEvent = clickEvent, + conversion = conversion + ) } \ No newline at end of file diff --git a/appsflyer/src/iosMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.ios.kt b/appsflyer/src/iosMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.ios.kt index 4616c8b..1decab7 100644 --- a/appsflyer/src/iosMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.ios.kt +++ b/appsflyer/src/iosMain/kotlin/com/metacto/kmm/appsflyer/OneLinkService.ios.kt @@ -32,9 +32,7 @@ actual class OneLinkService actual constructor( actual fun initialize() { AppsFlyerLib.shared().apply { - options.devAppKey.let { - setAppsFlyerDevKey(options.devAppKey) - } + setAppsFlyerDevKey(options.devAppKey) setAppleAppID(options.appleAppId!!) @@ -57,127 +55,71 @@ actual class OneLinkService actual constructor( } override fun didResolveDeepLink(result: AppsFlyerDeepLinkResult) { - println("🔗 DEEPLINK [KMM-UDL] didResolveDeepLink called with status: ${result.status}") when (result.status) { AFSDKDeepLinkResultStatus.AFSDKDeepLinkResultStatusFound -> { - println("🔗 DEEPLINK [KMM-UDL] Status: FOUND") val deepLink = result.deepLink val clickEventValues = deepLink?.clickEvent?.toMap() ?: emptyMap() - println("🔗 DEEPLINK [KMM-UDL] clickEventValues: $clickEventValues") - val deepLinkValue = deepLink?.deeplinkValue ?: clickEventValues.getDeepLinkValue() - println("🔗 DEEPLINK [KMM-UDL] deepLinkValue: $deepLinkValue") - val destination = deepLinkValue?.parseDestination() - println("🔗 DEEPLINK [KMM-UDL] destination: $destination") - - val deepLinkResult = DeepLinkResult( - origin = clickEventValues.buildOrigin(DeeplinkSource.UDL, UdlStatus.FOUND, null, deepLink?.isDeferred), - campaign = clickEventValues.buildCampaign(), - advertisement = clickEventValues.buildAdvertisement(), - deepLink = clickEventValues.buildDeepLink(destination), - timestamps = clickEventValues.buildTimestamps(), - cost = clickEventValues.buildCost(), - device = clickEventValues.buildDevice(), - retargeting = clickEventValues.buildRetargeting(), - engagement = clickEventValues.buildEngagement(), - viewability = clickEventValues.buildViewability(), - network = clickEventValues.buildNetwork(), - customParameters = clickEventValues.buildCustomParameters(), - socialPreview = clickEventValues.buildSocialPreview(), - system = clickEventValues.buildSystem(), + + val deepLinkResult = clickEventValues.buildDeepLinkResult( + source = DeeplinkSource.UDL, + udlStatus = UdlStatus.FOUND, + gcdStatus = null, + isDeferred = deepLink?.isDeferred, + destination = destination, clickEvent = clickEventValues, conversion = null ) - println("🔗 DEEPLINK [KMM-UDL] ===== DeepLinkResult =====") - println("🔗 DEEPLINK [KMM-UDL] $deepLinkResult") - println("🔗 DEEPLINK [KMM-UDL] =====================================") - println("🔗 DEEPLINK [KMM-UDL] Calling listener.onDeepLinkingResult") options.listener.onDeepLinkingResult(deepLinkResult) } AFSDKDeepLinkResultStatus.AFSDKDeepLinkResultStatusNotFound -> { - println("🔗 DEEPLINK [KMM-UDL] Status: NOT_FOUND") - options.listener.onDeepLinkingResult(null) + options.listener.onDeepLinkNotFound(DeepLinkResult.notFound()) } AFSDKDeepLinkResultStatus.AFSDKDeepLinkResultStatusFailure -> { - println("🔗 DEEPLINK [KMM-UDL] Status: FAILURE - error: ${result.error}") options.listener.onDeepLinkingError(DeepLinkError(result.error)) } else -> { - println("🔗 DEEPLINK [KMM-UDL] Status: UNKNOWN") - options.listener.onDeepLinkingResult(null) + options.listener.onDeepLinkNotFound(DeepLinkResult.notFound()) } } } override fun onConversionDataFail(error: NSError) { - println("🔗 DEEPLINK [KMM-GCD] onConversionDataFail: ${error.localizedDescription}") + options.listener.onDeepLinkingError(DeepLinkError(error)) } override fun onConversionDataSuccess(conversionInfo: Map) { - println("🔗 DEEPLINK [KMM-GCD] onConversionDataSuccess called") - println("🔗 DEEPLINK [KMM-GCD] conversionInfo: $conversionInfo") - println("🔗 DEEPLINK [KMM-GCD] conversionInfo size: ${conversionInfo.size}") - println("🔗 DEEPLINK [KMM-GCD] conversionInfo keys: ${conversionInfo.keys}") - println("🔗 DEEPLINK [KMM-GCD] All conversionInfo entries:") - conversionInfo.forEach { (key, value) -> - println("🔗 DEEPLINK [KMM-GCD] $key = $value") - } - val appConversionResult = this.getAppAttributionResult(conversionInfo) - options.listener.onAppAttribution(appConversionResult.isOrganic, appConversionResult.extras) - - val gcdMediaSource = appConversionResult.extras["media_source"]?.toString() - val gcdCampaign = appConversionResult.extras["campaign"]?.toString() @Suppress("UNCHECKED_CAST") val extras = appConversionResult.extras as Map - println("🔗 DEEPLINK [KMM-GCD] extras: $extras") - println("🔗 DEEPLINK [KMM-GCD] gcdMediaSource: $gcdMediaSource") - println("🔗 DEEPLINK [KMM-GCD] gcdCampaign: $gcdCampaign") - val isFirstLaunch = extras.getBoolean("is_first_launch") ?: false - println("🔗 DEEPLINK [KMM-GCD] isFirstLaunch: $isFirstLaunch") + if (!isFirstLaunch) return - // Try to get deep_link_value from raw conversionInfo first, then from extras val deepLinkValue = (conversionInfo["deep_link_value"] ?: extras["deep_link_value"])?.toString() - println("🔗 DEEPLINK [KMM-GCD] deep_link_value from conversionInfo: ${conversionInfo["deep_link_value"]}") - println("🔗 DEEPLINK [KMM-GCD] deep_link_value from extras: ${extras["deep_link_value"]}") - println("🔗 DEEPLINK [KMM-GCD] Final deepLinkValue: $deepLinkValue") - val destination = deepLinkValue?.parseDestination() - println("🔗 DEEPLINK [KMM-GCD] destination: $destination") - val gcdStatus = if (appConversionResult.isOrganic) GcdAfStatus.ORGANIC else GcdAfStatus.NON_ORGANIC - val deepLinkResult = DeepLinkResult( - origin = extras.buildOrigin(DeeplinkSource.GCD, null, gcdStatus, isFirstLaunch), - campaign = extras.buildCampaign(), - advertisement = extras.buildAdvertisement(), - deepLink = extras.buildDeepLink(destination), - timestamps = extras.buildTimestamps(), - cost = extras.buildCost(), - device = extras.buildDevice(), - retargeting = extras.buildRetargeting(), - engagement = extras.buildEngagement(), - viewability = extras.buildViewability(), - network = extras.buildNetwork(), - customParameters = extras.buildCustomParameters(), - socialPreview = extras.buildSocialPreview(), - system = extras.buildSystem(), + val deepLinkResult = extras.buildDeepLinkResult( + source = DeeplinkSource.GCD, + udlStatus = null, + gcdStatus = gcdStatus, + isDeferred = isFirstLaunch, + destination = destination, clickEvent = null, conversion = conversionInfo ) - println("🔗 DEEPLINK [KMM-GCD] ===== DeepLinkResult =====") - println("🔗 DEEPLINK [KMM-GCD] $deepLinkResult") - println("🔗 DEEPLINK [KMM-GCD] =====================================") - println("🔗 DEEPLINK [KMM-GCD] Calling listener.onDeepLinkingResult") - options.listener.onDeepLinkingResult(deepLinkResult) + options.listener.onAttributionData(deepLinkResult) + } + + override fun onAppOpenAttributionFailure(error: NSError) { + options.listener.onDeepLinkingError(DeepLinkError(error)) } actual fun start( From d59efbf83984794ccc8e162e6db8b44af1cc9256 Mon Sep 17 00:00:00 2001 From: Tarek Sabry Date: Tue, 11 Nov 2025 17:03:22 +0200 Subject: [PATCH 3/4] Use default values --- .../kmm/appsflyer/model/DeepLinkResult.kt | 431 ++++++------------ 1 file changed, 139 insertions(+), 292 deletions(-) diff --git a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkResult.kt b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkResult.kt index eed614e..990c774 100644 --- a/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkResult.kt +++ b/appsflyer/src/commonMain/kotlin/com/metacto/kmm/appsflyer/model/DeepLinkResult.kt @@ -2,359 +2,206 @@ package com.metacto.kmm.appsflyer.model data class Origin( val source: DeeplinkSource, - val udlStatus: UdlStatus?, - val status: GcdAfStatus?, - val matchType: String?, - val isDeferred: Boolean? + val udlStatus: UdlStatus? = null, + val status: GcdAfStatus? = null, + val matchType: String? = null, + val isDeferred: Boolean? = null ) data class Campaign( - val mediaSource: String?, - val name: String?, - val id: String?, - val channel: String?, - val siteId: String?, - val subSiteId: String?, - val partner: String?, - val marketingPartner: String?, - val agency: String?, - val accountId: String?, - val keywords: String? + val mediaSource: String? = null, + val name: String? = null, + val id: String? = null, + val channel: String? = null, + val siteId: String? = null, + val subSiteId: String? = null, + val partner: String? = null, + val marketingPartner: String? = null, + val agency: String? = null, + val accountId: String? = null, + val keywords: String? = null ) data class Advertisement( - val adset: String?, - val adsetId: String?, - val adgroup: String?, - val adgroupId: String?, - val adgroupName: String?, - val ad: String?, - val adId: String?, - val adType: String?, - val adFormat: String?, - val adEventId: String? + val adset: String? = null, + val adsetId: String? = null, + val adgroup: String? = null, + val adgroupId: String? = null, + val adgroupName: String? = null, + val ad: String? = null, + val adId: String? = null, + val adType: String? = null, + val adFormat: String? = null, + val adEventId: String? = null ) data class DeepLink( - val destination: String?, - val value: String?, - val sub1: String?, - val sub2: String?, - val sub3: String?, - val sub4: String?, - val sub5: String?, - val sub6: String?, - val sub7: String?, - val sub8: String?, - val sub9: String?, - val sub10: String?, - val scheme: String?, - val forceDeeplink: Boolean?, - val redirect: String?, - val webRedirect: String?, - val androidUrl: String?, - val iosUrl: String? + val destination: String? = null, + val value: String? = null, + val sub1: String? = null, + val sub2: String? = null, + val sub3: String? = null, + val sub4: String? = null, + val sub5: String? = null, + val sub6: String? = null, + val sub7: String? = null, + val sub8: String? = null, + val sub9: String? = null, + val sub10: String? = null, + val scheme: String? = null, + val forceDeeplink: Boolean? = null, + val redirect: String? = null, + val webRedirect: String? = null, + val androidUrl: String? = null, + val iosUrl: String? = null ) data class Timestamps( - val clickTime: String?, - val installTime: String?, - val isFirstLaunch: Boolean?, - val clickLookback: String?, - val viewthroughLookback: String?, - val reengagementWindow: String? + val clickTime: String? = null, + val installTime: String? = null, + val isFirstLaunch: Boolean? = null, + val clickLookback: String? = null, + val viewthroughLookback: String? = null, + val reengagementWindow: String? = null ) data class Cost( - val original: String?, - val centsUsd: String?, - val currency: String?, - val value: String?, - val model: String?, - val perInstall: String? + val original: String? = null, + val centsUsd: String? = null, + val currency: String? = null, + val value: String? = null, + val model: String? = null, + val perInstall: String? = null ) data class Device( - val os: String?, - val osVersion: String?, - val model: String?, - val userAgent: String?, - val ipAddress: String?, - val mediaType: String?, - val android: AndroidIdentifiers, - val ios: IosIdentifiers + val os: String? = null, + val osVersion: String? = null, + val model: String? = null, + val userAgent: String? = null, + val ipAddress: String? = null, + val mediaType: String? = null, + val android: AndroidIdentifiers = AndroidIdentifiers(), + val ios: IosIdentifiers = IosIdentifiers() ) data class AndroidIdentifiers( - val advertisingId: String?, - val advertisingIdSha1: String?, - val advertisingIdMd5: String?, - val androidId: String?, - val androidIdSha1: String?, - val androidIdMd5: String?, - val imei: String?, - val imeiSha1: String?, - val imeiMd5: String?, - val oaid: String?, - val oaidSha1: String?, - val oaidMd5: String?, - val fireAdvertisingId: String?, - val emailSha1: String?, - val storeListing: String? + val advertisingId: String? = null, + val advertisingIdSha1: String? = null, + val advertisingIdMd5: String? = null, + val androidId: String? = null, + val androidIdSha1: String? = null, + val androidIdMd5: String? = null, + val imei: String? = null, + val imeiSha1: String? = null, + val imeiMd5: String? = null, + val oaid: String? = null, + val oaidSha1: String? = null, + val oaidMd5: String? = null, + val fireAdvertisingId: String? = null, + val emailSha1: String? = null, + val storeListing: String? = null ) data class IosIdentifiers( - val idfa: String?, - val idfaSha1: String?, - val idfaMd5: String?, - val idfv: String?, - val idfvSha1: String?, - val idfvMd5: String?, - val mac: String?, - val macSha1: String?, - val productPage: String? + val idfa: String? = null, + val idfaSha1: String? = null, + val idfaMd5: String? = null, + val idfv: String? = null, + val idfvSha1: String? = null, + val idfvMd5: String? = null, + val mac: String? = null, + val macSha1: String? = null, + val productPage: String? = null ) data class Retargeting( - val isRetargeting: Boolean?, - val conversionType: String? + val isRetargeting: Boolean? = null, + val conversionType: String? = null ) data class Engagement( - val isIncentivized: Boolean?, - val clickId: String?, - val referrer: String? + val isIncentivized: Boolean? = null, + val clickId: String? = null, + val referrer: String? = null ) data class Viewability( - val videoTotalLength: String?, - val videoPlayedLength: String?, - val playablePlayedLength: String?, - val adTimeViewed: String?, - val adDisplayedPercent: String?, - val audioTotalLength: String?, - val audioPlayedLength: String? + val videoTotalLength: String? = null, + val videoPlayedLength: String? = null, + val playablePlayedLength: String? = null, + val adTimeViewed: String? = null, + val adDisplayedPercent: String? = null, + val audioTotalLength: String? = null, + val audioPlayedLength: String? = null ) data class Network( - val meta: MetaParameters?, - val google: GoogleParameters?, - val campaignType: String? + val meta: MetaParameters? = null, + val google: GoogleParameters? = null, + val campaignType: String? = null ) data class MetaParameters( - val isFacebook: Boolean?, - val isPaid: Boolean?, - val isInstagram: Boolean? + val isFacebook: Boolean? = null, + val isPaid: Boolean? = null, + val isInstagram: Boolean? = null ) data class GoogleParameters( - val accountId: String?, - val clickId: String?, - val network: String?, - val clickUrl: String?, - val latitude: String?, - val videoId: String? + val accountId: String? = null, + val clickId: String? = null, + val network: String? = null, + val clickUrl: String? = null, + val latitude: String? = null, + val videoId: String? = null ) data class CustomParameters( - val sub1: String?, - val sub2: String?, - val sub3: String?, - val sub4: String?, - val sub5: String? + val sub1: String? = null, + val sub2: String? = null, + val sub3: String? = null, + val sub4: String? = null, + val sub5: String? = null ) data class SocialPreview( - val title: String?, - val description: String?, - val image: String? + val title: String? = null, + val description: String? = null, + val image: String? = null ) data class System( - val referralCode: String?, - val parameterForwarding: Boolean?, - val baseParametersForward: Boolean? + val referralCode: String? = null, + val parameterForwarding: Boolean? = null, + val baseParametersForward: Boolean? = null ) data class DeepLinkResult( val origin: Origin, - val campaign: Campaign, - val advertisement: Advertisement, - val deepLink: DeepLink, - val timestamps: Timestamps, - val cost: Cost, - val device: Device, - val retargeting: Retargeting, - val engagement: Engagement, - val viewability: Viewability, - val network: Network, - val customParameters: CustomParameters, - val socialPreview: SocialPreview, - val system: System, - val clickEvent: Map?, - val conversion: Map? + val campaign: Campaign = Campaign(), + val advertisement: Advertisement = Advertisement(), + val deepLink: DeepLink = DeepLink(), + val timestamps: Timestamps = Timestamps(), + val cost: Cost = Cost(), + val device: Device = Device(), + val retargeting: Retargeting = Retargeting(), + val engagement: Engagement = Engagement(), + val viewability: Viewability = Viewability(), + val network: Network = Network(), + val customParameters: CustomParameters = CustomParameters(), + val socialPreview: SocialPreview = SocialPreview(), + val system: System = System(), + val clickEvent: Map? = null, + val conversion: Map? = null ) { companion object { fun notFound(): DeepLinkResult { return DeepLinkResult( origin = Origin( source = DeeplinkSource.UDL, - udlStatus = UdlStatus.NOT_FOUND, - status = null, - matchType = null, - isDeferred = null - ), - campaign = Campaign( - mediaSource = null, - name = null, - id = null, - channel = null, - siteId = null, - subSiteId = null, - partner = null, - marketingPartner = null, - agency = null, - accountId = null, - keywords = null - ), - advertisement = Advertisement( - adset = null, - adsetId = null, - adgroup = null, - adgroupId = null, - adgroupName = null, - ad = null, - adId = null, - adType = null, - adFormat = null, - adEventId = null - ), - deepLink = DeepLink( - destination = null, - value = null, - sub1 = null, - sub2 = null, - sub3 = null, - sub4 = null, - sub5 = null, - sub6 = null, - sub7 = null, - sub8 = null, - sub9 = null, - sub10 = null, - scheme = null, - forceDeeplink = null, - redirect = null, - webRedirect = null, - androidUrl = null, - iosUrl = null - ), - timestamps = Timestamps( - clickTime = null, - installTime = null, - isFirstLaunch = null, - clickLookback = null, - viewthroughLookback = null, - reengagementWindow = null - ), - cost = Cost( - original = null, - centsUsd = null, - currency = null, - value = null, - model = null, - perInstall = null - ), - device = Device( - os = null, - osVersion = null, - model = null, - userAgent = null, - ipAddress = null, - mediaType = null, - android = AndroidIdentifiers( - advertisingId = null, - advertisingIdSha1 = null, - advertisingIdMd5 = null, - androidId = null, - androidIdSha1 = null, - androidIdMd5 = null, - imei = null, - imeiSha1 = null, - imeiMd5 = null, - oaid = null, - oaidSha1 = null, - oaidMd5 = null, - fireAdvertisingId = null, - emailSha1 = null, - storeListing = null - ), - ios = IosIdentifiers( - idfa = null, - idfaSha1 = null, - idfaMd5 = null, - idfv = null, - idfvSha1 = null, - idfvMd5 = null, - mac = null, - macSha1 = null, - productPage = null - ) - ), - retargeting = Retargeting( - isRetargeting = null, - conversionType = null - ), - engagement = Engagement( - isIncentivized = null, - clickId = null, - referrer = null - ), - viewability = Viewability( - videoTotalLength = null, - videoPlayedLength = null, - playablePlayedLength = null, - adTimeViewed = null, - adDisplayedPercent = null, - audioTotalLength = null, - audioPlayedLength = null - ), - network = Network( - meta = MetaParameters( - isFacebook = null, - isPaid = null, - isInstagram = null - ), - google = GoogleParameters( - accountId = null, - clickId = null, - network = null, - clickUrl = null, - latitude = null, - videoId = null - ), - campaignType = null - ), - customParameters = CustomParameters( - sub1 = null, - sub2 = null, - sub3 = null, - sub4 = null, - sub5 = null - ), - socialPreview = SocialPreview( - title = null, - description = null, - image = null - ), - system = System( - referralCode = null, - parameterForwarding = null, - baseParametersForward = null - ), - clickEvent = null, - conversion = null + udlStatus = UdlStatus.NOT_FOUND + ) ) } } From 85e7a5d166d1c3ae16e2e4c1636e9014afbfd539 Mon Sep 17 00:00:00 2001 From: Tarek Sabry Date: Tue, 11 Nov 2025 18:27:56 +0200 Subject: [PATCH 4/4] Deploy release --- .github/workflows/deployRelease.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deployRelease.yml b/.github/workflows/deployRelease.yml index 62fb4f5..57f47e8 100644 --- a/.github/workflows/deployRelease.yml +++ b/.github/workflows/deployRelease.yml @@ -4,6 +4,7 @@ on: push: branches: - 'main' + - 'revamp/parameter-pasrsing' # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: build: @@ -57,4 +58,4 @@ jobs: run: cat ./local.properties - name: Publish to GitHub Packages - run: ./gradlew publishAllPublicationsToGithubRepository \ No newline at end of file + run: ./gradlew publishAllPublicationsToGithubRepository