Saves a copy of the NIP65 and NIP17 relay lists locally
This commit is contained in:
@@ -42,6 +42,8 @@ import com.vitorpamplona.quartz.encoders.Nip47WalletConnect
|
||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.encoders.toNpub
|
||||
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.events.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.events.ContactListEvent
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||
@@ -90,6 +92,8 @@ private object PrefKeys {
|
||||
const val DEFAULT_DISCOVERY_FOLLOW_LIST = "defaultDiscoveryFollowList"
|
||||
const val ZAP_PAYMENT_REQUEST_SERVER = "zapPaymentServer"
|
||||
const val LATEST_CONTACT_LIST = "latestContactList"
|
||||
const val LATEST_DM_RELAY_LIST = "latestDMRelayList"
|
||||
const val LATEST_NIP65_RELAY_LIST = "latestNIP65RelayList"
|
||||
const val HIDE_DELETE_REQUEST_DIALOG = "hide_delete_request_dialog"
|
||||
const val HIDE_BLOCK_ALERT_DIALOG = "hide_block_alert_dialog"
|
||||
const val HIDE_NIP_17_WARNING_DIALOG = "hide_nip24_warning_dialog" // delete later
|
||||
@@ -311,10 +315,33 @@ object LocalPreferences {
|
||||
PrefKeys.ZAP_PAYMENT_REQUEST_SERVER,
|
||||
Event.mapper.writeValueAsString(account.zapPaymentRequest),
|
||||
)
|
||||
putString(
|
||||
PrefKeys.LATEST_CONTACT_LIST,
|
||||
Event.mapper.writeValueAsString(account.backupContactList),
|
||||
)
|
||||
if (account.backupContactList != null) {
|
||||
putString(
|
||||
PrefKeys.LATEST_CONTACT_LIST,
|
||||
Event.mapper.writeValueAsString(account.backupContactList),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_CONTACT_LIST)
|
||||
}
|
||||
|
||||
if (account.backupDMRelayList != null) {
|
||||
putString(
|
||||
PrefKeys.LATEST_DM_RELAY_LIST,
|
||||
Event.mapper.writeValueAsString(account.backupDMRelayList),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_DM_RELAY_LIST)
|
||||
}
|
||||
|
||||
if (account.backupNIP65RelayList != null) {
|
||||
putString(
|
||||
PrefKeys.LATEST_NIP65_RELAY_LIST,
|
||||
Event.mapper.writeValueAsString(account.backupNIP65RelayList),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_NIP65_RELAY_LIST)
|
||||
}
|
||||
|
||||
putBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, account.hideDeleteRequestDialog)
|
||||
putBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, account.hideNIP17WarningDialog)
|
||||
putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, account.hideBlockAlertDialog)
|
||||
@@ -471,8 +498,8 @@ object LocalPreferences {
|
||||
val latestContactList =
|
||||
try {
|
||||
getString(PrefKeys.LATEST_CONTACT_LIST, null)?.let {
|
||||
println("Decoding Contact List: " + it)
|
||||
if (it != null) {
|
||||
if (it != "null") {
|
||||
println("Decoding Contact List: $it")
|
||||
Event.fromJson(it) as ContactListEvent?
|
||||
} else {
|
||||
null
|
||||
@@ -488,6 +515,46 @@ object LocalPreferences {
|
||||
null
|
||||
}
|
||||
|
||||
val latestDmRelayList =
|
||||
try {
|
||||
getString(PrefKeys.LATEST_DM_RELAY_LIST, null)?.let {
|
||||
if (it != "null") {
|
||||
println("Decoding DM Relay List: $it")
|
||||
Event.fromJson(it) as ChatMessageRelayListEvent?
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.w(
|
||||
"LocalPreferences",
|
||||
"Error Decoding DM Relay List ${getString(PrefKeys.LATEST_DM_RELAY_LIST, null)}",
|
||||
e,
|
||||
)
|
||||
null
|
||||
}
|
||||
|
||||
val latestNip65RelayList =
|
||||
try {
|
||||
getString(PrefKeys.LATEST_NIP65_RELAY_LIST, null)?.let {
|
||||
if (it != "null") {
|
||||
println("Decoding NIP65 Relay List: $it")
|
||||
Event.fromJson(it) as AdvertisedRelayListEvent?
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.w(
|
||||
"LocalPreferences",
|
||||
"Error Decoding NIP65 Relay List ${getString(PrefKeys.LATEST_NIP65_RELAY_LIST, null)}",
|
||||
e,
|
||||
)
|
||||
null
|
||||
}
|
||||
|
||||
val pendingAttestations =
|
||||
try {
|
||||
getString(PrefKeys.PENDING_ATTESTATIONS, null)?.let {
|
||||
@@ -502,7 +569,7 @@ object LocalPreferences {
|
||||
if (e is CancellationException) throw e
|
||||
Log.w(
|
||||
"LocalPreferences",
|
||||
"Error Decoding Contact List ${getString(PrefKeys.LATEST_CONTACT_LIST, null)}",
|
||||
"Error Decoding Contact List ${getString(PrefKeys.PENDING_ATTESTATIONS, null)}",
|
||||
e,
|
||||
)
|
||||
null
|
||||
@@ -595,6 +662,8 @@ object LocalPreferences {
|
||||
hideBlockAlertDialog = hideBlockAlertDialog,
|
||||
hideNIP17WarningDialog = hideNIP17WarningDialog,
|
||||
backupContactList = latestContactList,
|
||||
backupNIP65RelayList = latestNip65RelayList,
|
||||
backupDMRelayList = latestDmRelayList,
|
||||
proxy = proxy,
|
||||
proxyPort = proxyPort,
|
||||
showSensitiveContent = MutableStateFlow(showSensitiveContent),
|
||||
|
||||
@@ -116,6 +116,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.combineTransform
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flattenMerge
|
||||
@@ -192,6 +193,8 @@ class Account(
|
||||
var hideBlockAlertDialog: Boolean = false,
|
||||
var hideNIP17WarningDialog: Boolean = false,
|
||||
var backupContactList: ContactListEvent? = null,
|
||||
var backupDMRelayList: ChatMessageRelayListEvent? = null,
|
||||
var backupNIP65RelayList: AdvertisedRelayListEvent? = null,
|
||||
var proxy: Proxy? = null,
|
||||
var proxyPort: Int = 9050,
|
||||
var showSensitiveContent: MutableStateFlow<Boolean?> = MutableStateFlow(null),
|
||||
@@ -269,7 +272,9 @@ class Account(
|
||||
if (mappedRelaySet.none { it.url == newUrl }) {
|
||||
mappedRelaySet = mappedRelaySet +
|
||||
RelaySetupInfo(
|
||||
newUrl, true, true,
|
||||
newUrl,
|
||||
true,
|
||||
true,
|
||||
setOf(
|
||||
FeedType.PRIVATE_DMS,
|
||||
),
|
||||
@@ -294,7 +299,9 @@ class Account(
|
||||
if (mappedRelaySet.none { it.url == newUrl }) {
|
||||
mappedRelaySet = mappedRelaySet +
|
||||
RelaySetupInfo(
|
||||
newUrl, true, false,
|
||||
newUrl,
|
||||
true,
|
||||
false,
|
||||
setOf(
|
||||
FeedType.SEARCH,
|
||||
),
|
||||
@@ -319,9 +326,14 @@ class Account(
|
||||
if (mappedRelaySet.none { it.url == newUrl }) {
|
||||
mappedRelaySet = mappedRelaySet +
|
||||
RelaySetupInfo(
|
||||
newUrl, true, true,
|
||||
newUrl,
|
||||
true,
|
||||
true,
|
||||
setOf(
|
||||
FeedType.FOLLOWS, FeedType.PUBLIC_CHATS, FeedType.GLOBAL, FeedType.PRIVATE_DMS,
|
||||
FeedType.FOLLOWS,
|
||||
FeedType.PUBLIC_CHATS,
|
||||
FeedType.GLOBAL,
|
||||
FeedType.PRIVATE_DMS,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -344,9 +356,14 @@ class Account(
|
||||
if (mappedRelaySet.none { it.url == newUrl }) {
|
||||
mappedRelaySet = mappedRelaySet +
|
||||
RelaySetupInfo(
|
||||
newUrl, true, true,
|
||||
newUrl,
|
||||
true,
|
||||
true,
|
||||
setOf(
|
||||
FeedType.FOLLOWS, FeedType.PUBLIC_CHATS, FeedType.GLOBAL, FeedType.PRIVATE_DMS,
|
||||
FeedType.FOLLOWS,
|
||||
FeedType.PUBLIC_CHATS,
|
||||
FeedType.GLOBAL,
|
||||
FeedType.PRIVATE_DMS,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -363,10 +380,14 @@ class Account(
|
||||
val write = nip65setup.type == AdvertisedRelayListEvent.AdvertisedRelayType.BOTH || nip65setup.type == AdvertisedRelayListEvent.AdvertisedRelayType.READ
|
||||
|
||||
RelaySetupInfo(
|
||||
relay.url, true, relay.write || write,
|
||||
relay.url,
|
||||
true,
|
||||
relay.write || write,
|
||||
relay.feedTypes +
|
||||
setOf(
|
||||
FeedType.FOLLOWS, FeedType.GLOBAL, FeedType.PUBLIC_CHATS,
|
||||
FeedType.FOLLOWS,
|
||||
FeedType.GLOBAL,
|
||||
FeedType.PUBLIC_CHATS,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
@@ -380,9 +401,12 @@ class Account(
|
||||
|
||||
mappedRelaySet = mappedRelaySet +
|
||||
RelaySetupInfo(
|
||||
newNip65Setup.relayUrl, true, write,
|
||||
newNip65Setup.relayUrl,
|
||||
true,
|
||||
write,
|
||||
setOf(
|
||||
FeedType.FOLLOWS, FeedType.PUBLIC_CHATS,
|
||||
FeedType.FOLLOWS,
|
||||
FeedType.PUBLIC_CHATS,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -2635,6 +2659,26 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDMRelayList(newDMRelayList: ChatMessageRelayListEvent?) {
|
||||
if (newDMRelayList == null || newDMRelayList.tags.isEmpty()) return
|
||||
|
||||
// Events might be different objects, we have to compare their ids.
|
||||
if (backupDMRelayList?.id != newDMRelayList.id) {
|
||||
backupDMRelayList = newDMRelayList
|
||||
saveable.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateNIP65RelayList(newNIP65RelayList: AdvertisedRelayListEvent?) {
|
||||
if (newNIP65RelayList == null || newNIP65RelayList.tags.isEmpty()) return
|
||||
|
||||
// Events might be different objects, we have to compare their ids.
|
||||
if (backupNIP65RelayList?.id != newNIP65RelayList.id) {
|
||||
backupNIP65RelayList = newNIP65RelayList
|
||||
saveable.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
// Takes a User's relay list and adds the types of feeds they are active for.
|
||||
fun activeRelays(): Array<RelaySetupInfo>? {
|
||||
val usersRelayList =
|
||||
@@ -2968,8 +3012,32 @@ class Account(
|
||||
suspend fun registerObservers() =
|
||||
withContext(Dispatchers.Main) {
|
||||
// saves contact list for the next time.
|
||||
userProfile().live().follows.observeForever {
|
||||
GlobalScope.launch(Dispatchers.IO) { updateContactListTo(userProfile().latestContactList) }
|
||||
scope.launch {
|
||||
Log.d("AccountRegisterObservers", "Kind 3 Collector Start")
|
||||
userProfile().flow().follows.stateFlow.collect {
|
||||
Log.d("AccountRegisterObservers", "Updating Kind 3 ${userProfile().toBestDisplayName()}")
|
||||
updateContactListTo(userProfile().latestContactList)
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
Log.d("AccountRegisterObservers", "NIP-17 Relay List Collector Start")
|
||||
getDMRelayListFlow().collect {
|
||||
Log.d("AccountRegisterObservers", "Updating DM Relay List for ${userProfile().toBestDisplayName()}")
|
||||
(it.note.event as? ChatMessageRelayListEvent)?.let {
|
||||
updateDMRelayList(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
Log.d("AccountRegisterObservers", "NIP-65 Relay List Collector Start")
|
||||
getNIP65RelayListFlow().collect {
|
||||
Log.d("AccountRegisterObservers", "Updating NIP-65 List for ${userProfile().toBestDisplayName()}")
|
||||
(it.note.event as? AdvertisedRelayListEvent)?.let {
|
||||
updateNIP65RelayList(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// imports transient blocks due to spam.
|
||||
@@ -2990,7 +3058,7 @@ class Account(
|
||||
}
|
||||
|
||||
init {
|
||||
Log.d("Init", "Account")
|
||||
Log.d("Account", "Init")
|
||||
backupContactList?.let {
|
||||
println("Loading saved contacts ${it.toJson()}")
|
||||
|
||||
@@ -2998,6 +3066,16 @@ class Account(
|
||||
GlobalScope.launch(Dispatchers.IO) { LocalCache.consume(it) }
|
||||
}
|
||||
}
|
||||
|
||||
backupDMRelayList?.let {
|
||||
println("Loading DM Relay List ${it.toJson()}")
|
||||
GlobalScope.launch(Dispatchers.IO) { LocalCache.verifyAndConsume(it, null) }
|
||||
}
|
||||
|
||||
backupNIP65RelayList?.let {
|
||||
println("Loading saved contacts ${it.toJson()}")
|
||||
GlobalScope.launch(Dispatchers.IO) { LocalCache.verifyAndConsume(it, null) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+80
-26
@@ -25,19 +25,35 @@ import com.vitorpamplona.amethyst.AccountInfo
|
||||
import com.vitorpamplona.amethyst.BuildConfig
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.ammolite.service.HttpClientManager
|
||||
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.events.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.events.RelayAuthEvent
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
class RegisterAccounts(
|
||||
private val accounts: List<AccountInfo>,
|
||||
) {
|
||||
private fun recursiveAuthCreation(
|
||||
val tag =
|
||||
if (BuildConfig.FLAVOR == "play") {
|
||||
"RegisterAccounts FirebaseMsgService"
|
||||
} else {
|
||||
"RegisterAccounts UnifiedPushService"
|
||||
}
|
||||
|
||||
private suspend fun signAllAuths(
|
||||
notificationToken: String,
|
||||
remainingTos: List<Pair<Account, String>>,
|
||||
output: MutableList<RelayAuthEvent>,
|
||||
@@ -48,12 +64,32 @@ class RegisterAccounts(
|
||||
return
|
||||
}
|
||||
|
||||
val next = remainingTos.first()
|
||||
coroutineScope {
|
||||
val jobs =
|
||||
remainingTos.map { accountRelayPair ->
|
||||
async {
|
||||
val result =
|
||||
withTimeoutOrNull(10000) {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
accountRelayPair.first.createAuthEvent(accountRelayPair.second, notificationToken) { result ->
|
||||
continuation.resume(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next.first.createAuthEvent(next.second, notificationToken) {
|
||||
output.add(it)
|
||||
recursiveAuthCreation(notificationToken, remainingTos.filter { next != it }, output, onReady)
|
||||
if (result != null) {
|
||||
output.add(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runs in parallel to avoid overcrowding Amber.
|
||||
withTimeoutOrNull(15000) {
|
||||
jobs.joinAll()
|
||||
}
|
||||
}
|
||||
|
||||
onReady(output)
|
||||
}
|
||||
|
||||
// creates proof that it controls all accounts
|
||||
@@ -63,25 +99,48 @@ class RegisterAccounts(
|
||||
onReady: (List<RelayAuthEvent>) -> Unit,
|
||||
) {
|
||||
val readyToSend =
|
||||
accounts.mapNotNull {
|
||||
val acc = LocalPreferences.loadCurrentAccountFromEncryptedStorage(it.npub)
|
||||
if (acc != null && acc.isWriteable()) {
|
||||
val readRelays =
|
||||
acc.userProfile().latestContactList?.relays() ?: acc.backupContactList?.relays()
|
||||
accounts
|
||||
.map {
|
||||
Log.d(tag, "Register Account ${it.npub}")
|
||||
|
||||
val relayToUse = readRelays?.firstNotNullOfOrNull { if (it.value.read) it.key else null }
|
||||
if (relayToUse != null) {
|
||||
Pair(acc, relayToUse)
|
||||
val acc = LocalPreferences.loadCurrentAccountFromEncryptedStorage(it.npub)
|
||||
|
||||
if (acc != null && acc.isWriteable()) {
|
||||
val nip65Read =
|
||||
(
|
||||
LocalCache
|
||||
.getAddressableNoteIfExists(
|
||||
AdvertisedRelayListEvent.createAddressTag(acc.userProfile().pubkeyHex),
|
||||
)?.event as? AdvertisedRelayListEvent
|
||||
)?.readRelays() ?: acc.backupNIP65RelayList?.readRelays() ?: emptyList<String>()
|
||||
|
||||
Log.d(tag, "Register Account ${acc.userProfile().toBestDisplayName()} NIP65 Reads ${nip65Read.joinToString(", ")}")
|
||||
|
||||
val nip17Read =
|
||||
(
|
||||
LocalCache
|
||||
.getAddressableNoteIfExists(
|
||||
ChatMessageRelayListEvent.createAddressTag(acc.userProfile().pubkeyHex),
|
||||
)?.event as? ChatMessageRelayListEvent
|
||||
)?.relays() ?: acc.backupDMRelayList?.relays() ?: emptyList<String>()
|
||||
|
||||
Log.d(tag, "Register Account ${acc.userProfile().toBestDisplayName()} NIP17 Reads ${nip17Read.joinToString(", ")}")
|
||||
|
||||
val kind3Relays = (acc.userProfile().latestContactList?.relays() ?: acc.backupContactList?.relays())
|
||||
val readKind3Relays = kind3Relays?.mapNotNull { if (it.value.read) it.key else null } ?: emptyList<String>()
|
||||
|
||||
Log.d(tag, "Register Account ${acc.userProfile().toBestDisplayName()} Kind3 Reads ${readKind3Relays.joinToString(", ")}")
|
||||
|
||||
(nip65Read + nip17Read + readKind3Relays).map {
|
||||
Pair(acc, it)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
emptyList<Pair<Account, String>>()
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}.flatten()
|
||||
|
||||
val listOfAuthEvents = mutableListOf<RelayAuthEvent>()
|
||||
recursiveAuthCreation(
|
||||
signAllAuths(
|
||||
notificationToken,
|
||||
readyToSend,
|
||||
listOfAuthEvents,
|
||||
@@ -101,7 +160,8 @@ class RegisterAccounts(
|
||||
val body = jsonObject.toRequestBody(mediaType)
|
||||
|
||||
val request =
|
||||
Request.Builder()
|
||||
Request
|
||||
.Builder()
|
||||
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
|
||||
.url("https://push.amethyst.social/register")
|
||||
.post(body)
|
||||
@@ -112,12 +172,6 @@ class RegisterAccounts(
|
||||
val isSucess = client.newCall(request).execute().use { it.isSuccessful }
|
||||
} catch (e: java.lang.Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
val tag =
|
||||
if (BuildConfig.FLAVOR == "play") {
|
||||
"FirebaseMsgService"
|
||||
} else {
|
||||
"UnifiedPushService"
|
||||
}
|
||||
Log.e(tag, "Unable to register with push server", e)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user