Skip to content

Commit de830f1

Browse files
authored
Support actionError and handle different pir errors (#6179)
Task/Issue URL: https://app.asana.com/1/137249556945/project/72649045549333/task/1210330956553726?focus=true ### Description See attached task description ### Steps to test this PR https://app.asana.com/1/137249556945/project/72649045549333/task/1210461994140888?focus=true
1 parent e765872 commit de830f1

File tree

11 files changed

+317
-94
lines changed

11 files changed

+317
-94
lines changed

pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/common/PirActionsRunner.kt

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ import com.duckduckgo.pir.internal.common.NativeBrokerActionHandler.NativeAction
3535
import com.duckduckgo.pir.internal.common.PirJob.RunType
3636
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine
3737
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.CaptchaInfoReceived
38+
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.CaptchaServiceFailed
3839
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.EmailConfirmationLinkReceived
40+
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.EmailFailed
3941
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.EmailReceived
4042
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.ExecuteNextBrokerAction
4143
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.JsActionFailed
4244
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.JsActionSuccess
45+
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.JsErrorReceived
4346
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.LoadUrlComplete
4447
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.LoadUrlFailed
4548
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.RetryAwaitCaptchaSolution
@@ -58,7 +61,7 @@ import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Si
5861
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngineFactory
5962
import com.duckduckgo.pir.internal.scripts.BrokerActionProcessor
6063
import com.duckduckgo.pir.internal.scripts.BrokerActionProcessor.ActionResultListener
61-
import com.duckduckgo.pir.internal.scripts.models.PirErrorReponse
64+
import com.duckduckgo.pir.internal.scripts.models.PirError
6265
import com.duckduckgo.pir.internal.scripts.models.PirScriptRequestData
6366
import com.duckduckgo.pir.internal.scripts.models.PirSuccessResponse
6467
import com.duckduckgo.pir.internal.scripts.models.ProfileQuery
@@ -273,7 +276,7 @@ class RealPirActionsRunner @AssistedInject constructor(
273276
// IF this timer completes, then timeout was reached
274277
kotlin.runCatching {
275278
onError(
276-
PirErrorReponse(
279+
PirError.ActionFailed(
277280
actionID = effect.actionId,
278281
message = "Local timeout",
279282
),
@@ -294,9 +297,8 @@ class RealPirActionsRunner @AssistedInject constructor(
294297
private suspend fun handleAwaitCaptchaSolution(effect: AwaitCaptchaSolution) = withContext(dispatcherProvider.io()) {
295298
if (effect.transactionID.isEmpty()) {
296299
onError(
297-
PirErrorReponse(
298-
actionID = effect.actionId,
299-
message = "Unable to solve captcha",
300+
PirError.CaptchaServiceError(
301+
"Unable to solve captcha",
300302
),
301303
)
302304
} else {
@@ -319,9 +321,8 @@ class RealPirActionsRunner @AssistedInject constructor(
319321
else -> {
320322
if (effect.attempt == effect.retries) {
321323
onError(
322-
PirErrorReponse(
323-
actionID = effect.actionId,
324-
message = "Unable to solve captcha",
324+
PirError.CaptchaServiceError(
325+
"Unable to solve captcha",
325326
),
326327
)
327328
} else {
@@ -339,9 +340,8 @@ class RealPirActionsRunner @AssistedInject constructor(
339340
}
340341
} else {
341342
onError(
342-
PirErrorReponse(
343-
actionID = effect.actionId,
344-
message = "Unable to solve captcha",
343+
PirError.CaptchaServiceError(
344+
"Unable to solve captcha",
345345
),
346346
)
347347
}
@@ -375,9 +375,8 @@ class RealPirActionsRunner @AssistedInject constructor(
375375
} else {
376376
val result = it as Failure
377377
onError(
378-
PirErrorReponse(
379-
actionID = it.actionId,
380-
message = result.message,
378+
PirError.CaptchaServiceError(
379+
error = result.message,
381380
),
382381
)
383382
}
@@ -402,9 +401,8 @@ class RealPirActionsRunner @AssistedInject constructor(
402401
} else {
403402
val result = it as Failure
404403
onError(
405-
PirErrorReponse(
406-
actionID = result.actionId,
407-
message = result.message,
404+
PirError.EmailError(
405+
error = result.message,
408406
),
409407
)
410408
}
@@ -427,9 +425,8 @@ class RealPirActionsRunner @AssistedInject constructor(
427425
} else {
428426
val result = it as Failure
429427
onError(
430-
PirErrorReponse(
431-
actionID = result.actionId,
432-
message = result.message,
428+
PirError.EmailError(
429+
error = result.message,
433430
),
434431
)
435432
}
@@ -470,15 +467,31 @@ class RealPirActionsRunner @AssistedInject constructor(
470467
)
471468
}
472469

473-
override fun onError(pirErrorReponse: PirErrorReponse) {
470+
override fun onError(pirError: PirError) {
474471
if (timerJob.isActive) {
475472
timerJob.cancel()
476473
}
477474

478-
engine?.dispatch(
479-
JsActionFailed(
480-
pirErrorReponse = pirErrorReponse,
481-
),
482-
)
475+
when (pirError) {
476+
is PirError.ActionFailed -> JsActionFailed(
477+
error = pirError,
478+
)
479+
480+
is PirError.CaptchaServiceError -> CaptchaServiceFailed(
481+
error = pirError,
482+
)
483+
484+
is PirError.EmailError -> EmailFailed(
485+
error = pirError,
486+
)
487+
488+
is PirError.JsError -> JsErrorReceived(
489+
error = pirError,
490+
)
491+
492+
else -> null
493+
}?.also {
494+
engine?.dispatch(it)
495+
}
483496
}
484497
}

pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/common/PirRunStateHandler.kt

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import com.duckduckgo.pir.internal.common.PirRunStateHandler.PirRunState.BrokerS
3333
import com.duckduckgo.pir.internal.common.PirRunStateHandler.PirRunState.BrokerScheduledScanStarted
3434
import com.duckduckgo.pir.internal.pixels.PirPixelSender
3535
import com.duckduckgo.pir.internal.scripts.models.ExtractedProfile
36-
import com.duckduckgo.pir.internal.scripts.models.PirErrorReponse
3736
import com.duckduckgo.pir.internal.scripts.models.PirSuccessResponse
3837
import com.duckduckgo.pir.internal.scripts.models.PirSuccessResponse.ClickResponse
3938
import com.duckduckgo.pir.internal.scripts.models.PirSuccessResponse.ExpectationResponse
@@ -92,7 +91,8 @@ interface PirRunStateHandler {
9291
data class BrokerScanActionFailed(
9392
override val brokerName: String,
9493
val actionType: String,
95-
val pirErrorReponse: PirErrorReponse,
94+
val actionID: String,
95+
val message: String,
9696
) : PirRunState(brokerName)
9797

9898
data class BrokerOptOutStarted(
@@ -131,7 +131,8 @@ interface PirRunStateHandler {
131131
val extractedProfile: ExtractedProfile,
132132
val completionTimeInMillis: Long,
133133
val actionType: String,
134-
val result: PirErrorReponse,
134+
val actionID: String,
135+
val message: String,
135136
) : PirRunState(brokerName)
136137
}
137138
}
@@ -155,9 +156,6 @@ class RealPirRunStateHandler @Inject constructor(
155156
).add(KotlinJsonAdapterFactory())
156157
.build().adapter(PirSuccessResponse::class.java)
157158
}
158-
private val pirErrorAdapter by lazy {
159-
Moshi.Builder().build().adapter(PirErrorReponse::class.java)
160-
}
161159

162160
override suspend fun handleState(pirRunState: PirRunState) = withContext(dispatcherProvider.io()) {
163161
when (pirRunState) {
@@ -259,7 +257,7 @@ class RealPirRunStateHandler @Inject constructor(
259257
repository.saveErrorResult(
260258
brokerName = state.brokerName,
261259
actionType = state.actionType,
262-
error = state.pirErrorReponse,
260+
message = state.message,
263261
)
264262
}
265263

@@ -315,7 +313,7 @@ class RealPirRunStateHandler @Inject constructor(
315313
completionTimeInMillis = state.completionTimeInMillis,
316314
actionType = state.actionType,
317315
isError = true,
318-
result = pirErrorAdapter.toJson(state.result),
316+
result = "${state.actionID}: ${state.message}}",
319317
)
320318
}
321319
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.pir.internal.common.actions
18+
19+
import com.duckduckgo.di.scopes.AppScope
20+
import com.duckduckgo.pir.internal.common.actions.EventHandler.Next
21+
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event
22+
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.CaptchaServiceFailed
23+
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.ExecuteNextBrokerAction
24+
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.State
25+
import com.duckduckgo.pir.internal.scripts.models.PirScriptRequestData.UserProfile
26+
import com.squareup.anvil.annotations.ContributesMultibinding
27+
import javax.inject.Inject
28+
import kotlin.reflect.KClass
29+
30+
@ContributesMultibinding(
31+
scope = AppScope::class,
32+
boundType = EventHandler::class,
33+
)
34+
class CaptchaServiceFailedEventHandler @Inject constructor() : EventHandler {
35+
override val event: KClass<out Event> = CaptchaServiceFailed::class
36+
37+
override suspend fun invoke(
38+
state: State,
39+
event: Event,
40+
): Next {
41+
/**
42+
* A captcha related error has occurred. However, we still would like to continue to the next broker action.
43+
*/
44+
return Next(
45+
nextState = state.copy(
46+
currentActionIndex = state.currentActionIndex + 1,
47+
),
48+
nextEvent = ExecuteNextBrokerAction(
49+
UserProfile(
50+
userProfile = state.profileQuery,
51+
),
52+
),
53+
)
54+
}
55+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.pir.internal.common.actions
18+
19+
import com.duckduckgo.di.scopes.AppScope
20+
import com.duckduckgo.pir.internal.common.actions.EventHandler.Next
21+
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event
22+
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.EmailFailed
23+
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.JsActionFailed
24+
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.State
25+
import com.duckduckgo.pir.internal.scripts.models.PirError
26+
import com.squareup.anvil.annotations.ContributesMultibinding
27+
import javax.inject.Inject
28+
import kotlin.reflect.KClass
29+
30+
@ContributesMultibinding(
31+
scope = AppScope::class,
32+
boundType = EventHandler::class,
33+
)
34+
class EmailFailedEventHandler @Inject constructor() : EventHandler {
35+
override val event: KClass<out Event> = EmailFailed::class
36+
37+
override suspend fun invoke(
38+
state: State,
39+
event: Event,
40+
): Next {
41+
/**
42+
* If we encounter an email related email, it means the broker action depending on it will fail.
43+
*/
44+
val currentBroker = state.brokers[state.currentBrokerIndex]
45+
val currentAction = currentBroker.actions[state.currentActionIndex]
46+
47+
return Next(
48+
nextState = state,
49+
nextEvent = JsActionFailed(
50+
error = PirError.ActionFailed(
51+
actionID = currentAction.id,
52+
message = (event as EmailFailed).error.error,
53+
),
54+
),
55+
)
56+
}
57+
}

pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/common/actions/JsActionFailedEventHandler.kt

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,8 @@ import com.duckduckgo.pir.internal.common.PirRunStateHandler.PirRunState.BrokerS
2525
import com.duckduckgo.pir.internal.common.actions.EventHandler.Next
2626
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event
2727
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.BrokerActionsCompleted
28-
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.ExecuteNextBrokerAction
2928
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.Event.JsActionFailed
3029
import com.duckduckgo.pir.internal.common.actions.PirActionsRunnerStateEngine.State
31-
import com.duckduckgo.pir.internal.scripts.models.BrokerAction.GetCaptchaInfo
32-
import com.duckduckgo.pir.internal.scripts.models.BrokerAction.SolveCaptcha
33-
import com.duckduckgo.pir.internal.scripts.models.PirScriptRequestData.UserProfile
3430
import com.duckduckgo.pir.internal.scripts.models.asActionType
3531
import com.squareup.anvil.annotations.ContributesMultibinding
3632
import javax.inject.Inject
@@ -52,19 +48,19 @@ class JsActionFailedEventHandler @Inject constructor(
5248
): Next {
5349
/**
5450
* This means we have received an error from the JS layer for the last action we pushed.
55-
* If the action is GetCaptchaInfo or SolveCaptcha, we proceed to the next action (ignore error)
56-
* Else we end the run for the broker
51+
* We end the run for the broker.
5752
*/
5853
val currentBroker = state.brokers[state.currentBrokerIndex]
5954
val currentAction = currentBroker.actions[state.currentActionIndex]
60-
val pirErrorReponse = (event as JsActionFailed).pirErrorReponse
55+
val error = (event as JsActionFailed).error
6156

6257
if (state.runType != RunType.OPTOUT) {
6358
pirRunStateHandler.handleState(
6459
BrokerScanActionFailed(
6560
brokerName = currentBroker.brokerName,
6661
actionType = currentAction.asActionType(),
67-
pirErrorReponse = pirErrorReponse,
62+
actionID = error.actionID,
63+
message = error.message,
6864
),
6965
)
7066
} else {
@@ -75,29 +71,17 @@ class JsActionFailedEventHandler @Inject constructor(
7571
extractedProfile = it,
7672
completionTimeInMillis = currentTimeProvider.currentTimeMillis(),
7773
actionType = currentAction.asActionType(),
78-
result = pirErrorReponse,
74+
actionID = error.actionID,
75+
message = error.message,
7976
),
8077
)
8178
}
8279
}
8380

84-
return if (currentAction is GetCaptchaInfo || currentAction is SolveCaptcha) {
85-
Next(
86-
nextState = state.copy(
87-
currentActionIndex = state.currentActionIndex + 1,
88-
),
89-
nextEvent = ExecuteNextBrokerAction(
90-
UserProfile(
91-
userProfile = state.profileQuery,
92-
),
93-
),
94-
)
95-
} else {
96-
// If error happens we skip to next Broker as next steps will not make sense
97-
Next(
98-
nextState = state,
99-
nextEvent = BrokerActionsCompleted(isSuccess = false),
100-
)
101-
}
81+
// If error happens we skip to next Broker as next steps will not make sense
82+
return Next(
83+
nextState = state,
84+
nextEvent = BrokerActionsCompleted(isSuccess = false),
85+
)
10286
}
10387
}

0 commit comments

Comments
 (0)