SHA256
Исправить восстановление DM после перелогина
This commit is contained in:
+2
-1
@@ -51,6 +51,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(Net_CreateAuthSession__Handler.class);
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
private static final long CLOSE_AFTER_ERROR_DELAY_MS = 75L;
|
||||
private static final long SIGNED_DM_BACKLOG_AFTER_AUTH_DELAY_MS = 250L;
|
||||
|
||||
public static final long ALLOWED_SKEW_MS = 30_000L;
|
||||
|
||||
@@ -423,7 +424,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
|
||||
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER);
|
||||
|
||||
ActiveConnectionsRegistry.getInstance().register(ctx);
|
||||
SignedMessagesRealtime.dispatchPendingForSession(ctx);
|
||||
SignedMessagesRealtime.dispatchPendingForSessionAsync(ctx, SIGNED_DM_BACKLOG_AFTER_AUTH_DELAY_MS);
|
||||
|
||||
// --- формируем ответ ---
|
||||
Net_CreateAuthSession_Response resp = new Net_CreateAuthSession_Response();
|
||||
|
||||
+2
-1
@@ -44,6 +44,7 @@ public class Net_SessionLogin_Handler implements JsonMessageHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(Net_SessionLogin_Handler.class);
|
||||
|
||||
private static final long ALLOWED_SKEW_MS = 30_000L;
|
||||
private static final long SIGNED_DM_BACKLOG_AFTER_AUTH_DELAY_MS = 250L;
|
||||
|
||||
@Override
|
||||
public Net_Response handle(Net_Request baseReq, ConnectionContext ctx) throws Exception {
|
||||
@@ -301,7 +302,7 @@ public class Net_SessionLogin_Handler implements JsonMessageHandler {
|
||||
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER);
|
||||
|
||||
ActiveConnectionsRegistry.getInstance().register(ctx);
|
||||
SignedMessagesRealtime.dispatchPendingForSession(ctx);
|
||||
SignedMessagesRealtime.dispatchPendingForSessionAsync(ctx, SIGNED_DM_BACKLOG_AFTER_AUTH_DELAY_MS);
|
||||
|
||||
// ответ
|
||||
Net_SessionLogin_Response resp = new Net_SessionLogin_Response();
|
||||
|
||||
+24
@@ -18,10 +18,22 @@ import java.util.Base64;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class SignedMessagesRealtime {
|
||||
private static final Logger log = LoggerFactory.getLogger(SignedMessagesRealtime.class);
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
private static final ScheduledExecutorService BACKLOG_EXECUTOR = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread thread = new Thread(r, "signed-messages-backlog");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
}
|
||||
});
|
||||
|
||||
private SignedMessagesRealtime() {}
|
||||
|
||||
@@ -63,7 +75,19 @@ public final class SignedMessagesRealtime {
|
||||
String login = ctx.getLogin();
|
||||
String sessionId = ctx.getSessionId();
|
||||
if (isBlank(login) || isBlank(sessionId)) return;
|
||||
dispatchPendingForSession(login, sessionId);
|
||||
}
|
||||
|
||||
public static void dispatchPendingForSessionAsync(ConnectionContext ctx, long delayMs) {
|
||||
if (ctx == null || !ctx.isAuthenticatedUser()) return;
|
||||
String login = safeLower(ctx.getLogin());
|
||||
String sessionId = String.valueOf(ctx.getSessionId() == null ? "" : ctx.getSessionId()).trim();
|
||||
if (isBlank(login) || isBlank(sessionId)) return;
|
||||
long safeDelayMs = Math.max(0L, delayMs);
|
||||
BACKLOG_EXECUTOR.schedule(() -> dispatchPendingForSession(login, sessionId), safeDelayMs, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private static void dispatchPendingForSession(String login, String sessionId) {
|
||||
try {
|
||||
List<SignedMessageV2Entry> pending = SignedMessagesV2DAO.getInstance()
|
||||
.listPendingForSession(login, sessionId);
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
client.version=1.2.301
|
||||
server.version=1.2.280
|
||||
client.version=1.2.302
|
||||
server.version=1.2.281
|
||||
|
||||
+73
-16
@@ -135,6 +135,8 @@ const toolbarEl = document.getElementById('toolbar-slot');
|
||||
const appShellEl = document.querySelector('.app-shell');
|
||||
|
||||
const CONNECTION_CHECK_INTERVAL_MS = 20 * 1000;
|
||||
const SIGNED_DM_DECRYPT_CONTEXT_WAIT_MS = 2000;
|
||||
const SIGNED_DM_DECRYPT_CONTEXT_POLL_MS = 50;
|
||||
const UI_VERSION_PERIODIC_CHECK_MS = 5 * 60 * 1000;
|
||||
const CURRENT_BUILD_HASH = String(window.__SHINE_BUILD_HASH__ || '').trim();
|
||||
const UI_BUILD_HASH_PATTERN = /window\.__SHINE_BUILD_HASH__\s*=\s*'([^']+)'/;
|
||||
@@ -641,6 +643,44 @@ function wsIsOpen() {
|
||||
return !!(ws && ws.readyState === WebSocket.OPEN);
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => {
|
||||
window.setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForSignedDmDecryptContext(timeoutMs = SIGNED_DM_DECRYPT_CONTEXT_WAIT_MS) {
|
||||
const startedAtMs = Date.now();
|
||||
while ((Date.now() - startedAtMs) < timeoutMs) {
|
||||
const login = String(state.session?.login || '').trim();
|
||||
const storagePwd = String(state.session?.storagePwdInMemory || '').trim();
|
||||
if (login && storagePwd) {
|
||||
return { login, storagePwd };
|
||||
}
|
||||
await sleep(SIGNED_DM_DECRYPT_CONTEXT_POLL_MS);
|
||||
}
|
||||
return {
|
||||
login: String(state.session?.login || '').trim(),
|
||||
storagePwd: String(state.session?.storagePwdInMemory || '').trim(),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveSignedDmChatId({ parsed, login }) {
|
||||
const parsedBlock = parsed || {};
|
||||
const messageType = Number(parsedBlock.messageType || 0);
|
||||
const fromLogin = String(parsedBlock.fromLogin || '').trim();
|
||||
const toLogin = String(parsedBlock.toLogin || '').trim();
|
||||
const currentLogin = String(login || '').trim().toLowerCase();
|
||||
|
||||
if (messageType === 1) return normalizeDmChatId(fromLogin);
|
||||
if (messageType === 2) return normalizeDmChatId(toLogin);
|
||||
|
||||
if (currentLogin && currentLogin === fromLogin.toLowerCase()) {
|
||||
return normalizeDmChatId(toLogin);
|
||||
}
|
||||
return normalizeDmChatId(fromLogin);
|
||||
}
|
||||
|
||||
async function ensureSessionAfterWsReconnect() {
|
||||
if (!state.session.isAuthorized) return true;
|
||||
if (wsSessionRestoreInFlight) return wsSessionRestoreInFlight;
|
||||
@@ -1020,29 +1060,44 @@ async function init() {
|
||||
const fromLogin = parsed.fromLogin || '';
|
||||
const toLogin = parsed.toLogin || '';
|
||||
const messageType = Number(parsed.messageType || 0);
|
||||
const currentLogin = String(state.session?.login || '').trim().toLowerCase();
|
||||
const chatPeerLogin = currentLogin && currentLogin === String(fromLogin || '').trim().toLowerCase()
|
||||
? toLogin
|
||||
: fromLogin;
|
||||
const chatId = normalizeDmChatId(chatPeerLogin);
|
||||
let shouldAckSessionDelivery = true;
|
||||
let text = '';
|
||||
let sessionLoginForRouting = String(state.session?.login || '').trim();
|
||||
if (messageType === 1 || messageType === 2) {
|
||||
const decryptContext = await waitForSignedDmDecryptContext();
|
||||
if (!decryptContext.login || !decryptContext.storagePwd) {
|
||||
shouldAckSessionDelivery = false;
|
||||
addAppLogEntry({
|
||||
level: 'warn',
|
||||
source: 'signed-dm',
|
||||
message: 'Отложена обработка DM: decrypt-контекст сессии ещё не готов',
|
||||
details: { messageKey, baseKey: parsed.baseKey, messageType },
|
||||
});
|
||||
return;
|
||||
}
|
||||
sessionLoginForRouting = decryptContext.login;
|
||||
try {
|
||||
const decrypted = await authService.decryptSignedMessageContent({
|
||||
parsed,
|
||||
login: state.session?.login || '',
|
||||
storagePwd: state.session?.storagePwdInMemory || '',
|
||||
login: decryptContext.login,
|
||||
storagePwd: decryptContext.storagePwd,
|
||||
});
|
||||
text = String(decrypted?.text || '');
|
||||
} catch (error) {
|
||||
shouldAckSessionDelivery = false;
|
||||
addAppLogEntry({
|
||||
level: 'warn',
|
||||
source: 'signed-dm',
|
||||
message: 'Не удалось расшифровать DM',
|
||||
details: { messageKey, baseKey: parsed.baseKey, error: error?.message || 'unknown' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
const chatId = resolveSignedDmChatId({
|
||||
parsed,
|
||||
login: sessionLoginForRouting,
|
||||
});
|
||||
|
||||
let shouldRefreshToolbarUnread = false;
|
||||
|
||||
@@ -1125,15 +1180,17 @@ async function init() {
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await authService.ackSessionDelivery(messageKey);
|
||||
} catch (error) {
|
||||
addAppLogEntry({
|
||||
level: 'warn',
|
||||
source: 'signed-dm',
|
||||
message: 'Не удалось отправить ACK доставки по сессии',
|
||||
details: { messageKey, error: error?.message || 'unknown' },
|
||||
});
|
||||
if (shouldAckSessionDelivery) {
|
||||
try {
|
||||
await authService.ackSessionDelivery(messageKey);
|
||||
} catch (error) {
|
||||
addAppLogEntry({
|
||||
level: 'warn',
|
||||
source: 'signed-dm',
|
||||
message: 'Не удалось отправить ACK доставки по сессии',
|
||||
details: { messageKey, error: error?.message || 'unknown' },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const pageId = getRoute().pageId || '';
|
||||
|
||||
@@ -252,6 +252,8 @@ function createInitialState({ withStoredSession = true } = {}) {
|
||||
appLog: [],
|
||||
incomingDedup: {},
|
||||
knownMessageKeys: {},
|
||||
pendingOutgoingReadByBaseKey: {},
|
||||
pendingIncomingReadByBaseKey: {},
|
||||
outgoingTempSeq: 1,
|
||||
notificationsTab: 'replies',
|
||||
pageLabelCollapsed: false,
|
||||
@@ -558,32 +560,46 @@ export function markOutgoingSent(tempId, { messageKey = '', baseKey = '' } = {})
|
||||
export function markOutgoingReadByBaseKey(baseKey) {
|
||||
if (!baseKey) return;
|
||||
const keys = Object.keys(state.chats || {});
|
||||
let matched = false;
|
||||
keys.forEach((chatId) => {
|
||||
const list = getChatMessages(chatId);
|
||||
list.forEach((row) => {
|
||||
if (row?.from !== 'out') return;
|
||||
if (row.baseKey === baseKey) {
|
||||
matched = true;
|
||||
row.secondTick = true;
|
||||
persistMessageRecord(chatId, row);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (matched) {
|
||||
delete state.pendingOutgoingReadByBaseKey[baseKey];
|
||||
} else {
|
||||
state.pendingOutgoingReadByBaseKey[baseKey] = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function markIncomingReadByBaseKey(baseKey) {
|
||||
if (!baseKey) return;
|
||||
const keys = Object.keys(state.chats || {});
|
||||
let matched = false;
|
||||
keys.forEach((chatId) => {
|
||||
const list = getChatMessages(chatId);
|
||||
list.forEach((row) => {
|
||||
if (row?.from !== 'in') return;
|
||||
if (row.baseKey === baseKey) {
|
||||
matched = true;
|
||||
row.unread = false;
|
||||
row.readReceiptSent = true;
|
||||
persistMessageRecord(chatId, row);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (matched) {
|
||||
delete state.pendingIncomingReadByBaseKey[baseKey];
|
||||
} else {
|
||||
state.pendingIncomingReadByBaseKey[baseKey] = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function markReadReceiptSentByBaseKey(baseKey) {
|
||||
@@ -651,6 +667,15 @@ export function addSignedMessageToChat({
|
||||
row.firstTick = row.from === 'out';
|
||||
row.secondTick = Boolean(existing?.secondTick);
|
||||
row.readReceiptSent = Boolean(existing?.readReceiptSent);
|
||||
if (row.baseKey && row.from === 'out' && state.pendingOutgoingReadByBaseKey[row.baseKey]) {
|
||||
row.secondTick = true;
|
||||
delete state.pendingOutgoingReadByBaseKey[row.baseKey];
|
||||
}
|
||||
if (row.baseKey && row.from === 'in' && state.pendingIncomingReadByBaseKey[row.baseKey]) {
|
||||
row.unread = false;
|
||||
row.readReceiptSent = true;
|
||||
delete state.pendingIncomingReadByBaseKey[row.baseKey];
|
||||
}
|
||||
|
||||
if (existingIndex < 0) {
|
||||
list.push(row);
|
||||
@@ -872,6 +897,8 @@ function resetStateForSignedOut() {
|
||||
state.appLog = next.appLog;
|
||||
state.incomingDedup = next.incomingDedup;
|
||||
state.knownMessageKeys = next.knownMessageKeys;
|
||||
state.pendingOutgoingReadByBaseKey = next.pendingOutgoingReadByBaseKey;
|
||||
state.pendingIncomingReadByBaseKey = next.pendingIncomingReadByBaseKey;
|
||||
state.outgoingTempSeq = next.outgoingTempSeq;
|
||||
state.notificationsTab = next.notificationsTab;
|
||||
state.pageLabelCollapsed = next.pageLabelCollapsed;
|
||||
|
||||
Reference in New Issue
Block a user