SHA256
Исправить read-state каналов и импорт Solana PDA
This commit is contained in:
+5
@@ -28,6 +28,7 @@ public final class SolanaUserPdaImportService {
|
||||
private static final HttpClient HTTP = HttpClient.newHttpClient();
|
||||
private static final String MAGIC = "SHiNE";
|
||||
private static final int MAX_EFFECTIVE_ACCESS_SERVERS = 1;
|
||||
private static final int ARCHIVE_HEAD_PAYLOAD_BYTES = 64;
|
||||
|
||||
private SolanaUserPdaImportService() {}
|
||||
|
||||
@@ -266,6 +267,8 @@ public final class SolanaUserPdaImportService {
|
||||
}
|
||||
} else if (blockType == 70) {
|
||||
c += 1;
|
||||
} else if (blockType == 100) {
|
||||
c += ARCHIVE_HEAD_PAYLOAD_BYTES;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -374,6 +377,8 @@ public final class SolanaUserPdaImportService {
|
||||
}
|
||||
} else if (blockType == 70) {
|
||||
c += 1;
|
||||
} else if (blockType == 100) {
|
||||
c += ARCHIVE_HEAD_PAYLOAD_BYTES;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
client.version=1.12.15
|
||||
server.version=1.10.6
|
||||
client.version=1.12.16
|
||||
server.version=1.10.7
|
||||
|
||||
@@ -306,6 +306,7 @@ function createChannelReadTracker({
|
||||
let persistedSeenCount = safeInitialSeenCount;
|
||||
let initialPersistPending = !!initializeIfMissing;
|
||||
let inFlight = false;
|
||||
let writeBlocked = false;
|
||||
let disposed = false;
|
||||
let rafId = 0;
|
||||
let timerId = 0;
|
||||
@@ -318,7 +319,7 @@ function createChannelReadTracker({
|
||||
};
|
||||
|
||||
const queueFlush = (delayMs = 180) => {
|
||||
if (disposed || !canWrite) return;
|
||||
if (disposed || writeBlocked || !canWrite) return;
|
||||
clearTimer();
|
||||
timerId = setTimeout(() => {
|
||||
timerId = 0;
|
||||
@@ -327,7 +328,7 @@ function createChannelReadTracker({
|
||||
};
|
||||
|
||||
const flush = async () => {
|
||||
if (disposed || !canWrite) return;
|
||||
if (disposed || writeBlocked || !canWrite) return;
|
||||
const next = Math.max(safeInitialSeenCount, Math.min(desiredSeenCount, safeMessagesCount));
|
||||
if (next <= persistedSeenCount && !initialPersistPending) return;
|
||||
if (inFlight) {
|
||||
@@ -358,6 +359,18 @@ function createChannelReadTracker({
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
const errorCode = String(error?.code || '').trim().toUpperCase();
|
||||
if (errorCode === 'CHANNEL_NOT_FOLLOWED') {
|
||||
// This is a persistent state mismatch, not a transient transport failure.
|
||||
// Stop writes for the current channel view instead of hammering the server
|
||||
// after every scroll/resize. A fresh view after subscribe/refresh will create
|
||||
// a new tracker from the latest subscription state.
|
||||
writeBlocked = true;
|
||||
initialPersistPending = false;
|
||||
clearTimer();
|
||||
if (typeof onPersistError === 'function') onPersistError(error, { retrying: false });
|
||||
return;
|
||||
}
|
||||
if (typeof onPersistError === 'function') onPersistError(error, { retrying: true });
|
||||
queueFlush(800);
|
||||
} finally {
|
||||
@@ -2411,46 +2424,54 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
|
||||
? window.setTimeout(() => scrollChannelToUnreadLine(screen, unreadCount, false), 40)
|
||||
: 0;
|
||||
|
||||
const tracker = createChannelReadTracker({
|
||||
screen,
|
||||
routeKey,
|
||||
ownerBlockchainName: channelData.channel?.ownerBlockchainName || channelData.selector?.ownerBlockchainName,
|
||||
channelName: channelData.channel?.name || channelData.channel?.channelName,
|
||||
initializeIfMissing: !!(channelData.isSubscribed && !channelData.readStateInitialized),
|
||||
unreadCount,
|
||||
messagesCount,
|
||||
initialSeenCount: readCount,
|
||||
onPersistError: (error, options = {}) => {
|
||||
const detail = String(error?.message || '').trim();
|
||||
const retryText = options?.retrying === false ? '' : ' Сервер повторит попытку автоматически.';
|
||||
showStatus(`Не удалось сохранить, сколько сообщений прочитано.${detail ? ` ${detail}` : ''}${retryText}`);
|
||||
},
|
||||
onPersistSuccess: ({ readCount: persistedReadCount, unreadCount: persistedUnreadCount }) => {
|
||||
channelData.readCount = persistedReadCount;
|
||||
channelData.unreadCount = persistedUnreadCount;
|
||||
channelData.readStateInitialized = true;
|
||||
const shouldTrackReadState = !!(
|
||||
channelData.isSubscribed
|
||||
&& !channelData.isOwnChannel
|
||||
&& !channelData.isDiary
|
||||
);
|
||||
|
||||
// The "Новые сообщения" divider is a snapshot of the unread boundary at the
|
||||
// moment this channel view was opened. Persisting read state must not move or
|
||||
// remove it during the current view session; reopening the channel recalculates it.
|
||||
const tracker = shouldTrackReadState
|
||||
? createChannelReadTracker({
|
||||
screen,
|
||||
routeKey,
|
||||
ownerBlockchainName: channelData.channel?.ownerBlockchainName || channelData.selector?.ownerBlockchainName,
|
||||
channelName: channelData.channel?.name || channelData.channel?.channelName,
|
||||
initializeIfMissing: !!(!channelData.readStateInitialized),
|
||||
unreadCount,
|
||||
messagesCount,
|
||||
initialSeenCount: readCount,
|
||||
onPersistError: (error, options = {}) => {
|
||||
const detail = String(error?.message || '').trim();
|
||||
const retryText = options?.retrying === false ? '' : ' Сервер повторит попытку автоматически.';
|
||||
showStatus(`Не удалось сохранить, сколько сообщений прочитано.${detail ? ` ${detail}` : ''}${retryText}`);
|
||||
},
|
||||
onPersistSuccess: ({ readCount: persistedReadCount, unreadCount: persistedUnreadCount }) => {
|
||||
channelData.readCount = persistedReadCount;
|
||||
channelData.unreadCount = persistedUnreadCount;
|
||||
channelData.readStateInitialized = true;
|
||||
|
||||
const feedGroups = ['ownedChannels', 'followedUsersChannels', 'followedChannels'];
|
||||
for (const group of feedGroups) {
|
||||
const rows = Array.isArray(state.channelsFeed?.[group]) ? state.channelsFeed[group] : [];
|
||||
const row = rows.find((item) => (
|
||||
String(item?.channel?.ownerBlockchainName || '') === String(channelData.selector?.ownerBlockchainName || '')
|
||||
&& Number(item?.channel?.channelRoot?.blockNumber) === Number(channelData.selector?.channelRootBlockNumber)
|
||||
&& normalizeRouteHash(item?.channel?.channelRoot?.blockHash) === normalizeRouteHash(channelData.selector?.channelRootBlockHash)
|
||||
));
|
||||
if (row) {
|
||||
row.readCount = persistedReadCount;
|
||||
row.unreadCount = persistedUnreadCount;
|
||||
row.readStateInitialized = true;
|
||||
// The "Новые сообщения" divider is a snapshot of the unread boundary at the
|
||||
// moment this channel view was opened. Persisting read state must not move or
|
||||
// remove it during the current view session; reopening the channel recalculates it.
|
||||
|
||||
const feedGroups = ['ownedChannels', 'followedUsersChannels', 'followedChannels'];
|
||||
for (const group of feedGroups) {
|
||||
const rows = Array.isArray(state.channelsFeed?.[group]) ? state.channelsFeed[group] : [];
|
||||
const row = rows.find((item) => (
|
||||
String(item?.channel?.ownerBlockchainName || '') === String(channelData.selector?.ownerBlockchainName || '')
|
||||
&& Number(item?.channel?.channelRoot?.blockNumber) === Number(channelData.selector?.channelRootBlockNumber)
|
||||
&& normalizeRouteHash(item?.channel?.channelRoot?.blockHash) === normalizeRouteHash(channelData.selector?.channelRootBlockHash)
|
||||
));
|
||||
if (row) {
|
||||
row.readCount = persistedReadCount;
|
||||
row.unreadCount = persistedUnreadCount;
|
||||
row.readStateInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
showStatus('');
|
||||
},
|
||||
});
|
||||
showStatus('');
|
||||
},
|
||||
})
|
||||
: { cleanup() {}, measure() {} };
|
||||
|
||||
return () => {
|
||||
if (pendingScrollTimer) window.clearTimeout(pendingScrollTimer);
|
||||
|
||||
Reference in New Issue
Block a user