diff --git a/src/chat/chat_sync.c b/src/chat/chat_sync.c index 0a84ef02..48c4e27c 100644 --- a/src/chat/chat_sync.c +++ b/src/chat/chat_sync.c @@ -47,10 +47,12 @@ struct chat_sync { void* info_req_timer; void* join_timer; void* sync_timer; + void* invite_send_timer; /* timer waiting for joiner to respond to CHANNEL_INVITE */ uint8_t initialized; uint8_t sync_scheduled; uint64_t pending_invite_ch_id; uint64_t pending_invite_node_id; + uint8_t pending_invite_is_inviter; /* 1 = inviter sends CHANNEL_INVITE on conn_up, 0 = joiner sends CHANNEL_INFO_REQ */ char pending_password[128]; /* пароль для CHANNEL_INFO_REQ */ /* Pending incoming invites (для ask/deny) */ uint64_t pending_inv_in_ch_id[4]; @@ -61,6 +63,7 @@ struct chat_sync { #define CS_ID "chat_sync" #define CS_INFO_REQ_TIMEOUT_MS 5000 #define CS_JOIN_TIMEOUT_MS 8000 +#define CS_INVITE_SEND_TIMEOUT_MS 3000 /* inviter waits for joiner to respond */ #define CS_SYNC_INTERVAL_MS 1000 static const char* cs_msg_name(uint8_t type) { @@ -84,8 +87,9 @@ static const char* cs_msg_name(uint8_t type) { } static void cs_cancel_proto_timers(struct chat_sync* cs) { - if (cs->info_req_timer) { uasync_cancel_timeout(cs->inst->ua, cs->info_req_timer); cs->info_req_timer = NULL; } - if (cs->join_timer) { uasync_cancel_timeout(cs->inst->ua, cs->join_timer); cs->join_timer = NULL; } + if (cs->info_req_timer) { uasync_cancel_timeout(cs->inst->ua, cs->info_req_timer); cs->info_req_timer = NULL; } + if (cs->join_timer) { uasync_cancel_timeout(cs->inst->ua, cs->join_timer); cs->join_timer = NULL; } + if (cs->invite_send_timer) { uasync_cancel_timeout(cs->inst->ua, cs->invite_send_timer); cs->invite_send_timer = NULL; } } /* ── Forward decl ── */ @@ -145,6 +149,9 @@ static void cs_handle_peer_remove(struct chat_sync* cs, uint64_t peer, const char* ch_id, const uint8_t* pl, size_t len); static void cs_handle_channel_invite(struct chat_sync* cs, uint64_t peer, const char* ch_id, const uint8_t* pl, size_t len); +static int cs_send_channel_invite(struct chat_sync* cs, struct UTUN_INSTANCE* inst, + const char* ch_id, uint64_t target_node_id, const char* ch_name, + const uint8_t* ch_x25519, const uint8_t* ch_ed, const uint8_t* ch_sig); /* ── Recv dispatcher ── */ @@ -226,6 +233,23 @@ static void cs_join_timeout_cb(void* arg) { chat_event_post(CHAT_EVT_CONNECT_RESULT, err, 20); cs->pending_invite_node_id = 0; cs->pending_invite_ch_id = 0; + cs->pending_invite_is_inviter = 0; +} + +static void cs_invite_send_timeout_cb(void* arg) { + struct chat_sync* cs = (struct chat_sync*)arg; + if (!cs || !cs->initialized) return; + cs->invite_send_timer = NULL; + uint64_t peer = cs->pending_invite_node_id; + DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INVITE timeout — joiner did not respond peer=%016llx ch=%llu", + CS_ID, (unsigned long long)peer, (unsigned long long)cs->pending_invite_ch_id); + uint8_t err[20]; memcpy(err, &peer, 8); + int r = CONN_EVENT_TIMEOUT; memcpy(err + 8, &r, 4); + memcpy(err + 12, &cs->pending_invite_ch_id, 8); + chat_event_post(CHAT_EVT_CONNECT_RESULT, err, 20); + cs->pending_invite_node_id = 0; + cs->pending_invite_ch_id = 0; + cs->pending_invite_is_inviter = 0; } /* ── helper: find active ETCP_CONN for node ── */ @@ -447,7 +471,21 @@ static void cs_on_conn_up(struct ETCP_CONN* conn, int event, void* arg) { (void) DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: invite node_id MISMATCH: invite=0x%016llx ETCP_peer=0x%016llx — invite is STALE!", CS_ID, (unsigned long long)g_cs->pending_invite_node_id, (unsigned long long)peer); g_cs->pending_invite_node_id = peer; - cs_start_channel_join(g_cs, peer); + if (g_cs->pending_invite_is_inviter) { + /* we are the inviter — send CHANNEL_INVITE to the joiner */ + char ch_id[64]; snprintf(ch_id, sizeof(ch_id), "%llu", + (unsigned long long)g_cs->pending_invite_ch_id); + char ch_name[128]; uint8_t ch_x2[32], ch_ed2[32], ch_sig[64]; + if (topo_node_sqlite_channel_get(g_cs->inst->topo_sqlite_db, + ch_id, ch_name, (int)sizeof(ch_name), NULL, ch_x2, ch_ed2, ch_sig) == 0) { + cs_send_channel_invite(g_cs, g_cs->inst, ch_id, peer, ch_name, ch_x2, ch_ed2, ch_sig); + if (g_cs->invite_send_timer) { uasync_cancel_timeout(g_cs->inst->ua, g_cs->invite_send_timer); } + g_cs->invite_send_timer = uasync_set_timeout(g_cs->inst->ua, + CS_INVITE_SEND_TIMEOUT_MS * 10, g_cs, cs_invite_send_timeout_cb, "cs_invite_send"); + } + } else { + cs_start_channel_join(g_cs, peer); + } cs_on_peer_status_changed(peer, 1); return; } @@ -590,6 +628,7 @@ void chat_sync_destroy(struct UTUN_INSTANCE* inst) { if (!cs || !inst) return; DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: destroy", CS_ID); if (cs->sync_timer) { uasync_cancel_timeout(inst->ua, cs->sync_timer); cs->sync_timer = NULL; } + if (cs->invite_send_timer) { uasync_cancel_timeout(inst->ua, cs->invite_send_timer); cs->invite_send_timer = NULL; } if (cs->sync_scheduled) { cs->sync_scheduled = 0; cs_flush_sync(cs); } member_sync_destroy(inst); cs->initialized = 0; g_cs = NULL; @@ -855,6 +894,9 @@ static void cs_handle_channel_info_req(struct chat_sync* cs, uint64_t peer, const char* ch_id, const uint8_t* pl, size_t len) { DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: INFO_REQ ch=%s from=%016llx len=%zu", CS_ID, ch_id, (unsigned long long)peer, len); + /* joiner responded to our CHANNEL_INVITE — cancel invite send timer */ + if (cs->invite_send_timer) { uasync_cancel_timeout(cs->inst->ua, cs->invite_send_timer); cs->invite_send_timer = NULL; } + /* Parse password from request (v2 format: type + pass_len + optional_password) */ char req_password[128] = ""; if (len >= 1) { @@ -1241,6 +1283,19 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: JOIN starting member_sync with joiner node=0x%016llx ch=%s done_cb=%s", CS_ID, (unsigned long long)node_id, ch_id, sa ? "yes" : "no"); + /* if we're the inviter — joiner joined successfully, notify GUI */ + if (cs->pending_invite_ch_id != 0 && cs->pending_invite_is_inviter) { + uint64_t ch_id_num = strtoull(ch_id, NULL, 10); + uint8_t evt[20]; memcpy(evt, &node_id, 8); int r = 0; memcpy(evt + 8, &r, 4); + memcpy(evt + 12, &ch_id_num, 8); + chat_event_post(CHAT_EVT_CONNECT_RESULT, evt, 20); + DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: JOIN posted CHAT_EVT_CONNECT_RESULT to GUI node=0x%016llx ch=%s", + CS_ID, (unsigned long long)node_id, ch_id); + cs->pending_invite_node_id = 0; cs->pending_invite_ch_id = 0; + cs->pending_invite_is_inviter = 0; + if (cs->invite_send_timer) { uasync_cancel_timeout(cs->inst->ua, cs->invite_send_timer); cs->invite_send_timer = NULL; } + } + DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: JOIN accepted node=0x%016llx ch=%s addrs=%d", CS_ID, (unsigned long long)node_id, ch_id, addr_cnt); } @@ -1587,6 +1642,50 @@ static void cs_handle_channel_invite(struct chat_sync* cs, uint64_t peer, } } +/* ─── helper: build and send CS_MSG_CHANNEL_INVITE to peer ─── */ + +static int cs_send_channel_invite(struct chat_sync* cs, struct UTUN_INSTANCE* inst, + const char* ch_id, uint64_t target_node_id, + const char* ch_name, const uint8_t* ch_x25519, + const uint8_t* ch_ed, const uint8_t* ch_sig) { + uint64_t owner = inst->node_id; + uint8_t join_sig[64] = {0}; uint64_t join_ts = 0; + { sqlite3* vdb = inst->topo_sqlite_db; + topo_node_sqlite_member_get_join(vdb, ch_id, inst->node_id, join_sig, &join_ts); } + uint8_t upd_sig[64] = {0}; uint64_t upd_ts = (uint64_t)ntp_time_get_seconds(inst); + { + char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", inst->name[0] ? inst->name : ""); + uint8_t umsg[256]; size_t ulen = 0; + memcpy(umsg + ulen, join_sig, 64); ulen += 64; + memcpy(umsg + ulen, &upd_ts, 8); ulen += 8; + size_t jl = strlen(juser); memcpy(umsg + ulen, juser, jl); umsg[ulen + jl] = '\0'; ulen += jl + 1; + cs_ed25519_sign(inst->my_ed25519_privkey, umsg, ulen, upd_sig); + } + uint8_t buf[1024]; size_t boff = 0; + buf[boff++] = CS_MSG_CHANNEL_INVITE; + uint8_t cnl = (uint8_t)strlen(ch_name); + buf[boff++] = cnl; memcpy(buf + boff, ch_name, cnl); boff += cnl; + memcpy(buf + boff, &owner, 8); boff += 8; + memcpy(buf + boff, ch_x25519, 32); boff += 32; + memcpy(buf + boff, ch_ed, 32); boff += 32; + memcpy(buf + boff, ch_sig, 64); boff += 64; + uint8_t inv_flags = (join_sig[0] || join_ts) ? PEERS_FLAG_HAS_JOIN : 0; + buf[boff++] = inv_flags; + if (inv_flags & PEERS_FLAG_HAS_JOIN) { + memcpy(buf + boff, join_sig, 64); boff += 64; + memcpy(buf + boff, &join_ts, 8); boff += 8; + } + memcpy(buf + boff, upd_sig, 64); boff += 64; + memcpy(buf + boff, &upd_ts, 8); boff += 8; + char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", inst->name[0] ? inst->name : ""); + uint8_t il = (uint8_t)strlen(juser); + buf[boff++] = il; memcpy(buf + boff, juser, il); boff += il; + int rc = cs_send(cs, ch_id, target_node_id, buf, boff); + DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: sent CHANNEL_INVITE to 0x%016llx ch=%s rc=%d", + CS_ID, (unsigned long long)target_node_id, ch_id, rc); + return rc; +} + /* ─── chat_sync_invite_to_channel: пригласить узел в наш канал ─── */ void chat_sync_invite_to_channel(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t target_node_id) { @@ -1644,46 +1743,145 @@ void chat_sync_invite_to_channel(struct UTUN_INSTANCE* inst, const char* ch_id, Use a separate flag. */ g_cs->pending_invite_ch_id = channel_id; g_cs->pending_invite_node_id = target_node_id; + g_cs->pending_invite_is_inviter = 1; return; } - /* Already connected — send invite now */ - uint8_t join_sig[64] = {0}; uint64_t join_ts = 0; - { sqlite3* vdb = inst->topo_sqlite_db; - topo_node_sqlite_member_get_join(vdb, ch_id, inst->node_id, join_sig, &join_ts); } - uint8_t upd_sig[64] = {0}; uint64_t upd_ts = (uint64_t)ntp_time_get_seconds(inst); - { - char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", inst->name[0] ? inst->name : ""); - uint8_t umsg[256]; size_t ulen = 0; - memcpy(umsg + ulen, join_sig, 64); ulen += 64; - memcpy(umsg + ulen, &upd_ts, 8); ulen += 8; - size_t jl = strlen(juser); memcpy(umsg + ulen, juser, jl); umsg[ulen + jl] = '\0'; ulen += jl + 1; - cs_ed25519_sign(inst->my_ed25519_privkey, umsg, ulen, upd_sig); + /* Already connected — send invite now, wait for joiner to respond */ + cs_send_channel_invite(g_cs, inst, ch_id, target_node_id, ch_name, ch_x25519, ch_ed, ch_sig); + g_cs->pending_invite_ch_id = channel_id; + g_cs->pending_invite_node_id = target_node_id; + g_cs->pending_invite_is_inviter = 1; + if (g_cs->invite_send_timer) { uasync_cancel_timeout(inst->ua, g_cs->invite_send_timer); } + g_cs->invite_send_timer = uasync_set_timeout(inst->ua, + CS_INVITE_SEND_TIMEOUT_MS * 10, g_cs, cs_invite_send_timeout_cb, "cs_invite_send"); +} + +/* ─── chat_sync_invite_to_channel_with_addrs: как invite_to_channel, но с адресами ─── */ + +struct cs_invite_wrap { + uint64_t channel_id; + uint64_t target_node_id; + uint8_t pubkey[32]; + int addr_count; + int addrs_len; + uint8_t addrs_data[]; /* flexible array */ +}; + +static void cs_invite_trampoline(void* arg) { + struct cs_invite_wrap* w = (struct cs_invite_wrap*)arg; + if (!g_cs || !g_cs->inst) { u_free(w); return; } + struct UTUN_INSTANCE* inst = g_cs->inst; + uint64_t channel_id = w->channel_id; + uint64_t target_node_id = w->target_node_id; + + if (channel_id == 0 || target_node_id == 0 || target_node_id == inst->node_id) { + DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: invite_with_addrs invalid params ch=%llu target=0x%016llx", + CS_ID, (unsigned long long)channel_id, (unsigned long long)target_node_id); + u_free(w); return; } - uint8_t buf[1024]; size_t boff = 0; - buf[boff++] = CS_MSG_CHANNEL_INVITE; - uint8_t cnl = (uint8_t)strlen(ch_name); - buf[boff++] = cnl; memcpy(buf + boff, ch_name, cnl); boff += cnl; - memcpy(buf + boff, &owner, 8); boff += 8; - memcpy(buf + boff, ch_x25519, 32); boff += 32; - memcpy(buf + boff, ch_ed, 32); boff += 32; - memcpy(buf + boff, ch_sig, 64); boff += 64; - uint8_t inv_flags = (join_sig[0] || join_ts) ? PEERS_FLAG_HAS_JOIN : 0; - buf[boff++] = inv_flags; - if (inv_flags & PEERS_FLAG_HAS_JOIN) { - memcpy(buf + boff, join_sig, 64); boff += 64; - memcpy(buf + boff, &join_ts, 8); boff += 8; + char ch_id[64]; snprintf(ch_id, sizeof(ch_id), "%llu", (unsigned long long)channel_id); + + /* Check if already connected */ + struct ETCP_CONN* conn = cs_find_conn_for_node(inst, target_node_id); + if (conn && conn->links_up && conn->initialized) { + char ch_name[128]; uint8_t ch_x2[32], ch_ed2[32], ch_sig[64]; + if (topo_node_sqlite_channel_get(inst->topo_sqlite_db, + ch_id, ch_name, (int)sizeof(ch_name), NULL, ch_x2, ch_ed2, ch_sig) == 0) { + cs_send_channel_invite(g_cs, inst, ch_id, target_node_id, ch_name, ch_x2, ch_ed2, ch_sig); + g_cs->pending_invite_ch_id = channel_id; + g_cs->pending_invite_node_id = target_node_id; + g_cs->pending_invite_is_inviter = 1; + if (g_cs->invite_send_timer) { uasync_cancel_timeout(inst->ua, g_cs->invite_send_timer); } + g_cs->invite_send_timer = uasync_set_timeout(inst->ua, + CS_INVITE_SEND_TIMEOUT_MS * 10, g_cs, cs_invite_send_timeout_cb, "cs_invite_send"); + } + u_free(w); return; + } + + /* Not connected — build TOPO_NODE with addresses and connect */ + if (!inst->topo_groups) { + DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: invite_with_addrs — no topo_groups, cannot connect", CS_ID); + u_free(w); return; + } + + struct TOPO_NODE* ni = u_calloc(1, sizeof(*ni)); + if (!ni) { u_free(w); return; } + ni->node_id = target_node_id; memcpy(ni->public_key, w->pubkey, 32); + + const uint8_t* ap = w->addrs_data; + const uint8_t* ap_end = ap + w->addrs_len; + for (int i = 0; i < w->addr_count && ap + 1 <= ap_end; i++) { + uint8_t family = *ap++; uint8_t sock_id = *ap++; uint8_t proto = *ap++; + if (family == 4 && ap + 6 <= ap_end) { + struct TOPO_ADDR4* a4 = u_calloc(1, sizeof(*a4)); + if (!a4) break; + memcpy(a4->addr, ap, 4); ap += 4; + a4->port = ((uint16_t)ap[0] << 8) | ap[1]; ap += 2; + a4->type = TOPO_ADDR_NAT; a4->protocol = proto; + a4->next = ni->v4_addrs; ni->v4_addrs = a4; + } else if (family == 6 && ap + 18 <= ap_end) { + struct TOPO_ADDR6* a6 = u_calloc(1, sizeof(*a6)); + if (!a6) break; + memcpy(a6->addr, ap, 16); ap += 16; + a6->port = ((uint16_t)ap[0] << 8) | ap[1]; ap += 2; + a6->type = TOPO_ADDR_NAT; a6->protocol = proto; + a6->next = ni->v6_addrs; ni->v6_addrs = a6; + } else { break; } + } + + if (!ni->v4_addrs && !ni->v6_addrs) { + DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: invite_with_addrs no valid addrs for node=0x%016llx", + CS_ID, (unsigned long long)target_node_id); + u_free(ni); u_free(w); return; + } + + g_cs->pending_invite_ch_id = channel_id; + g_cs->pending_invite_node_id = target_node_id; + g_cs->pending_invite_is_inviter = 1; + + int r = conn_mgr_open_invite(inst, channel_id, ni, target_node_id, cs_invite_conn_cb, NULL, NULL); + if (r < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: invite_with_addrs conn_mgr_open_invite failed rc=%d", CS_ID, r); + g_cs->pending_invite_node_id = 0; g_cs->pending_invite_ch_id = 0; + g_cs->pending_invite_is_inviter = 0; + } + + while (ni->v4_addrs) { struct TOPO_ADDR4* n = ni->v4_addrs->next; u_free(ni->v4_addrs); ni->v4_addrs = n; } + while (ni->v6_addrs) { struct TOPO_ADDR6* n = ni->v6_addrs->next; u_free(ni->v6_addrs); ni->v6_addrs = n; } + u_free(ni); + u_free(w); +} + +void chat_sync_invite_to_channel_with_addrs( + struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t target_node_id, + const uint8_t* pubkey_bin, const uint8_t* addrs_data, int addr_count, + int addrs_data_len) +{ + if (!g_cs || !inst || !ch_id || !ch_id[0] || !pubkey_bin || !addrs_data || addr_count <= 0 || addrs_data_len <= 0) { + DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: invite_with_addrs invalid args", CS_ID); + return; + } + uint64_t channel_id = strtoull(ch_id, NULL, 10); + if (channel_id == 0 || target_node_id == 0 || target_node_id == inst->node_id) { + DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: invite_with_addrs invalid params ch=%s target=0x%016llx", + CS_ID, ch_id, (unsigned long long)target_node_id); + return; } - memcpy(buf + boff, upd_sig, 64); boff += 64; - memcpy(buf + boff, &upd_ts, 8); boff += 8; - char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", inst->name[0] ? inst->name : ""); - uint8_t il = (uint8_t)strlen(juser); - buf[boff++] = il; memcpy(buf + boff, juser, il); boff += il; - cs_send(g_cs, ch_id, target_node_id, buf, boff); - DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: sent CHANNEL_INVITE to 0x%016llx ch=%s", - CS_ID, (unsigned long long)target_node_id, ch_id); + struct cs_invite_wrap* w = u_calloc(1, sizeof(*w) + (size_t)addrs_data_len); + if (!w) return; + w->channel_id = channel_id; + w->target_node_id = target_node_id; + memcpy(w->pubkey, pubkey_bin, 32); + w->addr_count = addr_count; + w->addrs_len = addrs_data_len; + memcpy(w->addrs_data, addrs_data, (size_t)addrs_data_len); + + uasync_post(inst->ua, cs_invite_trampoline, w); + DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: invite_with_addrs posted to uasync ch=%s target=0x%016llx addrs=%d", + CS_ID, ch_id, (unsigned long long)target_node_id, addr_count); } /* ─── accept/deny incoming invite ─── */ diff --git a/src/chat/chat_sync.h b/src/chat/chat_sync.h index 70c21f8f..21912a35 100644 --- a/src/chat/chat_sync.h +++ b/src/chat/chat_sync.h @@ -81,6 +81,17 @@ void chat_sync_connect_from_invite(uint64_t channel_id, uint64_t node_id, int addrs_data_len, const char* password); +/* Пригласить target_node_id в канал ch_id с адресами из invite-ссылки (inviter → joiner). + Принимает pubkey и адреса целевого узла. Если соединения ещё нет — строит TOPO_NODE + с адресами, запускает conn_mgr для подключения, и по conn_up отправляет CS_MSG_CHANNEL_INVITE. + Если соединение уже есть — отправляет CHANNEL_INVITE сразу. + Копирует pubkey_bin и addrs_data (вызывающий может освободить после возврата). + Вызов из любого потока — внутри постит в uasync. */ +void chat_sync_invite_to_channel_with_addrs( + struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t target_node_id, + const uint8_t* pubkey_bin, const uint8_t* addrs_data, int addr_count, + int addrs_data_len); + /* Пригласить target_node_id в канал ch_id (inviter → joiner). Отправляет CS_MSG_CHANNEL_INVITE с полной информацией о канале и своих адресах */ void chat_sync_invite_to_channel(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t target_node_id); diff --git a/tools/chatgui/src/inviteby.cpp b/tools/chatgui/src/inviteby.cpp index 50d01320..cb796d8a 100644 --- a/tools/chatgui/src/inviteby.cpp +++ b/tools/chatgui/src/inviteby.cpp @@ -179,7 +179,10 @@ void InviteByLinkDialog::onSendInvite() { addrsBuf.append(static_cast(a.port & 0xFF)); } - chat_sync_invite_to_channel(chat_core_get_inst(), m_channelId.toUtf8().constData(), targetNodeId); + chat_sync_invite_to_channel_with_addrs( + chat_core_get_inst(), m_channelId.toUtf8().constData(), targetNodeId, + (const uint8_t*)d.pubkey.constData(), + (const uint8_t*)addrsBuf.constData(), d.addrs.size(), addrsBuf.size()); setInviteStatus(QString::fromUtf8("Приглашение отправлено. OK")); QTimer::singleShot(2000, this, [this]() {