Skip to content

Commit

Permalink
migration utils
Browse files Browse the repository at this point in the history
  • Loading branch information
polstianka committed Nov 6, 2024
1 parent 02796a9 commit f1ffc01
Show file tree
Hide file tree
Showing 34 changed files with 605 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ inline fun <reified T> fromJSON(json: String): T {
}

fun <R> withRetry(
times: Int = 3,
delay: Long = 300,
times: Int = 5,
delay: Long = 500,
retryBlock: () -> R
): R? {
var index = -1
Expand All @@ -47,7 +47,7 @@ fun <R> withRetry(
return null
} catch (e: Throwable) {
val statusCode = e.getHttpStatusCode()
if (statusCode == 429 || statusCode == 401 || statusCode == 502) {
if (statusCode == 429 || statusCode == 401 || statusCode == 502 || statusCode == 520) {
SystemClock.sleep(delay)
continue
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ class AccountRepository(
}
}

fun addMnemonic(words: List<String>) {
vaultSource.addMnemonic(words)
}

private suspend fun migrationFromRN() = withContext(Dispatchers.IO) {
val (selectedId, wallets) = migrationHelper.loadLegacy()
if (wallets.isNotEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,21 @@ class DAppsRepository(

init {
scope.launch(Dispatchers.IO) {
if (rnLegacy.isRequestMigration()) {
migrationFromLegacy()
}
try {
if (rnLegacy.isRequestMigration()) {
migrationFromLegacy()
}

val connections = database.getConnections()
if (connections.isEmpty()) {
migrationFromLegacy()
_connectionsFlow.value = database.getConnections()
} else {
_connectionsFlow.value = connections
val connections = database.getConnections()
if (connections.isEmpty()) {
migrationFromLegacy()
_connectionsFlow.value = database.getConnections()
} else {
_connectionsFlow.value = connections
}
} catch (e: Throwable) {
FirebaseCrashlytics.getInstance().recordException(e)
_connectionsFlow.value = emptyList()
}
}

Expand Down Expand Up @@ -250,7 +255,7 @@ class DAppsRepository(
}
}

private suspend fun migrationFromLegacy(connections: RNTCApps, testnet: Boolean) {
suspend fun migrationFromLegacy(connections: RNTCApps, testnet: Boolean) {
val accountId = connections.address.toRawAddress()
for (legacyApp in connections.apps) {
val newApp = AppEntity(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,36 +153,41 @@ internal class DatabaseSource(
prefs.remove(LAST_EVENT_ID_KEY)
true
} catch (e: Throwable) {
FirebaseCrashlytics.getInstance().recordException(e)
false
}
}

suspend fun insertConnection(connection: AppConnectEntity) = withContext(coroutineContext) {
val prefix = prefixAccount(connection.accountId, connection.testnet)

writableDatabase.delete(CONNECT_TABLE_NAME, "$CONNECT_TABLE_CLIENT_ID_COLUMN = ?", arrayOf(connection.clientId))
encryptedPrefs.edit {
remove(prefixKeyPair(prefix, connection.clientId))
remove(prefixProofSignature(prefix, connection.appUrl))
remove(prefixProofPayload(prefix, connection.appUrl))
}

val values = ContentValues()
values.put(CONNECT_TABLE_APP_URL_COLUMN, connection.appUrl.withoutQuery.toString().removeSuffix("/"))
values.put(CONNECT_TABLE_ACCOUNT_ID_COLUMN, connection.accountId)
values.put(CONNECT_TABLE_TESTNET_COLUMN, if (connection.testnet) 1 else 0)
values.put(CONNECT_TABLE_CLIENT_ID_COLUMN, connection.clientId)
values.put(CONNECT_TABLE_TYPE_COLUMN, connection.type.value)
values.put(CONNECT_TABLE_TIMESTAMP_COLUMN, connection.timestamp)
writableDatabase.insertOrThrow(CONNECT_TABLE_NAME, null, values)
try {
val prefix = prefixAccount(connection.accountId, connection.testnet)

writableDatabase.delete(CONNECT_TABLE_NAME, "$CONNECT_TABLE_CLIENT_ID_COLUMN = ?", arrayOf(connection.clientId))
encryptedPrefs.edit {
remove(prefixKeyPair(prefix, connection.clientId))
remove(prefixProofSignature(prefix, connection.appUrl))
remove(prefixProofPayload(prefix, connection.appUrl))
}

encryptedPrefs.putParcelable(prefixKeyPair(prefix, connection.clientId), connection.keyPair)
if (connection.proofSignature != null) {
encryptedPrefs.putString(prefixProofSignature(prefix, connection.appUrl), connection.proofSignature)
}
if (connection.proofPayload != null) {
encryptedPrefs.putString(prefixProofPayload(prefix, connection.appUrl), connection.proofPayload)
val values = ContentValues()
values.put(CONNECT_TABLE_APP_URL_COLUMN, connection.appUrl.withoutQuery.toString().removeSuffix("/"))
values.put(CONNECT_TABLE_ACCOUNT_ID_COLUMN, connection.accountId)
values.put(CONNECT_TABLE_TESTNET_COLUMN, if (connection.testnet) 1 else 0)
values.put(CONNECT_TABLE_CLIENT_ID_COLUMN, connection.clientId)
values.put(CONNECT_TABLE_TYPE_COLUMN, connection.type.value)
values.put(CONNECT_TABLE_TIMESTAMP_COLUMN, connection.timestamp)
writableDatabase.insertOrThrow(CONNECT_TABLE_NAME, null, values)


encryptedPrefs.putParcelable(prefixKeyPair(prefix, connection.clientId), connection.keyPair)
if (connection.proofSignature != null) {
encryptedPrefs.putString(prefixProofSignature(prefix, connection.appUrl), connection.proofSignature)
}
if (connection.proofPayload != null) {
encryptedPrefs.putString(prefixProofPayload(prefix, connection.appUrl), connection.proofPayload)
}
} catch (e: Throwable) {
FirebaseCrashlytics.getInstance().recordException(e)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.tonapps.wallet.data.rn

sealed class RNException(message: String, cause: Throwable? = null) : Exception(message, cause) {

data object EmptyChunks: RNException("wallets_chunks = 0") {
private fun readResolve(): Any = EmptyChunks
}

data class NotFoundChunk(val chunk: Int): RNException("chunk $chunk not found")

data class NotFoundMnemonic(val walletId: String): RNException("mnemonic for wallet $walletId not found")

data object NotFoundPasscode: RNException("passcode not found") {
private fun readResolve(): Any = NotFoundPasscode
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ class RNLegacy(
}
}

suspend fun getVaultStateWithThrow(passcode: String): RNVaultState = withContext(Dispatchers.IO) {
seedStorage.getWithThrow(passcode)
}

fun getSpamTransactions(walletId: String): RNSpamTransactions {
val key = keySpamTransactions(walletId)
val json = sql.getJSONObject(key) ?: return RNSpamTransactions(walletId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,25 @@ internal class RNSeedStorage(context: Context) {
val json = JSONObject(encryptedString)
return SeedState(json)
}

suspend fun getWithThrow(passcode: String): RNVaultState = withContext(Dispatchers.IO) {
val state = readStateWithThrow()
val decrypted = ScryptBox.decrypt(passcode, state)
val json = JSONObject(decrypted)
RNVaultState.of(json)
}

private suspend fun readStateWithThrow(): SeedState {
val chunks = kv.getItemImpl("${walletsKey}_chunks")?.toIntOrNull() ?: 0
if (0 >= chunks) {
throw RNException.EmptyChunks
}
val builder = StringBuilder()
for (i in 0 until chunks) {
val chunk = kv.getItemImpl("${walletsKey}_chunk_$i") ?: throw RNException.NotFoundChunk(i)
builder.append(chunk)
}
val json = JSONObject(builder.toString())
return SeedState(json)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ data class RNVaultState(
val string: String
get() = toJSON().toString()


fun getDecryptedData(walletId: String): RNDecryptedData? {
return keys[walletId]
}

fun list(): List<RNDecryptedData> {
val list = mutableListOf<RNDecryptedData>()
for (m in keys) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ data class RNWallets(
val lockScreenEnabled: Boolean
): RNData() {

val count: Int
get() = wallets.size

companion object {
val empty = RNWallets(
wallets = emptyList(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@ object AnalyticsHelper {
}

@UiThread
fun trackEvent(name: String) {
Aptabase.instance.trackEvent(name)
fun trackEvent(name: String, installId: String) {
Aptabase.instance.trackEvent(name, hashMapOf(
"firebase_user_id" to installId
))
}

@UiThread
fun trackEventClickDApp(url: String) {
Aptabase.instance.trackEvent("click_dapp", hashMapOf(url to "url"))
fun trackEventClickDApp(url: String, installId: String) {
Aptabase.instance.trackEvent("click_dapp", hashMapOf(
url to "url",
"firebase_user_id" to installId
))
}

private fun initAptabase(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.tonapps.tonkeeper.extensions

import com.tonapps.wallet.data.account.AccountRepository
import com.tonapps.wallet.data.rn.RNException
import com.tonapps.wallet.data.rn.RNLegacy
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.ton.api.pk.PrivateKeyEd25519
import org.ton.mnemonic.Mnemonic
import uikit.navigation.NavigationActivity

suspend fun AccountRepository.requestPrivateKey(
activity: NavigationActivity,
rnLegacy: RNLegacy,
walletId: String,
): PrivateKeyEd25519 = withContext(Dispatchers.IO) {
val privateKeyEd25519 = getPrivateKey(walletId)
if (privateKeyEd25519 != null) {
privateKeyEd25519
} else {
val vaultState = rnLegacy.requestVault(activity)
val mnemonic = vaultState.getDecryptedData(walletId)?.mnemonic ?: throw RNException.NotFoundMnemonic(walletId)
val seed = Mnemonic.toSeed(splitMnemonic(mnemonic))
PrivateKeyEd25519(seed)
}
}

private fun splitMnemonic(mnemonic: String): List<String> {
val words = if (mnemonic.contains(",")) {
mnemonic.split(",")
} else {
mnemonic.split(" ")
}
return words.map { it.trim() }
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.tonapps.tonkeeper.extensions

import com.tonapps.wallet.data.passcode.dialog.PasscodeDialog
import com.tonapps.wallet.data.rn.RNException
import com.tonapps.wallet.data.rn.RNLegacy
import com.tonapps.wallet.data.rn.data.RNVaultState
import com.tonapps.wallet.data.rn.data.RNWallets
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import uikit.navigation.NavigationActivity

suspend fun RNLegacy.requestVault(
activity: NavigationActivity
): RNVaultState = withContext(Dispatchers.IO) {
val wallets = getWallets()
if (wallets.count == 0) {
throw IllegalStateException("No wallets found")
}
val passcode = requestPasscode(activity, wallets)
getVaultStateWithThrow(passcode)
}

private suspend fun RNLegacy.requestPasscode(
activity: NavigationActivity,
wallets: RNWallets
): String = withContext(Dispatchers.Main) {
val passcodeFromBiometry = if (wallets.biometryEnabled) {
exportPasscodeWithBiometry()
} else {
null
}
val passcode = if (!passcodeFromBiometry.isNullOrBlank()) {
passcodeFromBiometry
} else {
PasscodeDialog.request(activity)
}

passcode ?: throw RNException.NotFoundPasscode
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class DAppScreen(wallet: WalletEntity): WalletContextScreen(R.layout.fragment_da

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AnalyticsHelper.trackEventClickDApp(args.url.toString())
AnalyticsHelper.trackEventClickDApp(args.url.toString(), rootViewModel.installId)
}

private fun applyHost(url: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class BrowserMainScreen(wallet: WalletEntity): WalletContextScreen(R.layout.frag

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AnalyticsHelper.trackEvent("browser_open")
AnalyticsHelper.trackEvent("browser_open", viewModel.installId)
navigation?.setFragmentResultListener(COUNTRY_REQUEST_KEY) { bundle ->

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class BrowserMainViewModel(

val countryFlow = settings.getLocaleCountryFlow(api)

val installId: String
get() = settings.installId

fun setBottomScrolled(value: Boolean) {
_childBottomScrolled.tryEmit(value)
}
Expand Down
Loading

0 comments on commit f1ffc01

Please sign in to comment.