From d9c08d51a7d2c1a9959d3e7f126137f3c61cf247 Mon Sep 17 00:00:00 2001 From: Domen Lanisnik Date: Mon, 18 Aug 2025 16:18:22 +0200 Subject: [PATCH 1/2] Use Moshi for message requests and responses --- .../messaging/PirDashboardWebConstants.kt | 8 -- ...dressToCurrentUserProfileMessageHandler.kt | 30 +++--- ...dNameToCurrentUserProfileMessageHandler.kt | 32 +++---- ...rWebGetCurrentUserProfileMessageHandler.kt | 56 ++++------- .../PirWebGetDataBrokersMessageHandler.kt | 40 +++----- .../handlers/PirWebHandshakeMessageHandler.kt | 22 ++--- .../PirWebInitialScanStatusMessageHandler.kt | 33 +++---- .../handlers/PirWebJsMessageHandler.kt | 83 +++++++++++----- .../PirWebSaveProfileMessageHandler.kt | 13 +-- ...PirWebSetBirthYearForCurrentUserProfile.kt | 19 ++-- .../PirWebStartScanAndOptOutMessageHandler.kt | 8 +- .../messaging/model/PirWebMessageRequest.kt | 38 ++++++++ .../messaging/model/PirWebMessageResponse.kt | 94 +++++++++++++++++++ 13 files changed, 293 insertions(+), 183 deletions(-) create mode 100644 pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/model/PirWebMessageRequest.kt create mode 100644 pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/model/PirWebMessageResponse.kt diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/PirDashboardWebConstants.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/PirDashboardWebConstants.kt index b729387eeba9..fdfcc20a8bd5 100644 --- a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/PirDashboardWebConstants.kt +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/PirDashboardWebConstants.kt @@ -27,12 +27,4 @@ object PirDashboardWebConstants { internal const val MESSAGE_CALLBACK = "messageCallback" internal const val SECRET = "duckduckgo-android-messaging-secret" internal const val ALLOWED_DOMAIN = "duckduckgo.com" - - internal const val PARAM_SUCCESS = "success" - internal const val PARAM_VERSION = "version" - internal const val PARAM_FIRST_NAME = "first" - internal const val PARAM_MIDDLE_NAME = "middle" - internal const val PARAM_LAST_NAME = "last" - internal const val PARAM_CITY = "city" - internal const val PARAM_STATE = "state" } diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddAddressToCurrentUserProfileMessageHandler.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddAddressToCurrentUserProfileMessageHandler.kt index 18baa9846f72..e504b8268d25 100644 --- a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddAddressToCurrentUserProfileMessageHandler.kt +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddAddressToCurrentUserProfileMessageHandler.kt @@ -21,6 +21,8 @@ import com.duckduckgo.js.messaging.api.JsMessage import com.duckduckgo.js.messaging.api.JsMessageCallback import com.duckduckgo.js.messaging.api.JsMessaging import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageRequest +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder import com.duckduckgo.pir.impl.models.Address import com.squareup.anvil.annotations.ContributesMultibinding @@ -37,8 +39,8 @@ import logcat.logcat class PirWebAddAddressToCurrentUserProfileMessageHandler @Inject constructor( private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder, ) : PirWebJsMessageHandler() { - override val messageNames: List = - listOf(PirDashboardWebMessages.ADD_ADDRESS_TO_CURRENT_USER_PROFILE) + + override val message = PirDashboardWebMessages.ADD_ADDRESS_TO_CURRENT_USER_PROFILE override fun process( jsMessage: JsMessage, @@ -47,39 +49,39 @@ class PirWebAddAddressToCurrentUserProfileMessageHandler @Inject constructor( ) { logcat { "PIR-WEB: PirWebAddAddressToCurrentUserProfileMessageHandler: process $jsMessage" } - val city = jsMessage.params.getStringParam("city") - val state = jsMessage.params.getStringParam("state") + val request = + jsMessage.toRequestMessage(PirWebMessageRequest.AddAddressToCurrentUserProfileRequest::class) // attempting to add an empty address should return success=false - if (city == null || state == null) { + if (request == null || request.city.isEmpty() || request.state.isEmpty()) { logcat { "PIR-WEB: PirWebAddAddressToCurrentUserProfileMessageHandler: missing city and/or state" } - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage = jsMessage, - success = false, + response = PirWebMessageResponse.DefaultResponse.ERROR, ) return } // attempting to add a duplicate address should return success=false - if (pirWebOnboardingStateHolder.addresses.any { it.city == city && it.state == state }) { + if (pirWebOnboardingStateHolder.addresses.any { it.city == request.city && it.state == request.state }) { logcat { "PIR-WEB: PirWebAddAddressToCurrentUserProfileMessageHandler: address already exists" } - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage = jsMessage, - success = false, + response = PirWebMessageResponse.DefaultResponse.ERROR, ) return } pirWebOnboardingStateHolder.addresses.add( Address( - city = city, - state = state, + city = request.city, + state = request.state, ), ) - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage = jsMessage, - success = true, + response = PirWebMessageResponse.DefaultResponse.SUCCESS, ) } } diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddNameToCurrentUserProfileMessageHandler.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddNameToCurrentUserProfileMessageHandler.kt index c516efd1d96b..7f861856fc7e 100644 --- a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddNameToCurrentUserProfileMessageHandler.kt +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddNameToCurrentUserProfileMessageHandler.kt @@ -20,8 +20,9 @@ import com.duckduckgo.di.scopes.ActivityScope import com.duckduckgo.js.messaging.api.JsMessage import com.duckduckgo.js.messaging.api.JsMessageCallback import com.duckduckgo.js.messaging.api.JsMessaging -import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebConstants import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageRequest +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder import com.squareup.anvil.annotations.ContributesMultibinding import javax.inject.Inject @@ -38,8 +39,7 @@ class PirWebAddNameToCurrentUserProfileMessageHandler @Inject constructor( private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder, ) : PirWebJsMessageHandler() { - override val messageNames: List = - listOf(PirDashboardWebMessages.ADD_NAME_TO_CURRENT_USER_PROFILE) + override val message = PirDashboardWebMessages.ADD_NAME_TO_CURRENT_USER_PROFILE override fun process( jsMessage: JsMessage, @@ -48,26 +48,24 @@ class PirWebAddNameToCurrentUserProfileMessageHandler @Inject constructor( ) { logcat { "PIR-WEB: PirWebAddNameToCurrentUserProfileMessageHandler: process $jsMessage" } - val firstName = jsMessage.params.getStringParam(PirDashboardWebConstants.PARAM_FIRST_NAME) - val middleName = jsMessage.params.getStringParam(PirDashboardWebConstants.PARAM_MIDDLE_NAME) - val lastName = jsMessage.params.getStringParam(PirDashboardWebConstants.PARAM_LAST_NAME) + val request = jsMessage.toRequestMessage(PirWebMessageRequest.AddNameToCurrentUserProfileRequest::class) // attempting to add an empty name should return success=false - if (firstName == null || lastName == null) { + if (request == null || request.first.isEmpty() || request.last.isEmpty()) { logcat { "PIR-WEB: PirWebAddNameToCurrentUserProfileMessageHandler: missing first and/or last names" } - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage = jsMessage, - success = false, + response = PirWebMessageResponse.DefaultResponse.ERROR, ) return } // attempting to add a duplicate name should return success=false - if (pirWebOnboardingStateHolder.names.any { it.firstName == firstName && it.middleName == middleName && it.lastName == lastName }) { + if (pirWebOnboardingStateHolder.names.any { it.firstName == request.first && it.middleName == request.middle && it.lastName == request.last }) { logcat { "PIR-WEB: PirWebAddNameToCurrentUserProfileMessageHandler: duplicate name detected" } - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage = jsMessage, - success = false, + response = PirWebMessageResponse.DefaultResponse.ERROR, ) return } @@ -75,15 +73,15 @@ class PirWebAddNameToCurrentUserProfileMessageHandler @Inject constructor( // Add the name to the current user profile pirWebOnboardingStateHolder.names.add( PirWebOnboardingStateHolder.Name( - firstName = firstName, - middleName = middleName, - lastName = lastName, + firstName = request.first, + middleName = request.middle, + lastName = request.last, ), ) - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage = jsMessage, - success = true, + response = PirWebMessageResponse.DefaultResponse.SUCCESS, ) } } diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebGetCurrentUserProfileMessageHandler.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebGetCurrentUserProfileMessageHandler.kt index 710157821aa9..eac04f9b5294 100644 --- a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebGetCurrentUserProfileMessageHandler.kt +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebGetCurrentUserProfileMessageHandler.kt @@ -22,8 +22,8 @@ import com.duckduckgo.di.scopes.ActivityScope import com.duckduckgo.js.messaging.api.JsMessage import com.duckduckgo.js.messaging.api.JsMessageCallback import com.duckduckgo.js.messaging.api.JsMessaging -import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebConstants import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder.Name import com.duckduckgo.pir.impl.store.PirRepository import com.squareup.anvil.annotations.ContributesMultibinding @@ -31,8 +31,6 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import logcat.logcat -import org.json.JSONArray -import org.json.JSONObject /** * Handles the initial getCurrentUserProfile message from Web which is used to retrieve the current user profile @@ -46,10 +44,9 @@ class PirWebGetCurrentUserProfileMessageHandler @Inject constructor( private val repository: PirRepository, private val dispatcherProvider: DispatcherProvider, @AppCoroutineScope private val appCoroutineScope: CoroutineScope, -) : - PirWebJsMessageHandler() { +) : PirWebJsMessageHandler() { - override val messageNames: List = listOf(PirDashboardWebMessages.GET_CURRENT_USER_PROFILE) + override val message = PirDashboardWebMessages.GET_CURRENT_USER_PROFILE override fun process( jsMessage: JsMessage, @@ -63,9 +60,9 @@ class PirWebGetCurrentUserProfileMessageHandler @Inject constructor( if (profiles.isEmpty()) { logcat { "PIR-WEB: GetCurrentUserProfileMessageHandler: no user profiles found" } - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage = jsMessage, - success = true, + response = PirWebMessageResponse.DefaultResponse.SUCCESS, ) return@launch } @@ -74,40 +71,25 @@ class PirWebGetCurrentUserProfileMessageHandler @Inject constructor( val addresses = profiles.map { it.addresses }.flatten() val birthYear = profiles.firstOrNull()?.birthYear ?: 0 - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage = jsMessage, - success = true, - customParams = mapOf( - PARAM_ADDRESSES to JSONArray().apply { - addresses.forEach { address -> - put( - JSONObject().apply { - put(PirDashboardWebConstants.PARAM_CITY, address.city) - put(PirDashboardWebConstants.PARAM_STATE, address.state) - }, - ) - } + response = PirWebMessageResponse.GetCurrentUserProfileResponse( + names = names.map { + PirWebMessageResponse.GetCurrentUserProfileResponse.Name( + first = it.firstName, + middle = it.middleName ?: "", + last = it.lastName, + ) }, - PARAM_BIRTH_YEAR to birthYear, - PARAM_NAMES to JSONArray().apply { - names.forEach { name -> - put( - JSONObject().apply { - put(PirDashboardWebConstants.PARAM_FIRST_NAME, name.firstName) - put(PirDashboardWebConstants.PARAM_MIDDLE_NAME, name.middleName ?: "") - put(PirDashboardWebConstants.PARAM_LAST_NAME, name.lastName) - }, - ) - } + addresses = addresses.map { + PirWebMessageResponse.GetCurrentUserProfileResponse.Address( + city = it.city, + state = it.state, + ) }, + birthYear = birthYear, ), ) } } - - companion object { - private const val PARAM_ADDRESSES = "addresses" - private const val PARAM_BIRTH_YEAR = "birthYear" - private const val PARAM_NAMES = "names" - } } diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebGetDataBrokersMessageHandler.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebGetDataBrokersMessageHandler.kt index 986178b72eab..45846fbb7464 100644 --- a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebGetDataBrokersMessageHandler.kt +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebGetDataBrokersMessageHandler.kt @@ -23,15 +23,13 @@ import com.duckduckgo.js.messaging.api.JsMessage import com.duckduckgo.js.messaging.api.JsMessageCallback import com.duckduckgo.js.messaging.api.JsMessaging import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse import com.duckduckgo.pir.impl.store.PirRepository -import com.duckduckgo.pir.impl.store.db.Broker import com.squareup.anvil.annotations.ContributesMultibinding import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import logcat.logcat -import org.json.JSONArray -import org.json.JSONObject /** * Handles the getDataBrokers message from Web which is used @@ -46,8 +44,8 @@ class PirWebGetDataBrokersMessageHandler @Inject constructor( private val dispatcherProvider: DispatcherProvider, @AppCoroutineScope private val appCoroutineScope: CoroutineScope, ) : PirWebJsMessageHandler() { - override val messageNames: List = - listOf(PirDashboardWebMessages.GET_DATA_BROKERS) + + override val message = PirDashboardWebMessages.GET_DATA_BROKERS override fun process( jsMessage: JsMessage, @@ -59,32 +57,18 @@ class PirWebGetDataBrokersMessageHandler @Inject constructor( appCoroutineScope.launch(dispatcherProvider.io()) { val brokers = repository.getAllActiveBrokerObjects() - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage, - success = true, - customParams = mapOf( - PARAM_DATA_BROKERS to brokers.toResponseBrokers(), + response = PirWebMessageResponse.GetDataBrokersResponse( + dataBrokers = brokers.map { + PirWebMessageResponse.GetDataBrokersResponse.DataBroker( + url = it.url, + name = it.name, + parentURL = it.parent, + ) + }, ), ) } } - - private fun List.toResponseBrokers(): JSONArray { - // TODO verify parentURL and add optOutURL as per documentation - return JSONArray().apply { - forEach { broker -> - put( - JSONObject().apply { - put("url", broker.url) - put("name", broker.name) - put("parentURL", broker.parent ?: JSONObject.NULL) - }, - ) - } - } - } - - companion object { - private const val PARAM_DATA_BROKERS = "dataBrokers" - } } diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebHandshakeMessageHandler.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebHandshakeMessageHandler.kt index 305bf135827a..95df51d23f52 100644 --- a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebHandshakeMessageHandler.kt +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebHandshakeMessageHandler.kt @@ -21,10 +21,10 @@ import com.duckduckgo.js.messaging.api.JsMessage import com.duckduckgo.js.messaging.api.JsMessageCallback import com.duckduckgo.js.messaging.api.JsMessaging import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse import com.squareup.anvil.annotations.ContributesMultibinding import javax.inject.Inject import logcat.logcat -import org.json.JSONObject /** * Handles the initial handshake message from Web which is used to establish communication. @@ -35,7 +35,7 @@ import org.json.JSONObject ) class PirWebHandshakeMessageHandler @Inject constructor() : PirWebJsMessageHandler() { - override val messageNames: List = listOf(PirDashboardWebMessages.HANDSHAKE) + override val message = PirDashboardWebMessages.HANDSHAKE override fun process( jsMessage: JsMessage, @@ -44,20 +44,14 @@ class PirWebHandshakeMessageHandler @Inject constructor() : PirWebJsMessageHandl ) { logcat { "PIR-WEB: PirWebHandshakeMessageHandler: process $jsMessage" } - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage = jsMessage, - success = true, - customParams = mapOf( - PARAM_USER_DATA to JSONObject().apply { - // TODO Check access token and subscription - put(PARAM_IS_AUTHENTICATED_USER, true) - }, + response = PirWebMessageResponse.HandshakeResponse( + success = true, + userData = PirWebMessageResponse.HandshakeResponse.UserData( + isAuthenticatedUser = true, + ), ), ) } - - companion object { - private const val PARAM_USER_DATA = "userData" - private const val PARAM_IS_AUTHENTICATED_USER = "isAuthenticatedUser" - } } diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebInitialScanStatusMessageHandler.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebInitialScanStatusMessageHandler.kt index fbf44ba6da92..18ba21d243ee 100644 --- a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebInitialScanStatusMessageHandler.kt +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebInitialScanStatusMessageHandler.kt @@ -23,6 +23,7 @@ import com.duckduckgo.js.messaging.api.JsMessage import com.duckduckgo.js.messaging.api.JsMessageCallback import com.duckduckgo.js.messaging.api.JsMessaging import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse import com.duckduckgo.pir.impl.store.PirRepository import com.duckduckgo.pir.impl.store.db.EventType.MANUAL_SCAN_COMPLETED import com.duckduckgo.pir.impl.store.db.EventType.MANUAL_SCAN_STARTED @@ -32,8 +33,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import logcat.logcat -import org.json.JSONArray -import org.json.JSONObject /** * Handles the initial scan status message from Web which is used to retrieve the status of the initial scan. @@ -46,10 +45,9 @@ class PirWebInitialScanStatusMessageHandler @Inject constructor( private val repository: PirRepository, private val dispatcherProvider: DispatcherProvider, @AppCoroutineScope private val appCoroutineScope: CoroutineScope, -) : - PirWebJsMessageHandler() { +) : PirWebJsMessageHandler() { - override val messageNames: List = listOf(PirDashboardWebMessages.INITIAL_SCAN_STATUS) + override val message = PirDashboardWebMessages.INITIAL_SCAN_STATUS override fun process( jsMessage: JsMessage, @@ -62,26 +60,17 @@ class PirWebInitialScanStatusMessageHandler @Inject constructor( val eventLogs = repository.getAllEventLogsFlow().firstOrNull().orEmpty() // TODO get actual scan progress results from the repository - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage = jsMessage, - success = true, - customParams = mapOf( - PARAM_RESULTS_FOUND to JSONArray(), - PARAM_SCAN_PROGRESS to JSONObject().apply { - put(PARAM_CURRENT_SCAN, eventLogs.count { it.eventType == MANUAL_SCAN_COMPLETED }) - put(PARAM_TOTAL_SCANS, eventLogs.count { it.eventType == MANUAL_SCAN_STARTED }) - put(PARAM_SCANNED_BROKERS, JSONArray()) - }, + response = PirWebMessageResponse.InitialScanResponse( + resultsFound = listOf(), + scanProgress = PirWebMessageResponse.InitialScanResponse.ScanProgress( + currentScan = eventLogs.count { it.eventType == MANUAL_SCAN_COMPLETED }, + totalScans = eventLogs.count { it.eventType == MANUAL_SCAN_STARTED }, + scannedBrokers = listOf(), + ), ), ) } } - - companion object { - private const val PARAM_RESULTS_FOUND = "resultsFound" - private const val PARAM_SCAN_PROGRESS = "scanProgress" - private const val PARAM_CURRENT_SCAN = "currentScan" - private const val PARAM_TOTAL_SCANS = "totalScans" - private const val PARAM_SCANNED_BROKERS = "scannedBrokers" - } } diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebJsMessageHandler.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebJsMessageHandler.kt index c9a111ba9e77..68d47fbcc463 100644 --- a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebJsMessageHandler.kt +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebJsMessageHandler.kt @@ -20,8 +20,18 @@ import com.duckduckgo.js.messaging.api.JsCallbackData import com.duckduckgo.js.messaging.api.JsMessage import com.duckduckgo.js.messaging.api.JsMessageHandler import com.duckduckgo.js.messaging.api.JsMessaging +import com.duckduckgo.pir.impl.brokers.JSONObjectAdapter import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebConstants import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageRequest +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import kotlin.reflect.KClass +import logcat.LogPriority.ERROR +import logcat.logcat import org.json.JSONObject /** @@ -41,33 +51,44 @@ abstract class PirWebJsMessageHandler : JsMessageHandler { override val allowedDomains: List = emptyList() override val featureName: String = PirDashboardWebConstants.SCRIPT_FEATURE_NAME override val methods: List - get() = messageNames.map { it.messageName } - abstract val messageNames: List + get() = listOf(message.messageName) + abstract val message: PirDashboardWebMessages + + private val responseAdapter by lazy { + Moshi.Builder().add( + PolymorphicJsonAdapterFactory.of(PirWebMessageResponse::class.java, JSON_TYPE_PARAM) + .withSubtype(PirWebMessageResponse.DefaultResponse::class.java, "default") + .withSubtype(PirWebMessageResponse.HandshakeResponse::class.java, "handshake") + .withSubtype(PirWebMessageResponse.InitialScanResponse::class.java, "initialScan") + .withSubtype(PirWebMessageResponse.GetDataBrokersResponse::class.java, "getDataBrokers") + .withSubtype(PirWebMessageResponse.GetCurrentUserProfileResponse::class.java, "getCurrentUserProfile"), + ).add(KotlinJsonAdapterFactory()) + .build().adapter(PirWebMessageResponse::class.java) + } + + private val requestAdapter by lazy { + Moshi.Builder().add(KotlinJsonAdapterFactory()).add(JSONObjectAdapter()).build() + } /** - * Constructs and sends a response to the [jsMessage] with PIR specific parameters already included. + * Uses the [PirWebMessageResponse] to construct and send a response to the [jsMessage]. * - * @param success Used to set the value of the `success` parameter in the response. - * @param customParams Additional parameters to include in the response on top of the standard PIR parameters like API version. + * @param response The body of the response to send back, must be a subclass of [PirWebMessageResponse]. */ - protected fun JsMessaging.sendPirResponse( + protected fun JsMessaging.sendResponse( jsMessage: JsMessage, - success: Boolean, - customParams: Map = emptyMap(), + response: PirWebMessageResponse, ) { + val responseParams = kotlin.runCatching { + response.toMessageParams() + }.getOrElse { + logcat(ERROR) { "PIR-WEB: Failed to serialize response: ${it.message}" } + JSONObject() // Fallback to empty JSON object if serialization fails + } + onResponse( JsCallbackData( - // TODO This could be improved by serializing objects with Moshi - params = JSONObject().apply { - put(PirDashboardWebConstants.PARAM_SUCCESS, success) - put( - PirDashboardWebConstants.PARAM_VERSION, - PirDashboardWebConstants.SCRIPT_API_VERSION, - ) - customParams.forEach { (name, value) -> - put(name, value) - } - }, + params = responseParams, featureName = jsMessage.featureName, method = jsMessage.method, id = jsMessage.id ?: "", @@ -75,11 +96,25 @@ abstract class PirWebJsMessageHandler : JsMessageHandler { ) } - protected fun JSONObject.getStringParam(param: String): String? { - return if (has(param)) { - getString(param).trim().takeIf { it.isNotEmpty() } - } else { - null + private fun PirWebMessageResponse.toMessageParams(): JSONObject { + return JSONObject(responseAdapter.toJson(this)).apply { + // remove the param that Moshi adds as it's not needed in the response + remove(JSON_TYPE_PARAM) } } + + /** + * Convenience function to convert the JSON params of [JsMessage] to a specific [PirWebMessageRequest] model to avoid manually parsing the JSON. + */ + protected fun JsMessage.toRequestMessage(requestClass: KClass): R? = kotlin.runCatching { + val jsonAdapter: JsonAdapter = requestAdapter.adapter(requestClass.java) + return jsonAdapter.fromJson(this.params.toString()) + }.getOrElse { + logcat(ERROR) { "PIR-WEB: Failed to deserialize request message: ${it.message}" } + null + } + + companion object { + private const val JSON_TYPE_PARAM = "type" + } } diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebSaveProfileMessageHandler.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebSaveProfileMessageHandler.kt index 10ca09e713a6..1c911e6f947a 100644 --- a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebSaveProfileMessageHandler.kt +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebSaveProfileMessageHandler.kt @@ -25,6 +25,7 @@ import com.duckduckgo.js.messaging.api.JsMessage import com.duckduckgo.js.messaging.api.JsMessageCallback import com.duckduckgo.js.messaging.api.JsMessaging import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder import com.duckduckgo.pir.impl.scan.PirForegroundScanService import com.duckduckgo.pir.impl.scan.PirScanScheduler @@ -53,8 +54,8 @@ class PirWebSaveProfileMessageHandler @Inject constructor( private val scanScheduler: PirScanScheduler, @AppCoroutineScope private val appCoroutineScope: CoroutineScope, ) : PirWebJsMessageHandler() { - override val messageNames: List = - listOf(PirDashboardWebMessages.SAVE_PROFILE) + + override val message = PirDashboardWebMessages.SAVE_PROFILE override fun process( jsMessage: JsMessage, @@ -69,9 +70,9 @@ class PirWebSaveProfileMessageHandler @Inject constructor( (pirWebOnboardingStateHolder.birthYear ?: 0) == 0 ) { logcat { "PIR-WEB: PirWebSaveProfileMessageHandler: incomplete profile information" } - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage = jsMessage, - success = false, + response = PirWebMessageResponse.DefaultResponse.ERROR, ) return } @@ -81,9 +82,9 @@ class PirWebSaveProfileMessageHandler @Inject constructor( repository.saveUserProfiles(profiles) // TODO check if all profiles were saved successfully - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage, - success = true, + response = PirWebMessageResponse.DefaultResponse.SUCCESS, ) context.startForegroundService(Intent(context, PirForegroundScanService::class.java)) diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebSetBirthYearForCurrentUserProfile.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebSetBirthYearForCurrentUserProfile.kt index f5f9f4c3d521..ff6959a4eebc 100644 --- a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebSetBirthYearForCurrentUserProfile.kt +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebSetBirthYearForCurrentUserProfile.kt @@ -21,6 +21,8 @@ import com.duckduckgo.js.messaging.api.JsMessage import com.duckduckgo.js.messaging.api.JsMessageCallback import com.duckduckgo.js.messaging.api.JsMessaging import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageRequest +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder import com.squareup.anvil.annotations.ContributesMultibinding import javax.inject.Inject @@ -37,8 +39,7 @@ class PirWebSetBirthYearForCurrentUserProfile @Inject constructor( private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder, ) : PirWebJsMessageHandler() { - override val messageNames: List = - listOf(PirDashboardWebMessages.SET_BIRTH_YEAR_FOR_CURRENT_USER_PROFILE) + override val message = PirDashboardWebMessages.SET_BIRTH_YEAR_FOR_CURRENT_USER_PROFILE override fun process( jsMessage: JsMessage, @@ -47,16 +48,16 @@ class PirWebSetBirthYearForCurrentUserProfile @Inject constructor( ) { logcat { "PIR-WEB: PirWebSetBirthYearForCurrentUserProfile: process $jsMessage" } + val birthYear = jsMessage.toRequestMessage( + PirWebMessageRequest.SetBirthYearForCurrentUserProfileRequest::class, + )?.year ?: 0 + // store the new birth year in the current user profile - pirWebOnboardingStateHolder.birthYear = jsMessage.params.getInt(PARAM_YEAR) + pirWebOnboardingStateHolder.birthYear = birthYear - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage = jsMessage, - success = true, + response = PirWebMessageResponse.DefaultResponse.SUCCESS, ) } - - companion object { - private const val PARAM_YEAR = "year" - } } diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebStartScanAndOptOutMessageHandler.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebStartScanAndOptOutMessageHandler.kt index d83095e60961..07a011f1393f 100644 --- a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebStartScanAndOptOutMessageHandler.kt +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebStartScanAndOptOutMessageHandler.kt @@ -21,6 +21,7 @@ import com.duckduckgo.js.messaging.api.JsMessage import com.duckduckgo.js.messaging.api.JsMessageCallback import com.duckduckgo.js.messaging.api.JsMessaging import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages +import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse import com.squareup.anvil.annotations.ContributesMultibinding import javax.inject.Inject import logcat.logcat @@ -34,8 +35,7 @@ import logcat.logcat ) class PirWebStartScanAndOptOutMessageHandler @Inject constructor() : PirWebJsMessageHandler() { - override val messageNames: List = - listOf(PirDashboardWebMessages.START_SCAN_AND_OPT_OUT) + override val message = PirDashboardWebMessages.START_SCAN_AND_OPT_OUT override fun process( jsMessage: JsMessage, @@ -46,9 +46,9 @@ class PirWebStartScanAndOptOutMessageHandler @Inject constructor() : PirWebJsMes // no-op, we rely on saveProfile to start the initial scans // we still need to respond to the message otherwise the web will not continue with the flow - jsMessaging.sendPirResponse( + jsMessaging.sendResponse( jsMessage = jsMessage, - success = true, + response = PirWebMessageResponse.DefaultResponse.SUCCESS, ) } } diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/model/PirWebMessageRequest.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/model/PirWebMessageRequest.kt new file mode 100644 index 000000000000..6255194a7788 --- /dev/null +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/model/PirWebMessageRequest.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.pir.impl.dashboard.messaging.model + +/** + * Represents the request body sent by the JS layer in PIR Web UI. + */ +sealed interface PirWebMessageRequest { + + data class AddNameToCurrentUserProfileRequest( + val first: String, + val middle: String? = null, + val last: String, + ) : PirWebMessageRequest + + data class AddAddressToCurrentUserProfileRequest( + val city: String, + val state: String, + ) : PirWebMessageRequest + + data class SetBirthYearForCurrentUserProfileRequest( + val year: Int, + ) : PirWebMessageRequest +} diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/model/PirWebMessageResponse.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/model/PirWebMessageResponse.kt new file mode 100644 index 000000000000..08c6565ca2d7 --- /dev/null +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/model/PirWebMessageResponse.kt @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.pir.impl.dashboard.messaging.model + +import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebConstants + +/** + * Represents the response body sent by the client back to the JS layer in Pir Web UI. + */ +sealed interface PirWebMessageResponse { + + data class DefaultResponse( + val success: Boolean, + val version: Int = PirDashboardWebConstants.SCRIPT_API_VERSION, + ) : PirWebMessageResponse { + companion object { + val SUCCESS = DefaultResponse(success = true) + val ERROR = DefaultResponse(success = false) + } + } + + data class HandshakeResponse( + val success: Boolean, + val userData: UserData, + val version: Int = PirDashboardWebConstants.SCRIPT_API_VERSION, + ) : PirWebMessageResponse { + + data class UserData( + val isAuthenticatedUser: Boolean, + ) + } + + data class GetCurrentUserProfileResponse( + val names: List, + val birthYear: Int? = null, + val addresses: List
, + ) : PirWebMessageResponse { + + data class Address( + val city: String, + val state: String, + ) + + data class Name( + val first: String, + val middle: String? = null, + val last: String, + ) + } + + data class GetDataBrokersResponse( + val dataBrokers: List, + ) : PirWebMessageResponse { + + data class DataBroker( + val name: String, + val url: String, + val optOutUrl: String? = null, + val parentURL: String? = null, + ) + } + + data class InitialScanResponse( + val resultsFound: List, + val scanProgress: ScanProgress, + ) : PirWebMessageResponse { + + // TODO add fields + class ScanResult + + data class ScanProgress( + val currentScan: Int, + val totalScans: Int, + val scannedBrokers: List, + ) { + // TODO add fields + class ScannedBroker + } + } +} From 9d9e533de439b5a72b99c7538af484e59c480b97 Mon Sep 17 00:00:00 2001 From: Domen Lanisnik Date: Mon, 18 Aug 2025 17:14:03 +0200 Subject: [PATCH 2/2] Fix formatting --- .../PirWebAddNameToCurrentUserProfileMessageHandler.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddNameToCurrentUserProfileMessageHandler.kt b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddNameToCurrentUserProfileMessageHandler.kt index 7f861856fc7e..e8eceee8f66e 100644 --- a/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddNameToCurrentUserProfileMessageHandler.kt +++ b/pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddNameToCurrentUserProfileMessageHandler.kt @@ -48,7 +48,8 @@ class PirWebAddNameToCurrentUserProfileMessageHandler @Inject constructor( ) { logcat { "PIR-WEB: PirWebAddNameToCurrentUserProfileMessageHandler: process $jsMessage" } - val request = jsMessage.toRequestMessage(PirWebMessageRequest.AddNameToCurrentUserProfileRequest::class) + val request = + jsMessage.toRequestMessage(PirWebMessageRequest.AddNameToCurrentUserProfileRequest::class) // attempting to add an empty name should return success=false if (request == null || request.first.isEmpty() || request.last.isEmpty()) { @@ -61,7 +62,12 @@ class PirWebAddNameToCurrentUserProfileMessageHandler @Inject constructor( } // attempting to add a duplicate name should return success=false - if (pirWebOnboardingStateHolder.names.any { it.firstName == request.first && it.middleName == request.middle && it.lastName == request.last }) { + if (pirWebOnboardingStateHolder.names.any { + it.firstName == request.first && + it.middleName == request.middle && + it.lastName == request.last + } + ) { logcat { "PIR-WEB: PirWebAddNameToCurrentUserProfileMessageHandler: duplicate name detected" } jsMessaging.sendResponse( jsMessage = jsMessage,