@@ -33,11 +33,15 @@ import com.duckduckgo.sync.impl.AccountErrorCodes.EXCHANGE_FAILED
33
33
import com.duckduckgo.sync.impl.AccountErrorCodes.GENERIC_ERROR
34
34
import com.duckduckgo.sync.impl.AccountErrorCodes.INVALID_CODE
35
35
import com.duckduckgo.sync.impl.AccountErrorCodes.LOGIN_FAILED
36
- import com.duckduckgo.sync.impl.CodeType.UNKNOWN
36
+ import com.duckduckgo.sync.impl.CodeType.Connect
37
+ import com.duckduckgo.sync.impl.CodeType.Exchange
38
+ import com.duckduckgo.sync.impl.CodeType.Recovery
39
+ import com.duckduckgo.sync.impl.CodeType.Unknown
37
40
import com.duckduckgo.sync.impl.ExchangeResult.*
38
41
import com.duckduckgo.sync.impl.Result.Error
39
42
import com.duckduckgo.sync.impl.Result.Success
40
43
import com.duckduckgo.sync.impl.pixels.*
44
+ import com.duckduckgo.sync.impl.ui.qrcode.SyncBarcodeUrl
41
45
import com.duckduckgo.sync.store.*
42
46
import com.squareup.anvil.annotations.*
43
47
import com.squareup.moshi.*
@@ -54,7 +58,7 @@ interface SyncAccountRepository {
54
58
fun isSyncSupported (): Boolean
55
59
fun createAccount (): Result <Boolean >
56
60
fun isSignedIn (): Boolean
57
- fun processCode (stringCode : String ): Result <Boolean >
61
+ fun processCode (code : CodeType ): Result <Boolean >
58
62
fun getAccountInfo (): AccountInfo
59
63
fun logout (deviceId : String ): Result <Boolean >
60
64
fun deleteAccount (): Result <Boolean >
@@ -114,58 +118,67 @@ class AppSyncAccountRepository @Inject constructor(
114
118
}
115
119
}
116
120
117
- override fun processCode (stringCode : String ): Result <Boolean > {
118
- val decodedCode: String? = kotlin.runCatching {
119
- return @runCatching stringCode.decodeB64()
120
- }.getOrNull()
121
- if (decodedCode == null ) {
122
- Timber .w(" Failed while b64 decoding barcode; barcode is unusable" )
123
- return Error (code = INVALID_CODE .code, reason = " Failed to decode code" )
124
- }
125
-
126
- kotlin.runCatching {
127
- Adapters .recoveryCodeAdapter.fromJson(decodedCode)?.recovery
128
- }.getOrNull()?.let {
129
- Timber .d(" Sync: code is a recovery code" )
130
- return login(it)
131
- }
121
+ override fun processCode (code : CodeType ): Result <Boolean > {
122
+ when (code) {
123
+ is Recovery -> {
124
+ Timber .d(" Sync: code is a recovery code" )
125
+ return login(code.code)
126
+ }
132
127
133
- kotlin.runCatching {
134
- Adapters .recoveryCodeAdapter.fromJson(decodedCode)?.connect
135
- }.getOrNull()?.let {
136
- Timber .d(" Sync: code is a connect code" )
137
- return connectDevice(it)
138
- }
128
+ is Connect -> {
129
+ Timber .d(" Sync: code is a connect code" )
130
+ return connectDevice(code.code)
131
+ }
139
132
140
- kotlin.runCatching {
141
- Adapters .invitationCodeAdapter.fromJson(decodedCode)?.exchangeKey
142
- }.getOrNull()?. let {
143
- if ( ! syncFeature.exchangeKeysToSyncWithAnotherDevice().isEnabled()) {
144
- Timber .w( " Sync: Scanned exchange code type but exchanging keys to sync with another device is disabled " )
145
- return @let null
133
+ is Exchange -> {
134
+ if ( ! syncFeature.exchangeKeysToSyncWithAnotherDevice().isEnabled()) {
135
+ Timber .w( " Sync: Scanned exchange code type but exchanging keys to sync with another device is disabled " )
136
+ } else {
137
+ return onInvitationCodeReceived(code.code )
138
+ }
146
139
}
147
140
148
- return onInvitationCodeReceived(it)
141
+ else -> {
142
+ Timber .d(" Sync: code type unknown" )
143
+ }
149
144
}
150
-
151
- Timber .e(" Sync: code is not supported" )
145
+ Timber .e(" Sync: code type (${code.javaClass.simpleName} ) is not supported" )
152
146
return Error (code = INVALID_CODE .code, reason = " Failed to decode code" )
153
147
}
154
148
155
149
override fun getCodeType (stringCode : String ): CodeType {
150
+ // check first if it's a URL which contains the code
151
+ val (code, wasInUrl) = kotlin.runCatching {
152
+ SyncBarcodeUrl .parseUrl(stringCode)?.webSafeB64EncodedCode?.removeUrlSafetyToRestoreB64()
153
+ ?.let { Pair (it, true ) }
154
+ ? : Pair (stringCode, false )
155
+ }.getOrDefault(Pair (stringCode, false ))
156
+
156
157
return kotlin.runCatching {
157
- val decodedCode = stringCode.decodeB64()
158
- when {
159
- Adapters .recoveryCodeAdapter.fromJson(decodedCode)?.recovery != null -> CodeType .RECOVERY
160
- Adapters .recoveryCodeAdapter.fromJson(decodedCode)?.connect != null -> CodeType .CONNECT
161
- Adapters .invitationCodeAdapter.fromJson(decodedCode)?.exchangeKey != null -> CodeType .EXCHANGE
162
- else -> UNKNOWN
163
- }
164
- }.onFailure {
158
+ val decodedCode = code.decodeB64()
159
+
160
+ canParseAsRecoveryCode(decodedCode)?.let {
161
+ if (wasInUrl) {
162
+ throw IllegalArgumentException (" cdr Sync: Recovery code found inside a URL which is not acceptable" )
163
+ } else {
164
+ Recovery (it)
165
+ }
166
+ }
167
+ ? : canParseAsExchangeCode(decodedCode)?.let { Exchange (it) }
168
+ ? : canParseAsConnectCode(decodedCode)?.let { Connect (it) }
169
+ ? : Unknown (code)
170
+ }.onSuccess {
171
+ Timber .i(" cdr Sync: code type is ${it.javaClass.simpleName} . was inside url: $wasInUrl " )
172
+ }.getOrElse {
165
173
Timber .e(it, " Failed to decode code" )
166
- }.getOrDefault(UNKNOWN )
174
+ Unknown (code)
175
+ }
167
176
}
168
177
178
+ private fun canParseAsRecoveryCode (decodedCode : String ) = Adapters .recoveryCodeAdapter.fromJson(decodedCode)?.recovery
179
+ private fun canParseAsExchangeCode (decodedCode : String ) = Adapters .invitationCodeAdapter.fromJson(decodedCode)?.exchangeKey
180
+ private fun canParseAsConnectCode (decodedCode : String ) = Adapters .recoveryCodeAdapter.fromJson(decodedCode)?.connect
181
+
169
182
private fun onInvitationCodeReceived (invitationCode : InvitationCode ): Result <Boolean > {
170
183
// Sync: InviteFlow - B (https://app.asana.com/0/72649045549333/1209571867429615)
171
184
Timber .d(" Sync-exchange: InviteFlow - B. code is an exchange code $invitationCode " )
@@ -596,7 +609,8 @@ class AppSyncAccountRepository @Inject constructor(
596
609
}
597
610
598
611
is Success -> {
599
- val loginResult = processCode(stringCode)
612
+ val codeType = getCodeType(stringCode)
613
+ val loginResult = processCode(codeType)
600
614
if (loginResult is Error ) {
601
615
syncPixels.fireUserSwitchedLoginError()
602
616
}
@@ -882,11 +896,11 @@ enum class AccountErrorCodes(val code: Int) {
882
896
EXCHANGE_FAILED (56 ),
883
897
}
884
898
885
- enum class CodeType {
886
- RECOVERY ,
887
- CONNECT ,
888
- EXCHANGE ,
889
- UNKNOWN ,
899
+ sealed interface CodeType {
900
+ data class Recovery ( val code : RecoveryCode ) : CodeType
901
+ data class Connect ( val code : ConnectCode ) : CodeType
902
+ data class Exchange ( val code : InvitationCode ) : CodeType
903
+ data class Unknown ( val code : String ) : CodeType
890
904
}
891
905
892
906
sealed class Result <out R > {
0 commit comments