1
0
forked from BRT/arc

Update error reporting functions to include event ID and improve scroll detection in transaction scripts. Adjust network ping interval to 15 seconds.

This commit is contained in:
slava 2026-06-02 14:07:23 +05:00
parent 6d60cecef4
commit 569eefb3e8
4 changed files with 65 additions and 16 deletions

View File

@ -519,6 +519,14 @@ GetLastTransactionsScript::SearchResult GetLastTransactionsScript::searchOne(
Node node;
};
// Guard от «залипшей» прокрутки: если свайп не двигает список (банковское
// приложение перестало скроллиться — частый случай при сбоях UI/жестов),
// видимые транзакции не меняются. Тогда листать до maxScrolls бессмысленно,
// а при десятках целей это упирается в 10-минутный watchdog → "Interrupted".
// Детектируем по сигнатуре видимого набора: 2 одинаковых подряд = прогресса нет.
QString prevViewSig;
int stallScrolls = 0;
for (int scroll = 0; scroll <= maxScrolls; ++scroll) {
if (interrupted()) {
m_error = "Interrupted";
@ -1012,6 +1020,27 @@ GetLastTransactionsScript::SearchResult GetLastTransactionsScript::searchOne(
}
}
// Детект отсутствия прогресса прокрутки: сравниваем сигнатуру текущего
// видимого набора с предыдущей. Совпала — свайп ничего не сдвинул.
{
QStringList sigParts;
for (const VypiskaItem &it : items) {
sigParts << QString::number(it.amount, 'f', 2) + "|" + it.date + "|" + it.time;
}
const QString viewSig = sigParts.join(';') + "##" + groupDates.join(',');
if (!prevViewSig.isEmpty() && viewSig == prevViewSig) {
if (++stallScrolls >= 2) {
qWarning() << "[Dushanbe::GetLastTransactions] No scroll progress for"
<< stallScrolls << "swipes (list stuck) — aborting search for amount="
<< QString::number(searchAmount, 'f', 2);
break;
}
} else {
stallScrolls = 0;
}
prevViewSig = viewSig;
}
// Скроллим вниз — свайп внутри области транзакций
if (scroll < maxScrolls) {
if (interrupted()) { m_error = "Interrupted"; return SearchResult::NotFound; }

View File

@ -164,25 +164,45 @@ Node ScreenXmlParser::findPinCodeEditText() {
}
bool ScreenXmlParser::isHomeScreen() {
// Нижняя навигация (Главная + соседняя вкладка) присутствует на ВСЕХ
// основных вкладках — Главная, История, Переводы, MiniApps — поэтому одного
// таб-бара недостаточно: экран «История/Операции» иначе ошибочно
// принимается за home, и скрипт пытается открыть боковое меню/листать не с
// той страницы (см. «Side menu not detected»). Требуем дополнительно
// уникальный для главной якорь и явно отсекаем экран истории.
bool hasGlavnaya = false;
bool hasBottomNav = false;
bool hasHomeAnchor = false;
bool isHistoryScreen = false;
for (const Node &node : m_nodes) {
if (node.contentDesc.contains(QString::fromUtf8("Главная"))) {
const QString &d = node.contentDesc;
if (d.contains(QString::fromUtf8("Главная"))) {
hasGlavnaya = true;
}
// Нижняя навигация — любая из соседних вкладок указывает, что мы на
// основной странице приложения, а не на splash/PIN/диалоге.
if (node.contentDesc == QString::fromUtf8("Переводы") ||
node.contentDesc == QString::fromUtf8("Сервисы") ||
node.contentDesc == QString::fromUtf8("MiniApps") ||
node.contentDesc == QString::fromUtf8("История")) {
if (d == QString::fromUtf8("Переводы") ||
d == QString::fromUtf8("Сервисы") ||
d == QString::fromUtf8("MiniApps") ||
d == QString::fromUtf8("История")) {
hasBottomNav = true;
}
if (hasGlavnaya && hasBottomNav) {
return true;
// Уникальные для главного экрана элементы (нет на Истории/Переводах):
// заголовок «Мини приложения», плитки переводов «DC по номеру …»
// (в 3.2.38 — «DC (по номеру …)») и карточка баланса «TJS\n<дата>».
if (d == QString::fromUtf8("Мини приложения") ||
d.startsWith(QString::fromUtf8("DC по номеру")) ||
d.startsWith(QString::fromUtf8("DC (по номеру")) ||
(d.startsWith(QStringLiteral("TJS")) && d.contains('\n'))) {
hasHomeAnchor = true;
}
// Маркеры экрана «История/Операции» — на главном их нет.
if (d == QString::fromUtf8("Выписка") ||
d == QString::fromUtf8("Операции")) {
isHistoryScreen = true;
}
}
return false;
return hasGlavnaya && hasBottomNav && hasHomeAnchor && !isHistoryScreen;
}
std::optional<double> ScreenXmlParser::parseBalanceFromHomeScreenOpt() {

View File

@ -78,7 +78,7 @@ void EventHandler::start() {
if (e.sentToServer) continue;
EventDAO::updateEvent(e.id, EventStatus::Error, "App restarted");
EventDAO::markSentToServer(e.id);
sendTaskError(eventTypeToString(e.type), e.externalId, e.bankName, "App restarted, event was pending", {}, -1, e.transactionId, e.accountId);
sendTaskError(eventTypeToString(e.type), e.externalId, e.bankName, "App restarted, event was pending", {}, e.id, e.transactionId, e.accountId);
qDebug() << "[EventHandler] Closed stale event id:" << e.id;
}
}
@ -127,7 +127,7 @@ void EventHandler::onTasksReceived(const QJsonArray &tasks) {
if (eventType == EventType::FetchProfile) {
const QList<EventInfo> cancelled = EventDAO::cancelWaitingByMaterial(materialId, EventType::FetchProfile);
for (const EventInfo &c : cancelled) {
sendTaskError(eventTypeToString(c.type), c.externalId, c.bankName, "Cancelled: replaced by new task", {}, -1, c.transactionId, c.accountId);
sendTaskError(eventTypeToString(c.type), c.externalId, c.bankName, "Cancelled: replaced by new task", {}, c.id, c.transactionId, c.accountId);
EventDAO::markSentToServer(c.id);
}
}
@ -168,7 +168,7 @@ void EventHandler::onTasksReceived(const QJsonArray &tasks) {
if (!onlineDevices.contains(account.deviceId)) {
qWarning() << "[EventHandler] Device offline for material_id:" << materialId << "device:" << account.deviceId;
EventDAO::updateEvent(eventId, EventStatus::Error, "Device is offline");
sendTaskError(typeStr, materialId, bankName, "Device is offline", {}, -1, incomingTransactionId, account.id);
sendTaskError(typeStr, materialId, bankName, "Device is offline", {}, eventId, incomingTransactionId, account.id);
}
}
@ -417,7 +417,7 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
declined["status"] = QStringLiteral("declined");
declined["extra"] = QJsonObject();
declined["created_at"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
sendTaskResult(QStringLiteral("CREATE_TRANSACTION"), event.externalId, event.bankName, declined, -1);
sendTaskResult(QStringLiteral("CREATE_TRANSACTION"), event.externalId, event.bankName, declined, event.id);
}
EventDAO::markSentToServer(event.id);
@ -774,7 +774,7 @@ void EventHandler::startThreadForTask(const MaterialInfo &account, const EventIn
const QString logMsg = QString("[%1] bank=%2 material=%3: Script timeout")
.arg(eventTypeToString(event.type), event.bankName, event.externalId);
GeneralLogDAO::insert("CRITICAL", "EventHandler", logMsg);
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, timeoutMsg, {}, -1, event.transactionId, event.accountId);
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, timeoutMsg, {}, event.id, event.transactionId, event.accountId);
EventDAO::markSentToServer(event.id);
// Убиваем банковское приложение на устройстве после таймаута
@ -811,7 +811,7 @@ void EventHandler::processNextTask(const QString &deviceId) {
if (waitSecs > 5 * 60) {
qWarning() << "[EventHandler] Task expired after" << waitSecs << "s, event id:" << event.id;
EventDAO::updateEvent(event.id, EventStatus::Error, "Task expired: waited " + QString::number(waitSecs) + "s");
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, "Task expired: not started within 5 minutes", {}, -1, event.transactionId, event.accountId);
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, "Task expired: not started within 5 minutes", {}, event.id, event.transactionId, event.accountId);
continue;
}
@ -821,7 +821,7 @@ void EventHandler::processNextTask(const QString &deviceId) {
qWarning() << "[EventHandler] Bank profile disabled for" << account.appCode << "device:" << deviceId;
EventDAO::updateEvent(event.id, EventStatus::Error, "Bank profile is disabled");
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName,
"Bank profile is disabled: " + account.appCode, {}, -1, event.transactionId, event.accountId);
"Bank profile is disabled: " + account.appCode, {}, event.id, event.transactionId, event.accountId);
continue;
}

View File

@ -194,7 +194,7 @@ private:
bool m_unhealthyEmitted = false; // чтобы не спамить syncHealthChanged
static constexpr int kSyncFailureThreshold = 3;
static constexpr int kPeriodicSyncMs = 60 * 1000;
static constexpr int kPingProfileMs = 5 * 1000;
static constexpr int kPingProfileMs = 15 * 1000;
// Heartbeat (ping)
bool m_pingEnabled = false;