Skip to content

PIR: Use models for request and response bodies #6603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -37,8 +39,8 @@ import logcat.logcat
class PirWebAddAddressToCurrentUserProfileMessageHandler @Inject constructor(
private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder,
) : PirWebJsMessageHandler() {
override val messageNames: List<PirDashboardWebMessages> =
listOf(PirDashboardWebMessages.ADD_ADDRESS_TO_CURRENT_USER_PROFILE)

override val message = PirDashboardWebMessages.ADD_ADDRESS_TO_CURRENT_USER_PROFILE

override fun process(
jsMessage: JsMessage,
Expand All @@ -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,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,8 +39,7 @@ class PirWebAddNameToCurrentUserProfileMessageHandler @Inject constructor(
private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder,
) : PirWebJsMessageHandler() {

override val messageNames: List<PirDashboardWebMessages> =
listOf(PirDashboardWebMessages.ADD_NAME_TO_CURRENT_USER_PROFILE)
override val message = PirDashboardWebMessages.ADD_NAME_TO_CURRENT_USER_PROFILE

override fun process(
jsMessage: JsMessage,
Expand All @@ -48,42 +48,46 @@ 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
}

// 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,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,15 @@ 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
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
Expand All @@ -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<PirDashboardWebMessages> = listOf(PirDashboardWebMessages.GET_CURRENT_USER_PROFILE)
override val message = PirDashboardWebMessages.GET_CURRENT_USER_PROFILE

override fun process(
jsMessage: JsMessage,
Expand All @@ -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
}
Expand All @@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -46,8 +44,8 @@ class PirWebGetDataBrokersMessageHandler @Inject constructor(
private val dispatcherProvider: DispatcherProvider,
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
) : PirWebJsMessageHandler() {
override val messageNames: List<PirDashboardWebMessages> =
listOf(PirDashboardWebMessages.GET_DATA_BROKERS)

override val message = PirDashboardWebMessages.GET_DATA_BROKERS

override fun process(
jsMessage: JsMessage,
Expand All @@ -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<Broker>.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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -35,7 +35,7 @@ import org.json.JSONObject
)
class PirWebHandshakeMessageHandler @Inject constructor() : PirWebJsMessageHandler() {

override val messageNames: List<PirDashboardWebMessages> = listOf(PirDashboardWebMessages.HANDSHAKE)
override val message = PirDashboardWebMessages.HANDSHAKE

override fun process(
jsMessage: JsMessage,
Expand All @@ -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"
}
}
Loading
Loading