Route Android server links through channel selection and ordinary JOIN request

This commit is contained in:
evgeny committed 2026-10-28 11:05:23 +01:00
1 parent a9cc6785a9
commit f2d6561509
3 files changed
+29 -11

No files matched your search

@@ -3,7 +3,9 @@ package com.utun.chat.data
/* Kotlin-представление invite: wire/base64 и допустимость полей проверяет общий C-кодек через NativeLib.
* Декодирование не запускает подключение. Для join передавать исходный link: из preview ссылка не собирается.
* Массивы моделей принадлежат получателю, copy() их не клонирует; после создания использовать только для чтения. */
const val INVITE_LINK_VERSION: Int = 0x03
const val INVITE_LINK_VERSION: Int = 0x04
const val INVITE_KIND_CHANNEL: Int = 0
const val INVITE_KIND_SERVER: Int = 1
const val INVITE_PUBKEY_SIZE: Int = 32
const val INVITE_SNAME_MAX: Int = 64
@@ -64,16 +66,18 @@ data class InviteData(
val addrs: List<InviteAddr>,
val password: String = "",
val reality: InviteReality? = null,
val link: String = ""
val link: String = "",
val kind: Int = INVITE_KIND_CHANNEL
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is InviteData) return false
return channelId == other.channelId && joinKey == other.joinKey && pubkey.contentEquals(other.pubkey) &&
return kind == other.kind && channelId == other.channelId && joinKey == other.joinKey && pubkey.contentEquals(other.pubkey) &&
nodeId == other.nodeId && addrs == other.addrs && password == other.password && reality == other.reality && link == other.link
}
override fun hashCode(): Int {
var result = channelId.hashCode()
result = 31 * result + kind
result = result * 31 + joinKey.hashCode()
result = result * 31 + pubkey.contentHashCode()
result = result * 31 + nodeId.hashCode()
@@ -112,7 +116,7 @@ object InviteLink {
(0 until addresses.length()).map { i ->
val a = addresses.getJSONObject(i)
InviteAddr(a.getInt("family"), bytes(a.getString("ip")), a.getInt("port"), a.getInt("socket"), a.getInt("proto"))
}, String(bytes(value.getString("password")), Charsets.UTF_8), reality, link)
}, String(bytes(value.getString("password")), Charsets.UTF_8), reality, link, value.getInt("kind"))
}
/** Диагностическая подпись адреса/сокета; IPv6 выводится hex без сжатия, строка не является endpoint для подключения. */
@@ -111,6 +111,7 @@ object NativeLib {
private val joinSequence = java.util.concurrent.atomic.AtomicLong(1)
fun nextJoinRequest(): Long = joinSequence.getAndIncrement()
fun joinChannel(link: String, request: Long): Int = nativeJoinChannel(link, request)
fun addServer(channel: Long, link: String, request: Long): Int = nativeAddServer(channel, link, request)
fun decodeInvite(link: String): String? = nativeDecodeInvite(link)
fun deriveNodeId(pubkey: ByteArray): Long = nativeDeriveNodeId(pubkey)
fun connectChannel(channelId: String) { nativeConnectChannel(channelId) }
@@ -367,6 +368,7 @@ object NativeLib {
private external fun nativeConnectNode(address: String, port: Int, pubkeyHex: String)
private external fun nativeJoinChannel(link: String, request: Long): Int
private external fun nativeDecodeInvite(link: String): String?
private external fun nativeAddServer(channel: Long, link: String, request: Long): Int
private external fun nativeDeriveNodeId(pubkey: ByteArray): Long
private external fun nativeConnectChannel(channelId: String)
private external fun nativeConnectMemberNode(channelId: String, nodeId: Long)
@@ -154,9 +154,13 @@ class ChatViewModel : ViewModel() {
private var nextPmInviteId = 0L
data class LinkJoinState(val link: String, val channelId: Long, val nodeId: Long,
val pending: Boolean = true, val result: Int? = null, val request: Long = 0)
val pending: Boolean = true, val result: Int? = null, val request: Long = 0,
val serverAddition: Boolean = false)
private val _linkJoin = MutableStateFlow<LinkJoinState?>(null)
val linkJoin: StateFlow<LinkJoinState?> = _linkJoin
private val _serverLink = MutableStateFlow<String?>(null)
val serverLink: StateFlow<String?> = _serverLink
fun dismissServerLink() { _serverLink.value = null }
private val _joinedChannel = MutableStateFlow<Channel?>(null)
val joinedChannel: StateFlow<Channel?> = _joinedChannel
@@ -1369,24 +1373,32 @@ class ChatViewModel : ViewModel() {
LogManager.addLog("INFO", "INVITE", "PM group picker closed request=${state.requestId} pending=${state.pending}")
}
fun openInviteLink(link: String) {
fun openInviteLink(link: String, serverChannel: Long = 0) {
if (_linkJoin.value?.pending == true) return
val invite = InviteLink.decode(link)
if (invite == null) {
LogManager.addLog("ERROR", "JOIN", "invalid message invite link")
_linkJoin.value = LinkJoinState(link, 0, 0, pending = false, result = -1); return
}
val existing = _channels.value.find { it.id.toULongOrNull()?.toLong() == invite.channelId }
if (existing != null && NativeLib.isChannelMember(existing.id, NativeLib.getMyNodeId())) {
val serverAddition = invite.kind == com.utun.chat.data.INVITE_KIND_SERVER
if (serverAddition && serverChannel == 0L) { _serverLink.value = link; return }
val channelId = if (serverAddition) serverChannel else invite.channelId
if (serverAddition && !NativeLib.isChannelMember(channelId.toString(), NativeLib.getMyNodeId())) {
LogManager.addLog("WARN", "JOIN", "server addition requires own channel ch=$channelId")
_linkJoin.value = LinkJoinState(link, channelId, invite.nodeId, pending = false, result = -7, serverAddition = true); return
}
val existing = _channels.value.find { it.id.toULongOrNull()?.toLong() == channelId }
if (!serverAddition && existing != null && NativeLib.isChannelMember(existing.id, NativeLib.getMyNodeId())) {
_linkJoin.value = null; _joinedChannel.value = existing; return
}
if (repo == null || !NativeLib.isReady()) {
LogManager.addLog("ERROR", "JOIN", "core unavailable for message invite")
_linkJoin.value = LinkJoinState(link, invite.channelId, invite.nodeId, pending = false, result = -7); return
_linkJoin.value = LinkJoinState(link, channelId, invite.nodeId, pending = false, result = -7, serverAddition = serverAddition); return
}
val request = NativeLib.nextJoinRequest()
_linkJoin.value = LinkJoinState(link, invite.channelId, invite.nodeId, request = request)
val result = repo?.joinChannel(link, request) ?: -7
_serverLink.value = null
_linkJoin.value = LinkJoinState(link, channelId, invite.nodeId, request = request, serverAddition = serverAddition)
val result = if (serverAddition) NativeLib.addServer(channelId, link, request) else repo?.joinChannel(link, request) ?: -7
if (result != 0) _linkJoin.value = _linkJoin.value?.copy(pending = false, result = result)
}