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 HttpClient HTTP = HttpClient.newHttpClient();
|
||||||
private static final String MAGIC = "SHiNE";
|
private static final String MAGIC = "SHiNE";
|
||||||
private static final int MAX_EFFECTIVE_ACCESS_SERVERS = 1;
|
private static final int MAX_EFFECTIVE_ACCESS_SERVERS = 1;
|
||||||
|
private static final int ARCHIVE_HEAD_PAYLOAD_BYTES = 64;
|
||||||
|
|
||||||
private SolanaUserPdaImportService() {}
|
private SolanaUserPdaImportService() {}
|
||||||
|
|
||||||
@@ -266,6 +267,8 @@ public final class SolanaUserPdaImportService {
|
|||||||
}
|
}
|
||||||
} else if (blockType == 70) {
|
} else if (blockType == 70) {
|
||||||
c += 1;
|
c += 1;
|
||||||
|
} else if (blockType == 100) {
|
||||||
|
c += ARCHIVE_HEAD_PAYLOAD_BYTES;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -374,6 +377,8 @@ public final class SolanaUserPdaImportService {
|
|||||||
}
|
}
|
||||||
} else if (blockType == 70) {
|
} else if (blockType == 70) {
|
||||||
c += 1;
|
c += 1;
|
||||||
|
} else if (blockType == 100) {
|
||||||
|
c += ARCHIVE_HEAD_PAYLOAD_BYTES;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,2 +1,2 @@
|
|||||||
client.version=1.12.15
|
client.version=1.12.16
|
||||||
server.version=1.10.6
|
server.version=1.10.7
|
||||||
|
|||||||
@@ -306,6 +306,7 @@ function createChannelReadTracker({
|
|||||||
let persistedSeenCount = safeInitialSeenCount;
|
let persistedSeenCount = safeInitialSeenCount;
|
||||||
let initialPersistPending = !!initializeIfMissing;
|
let initialPersistPending = !!initializeIfMissing;
|
||||||
let inFlight = false;
|
let inFlight = false;
|
||||||
|
let writeBlocked = false;
|
||||||
let disposed = false;
|
let disposed = false;
|
||||||
let rafId = 0;
|
let rafId = 0;
|
||||||
let timerId = 0;
|
let timerId = 0;
|
||||||
@@ -318,7 +319,7 @@ function createChannelReadTracker({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const queueFlush = (delayMs = 180) => {
|
const queueFlush = (delayMs = 180) => {
|
||||||
if (disposed || !canWrite) return;
|
if (disposed || writeBlocked || !canWrite) return;
|
||||||
clearTimer();
|
clearTimer();
|
||||||
timerId = setTimeout(() => {
|
timerId = setTimeout(() => {
|
||||||
timerId = 0;
|
timerId = 0;
|
||||||
@@ -327,7 +328,7 @@ function createChannelReadTracker({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const flush = async () => {
|
const flush = async () => {
|
||||||
if (disposed || !canWrite) return;
|
if (disposed || writeBlocked || !canWrite) return;
|
||||||
const next = Math.max(safeInitialSeenCount, Math.min(desiredSeenCount, safeMessagesCount));
|
const next = Math.max(safeInitialSeenCount, Math.min(desiredSeenCount, safeMessagesCount));
|
||||||
if (next <= persistedSeenCount && !initialPersistPending) return;
|
if (next <= persistedSeenCount && !initialPersistPending) return;
|
||||||
if (inFlight) {
|
if (inFlight) {
|
||||||
@@ -358,6 +359,18 @@ function createChannelReadTracker({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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 });
|
if (typeof onPersistError === 'function') onPersistError(error, { retrying: true });
|
||||||
queueFlush(800);
|
queueFlush(800);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -2411,46 +2424,54 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
|
|||||||
? window.setTimeout(() => scrollChannelToUnreadLine(screen, unreadCount, false), 40)
|
? window.setTimeout(() => scrollChannelToUnreadLine(screen, unreadCount, false), 40)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
const tracker = createChannelReadTracker({
|
const shouldTrackReadState = !!(
|
||||||
screen,
|
channelData.isSubscribed
|
||||||
routeKey,
|
&& !channelData.isOwnChannel
|
||||||
ownerBlockchainName: channelData.channel?.ownerBlockchainName || channelData.selector?.ownerBlockchainName,
|
&& !channelData.isDiary
|
||||||
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;
|
|
||||||
|
|
||||||
// The "Новые сообщения" divider is a snapshot of the unread boundary at the
|
const tracker = shouldTrackReadState
|
||||||
// moment this channel view was opened. Persisting read state must not move or
|
? createChannelReadTracker({
|
||||||
// remove it during the current view session; reopening the channel recalculates it.
|
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'];
|
// The "Новые сообщения" divider is a snapshot of the unread boundary at the
|
||||||
for (const group of feedGroups) {
|
// moment this channel view was opened. Persisting read state must not move or
|
||||||
const rows = Array.isArray(state.channelsFeed?.[group]) ? state.channelsFeed[group] : [];
|
// remove it during the current view session; reopening the channel recalculates it.
|
||||||
const row = rows.find((item) => (
|
|
||||||
String(item?.channel?.ownerBlockchainName || '') === String(channelData.selector?.ownerBlockchainName || '')
|
const feedGroups = ['ownedChannels', 'followedUsersChannels', 'followedChannels'];
|
||||||
&& Number(item?.channel?.channelRoot?.blockNumber) === Number(channelData.selector?.channelRootBlockNumber)
|
for (const group of feedGroups) {
|
||||||
&& normalizeRouteHash(item?.channel?.channelRoot?.blockHash) === normalizeRouteHash(channelData.selector?.channelRootBlockHash)
|
const rows = Array.isArray(state.channelsFeed?.[group]) ? state.channelsFeed[group] : [];
|
||||||
));
|
const row = rows.find((item) => (
|
||||||
if (row) {
|
String(item?.channel?.ownerBlockchainName || '') === String(channelData.selector?.ownerBlockchainName || '')
|
||||||
row.readCount = persistedReadCount;
|
&& Number(item?.channel?.channelRoot?.blockNumber) === Number(channelData.selector?.channelRootBlockNumber)
|
||||||
row.unreadCount = persistedUnreadCount;
|
&& normalizeRouteHash(item?.channel?.channelRoot?.blockHash) === normalizeRouteHash(channelData.selector?.channelRootBlockHash)
|
||||||
row.readStateInitialized = true;
|
));
|
||||||
|
if (row) {
|
||||||
|
row.readCount = persistedReadCount;
|
||||||
|
row.unreadCount = persistedUnreadCount;
|
||||||
|
row.readStateInitialized = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
showStatus('');
|
||||||
showStatus('');
|
},
|
||||||
},
|
})
|
||||||
});
|
: { cleanup() {}, measure() {} };
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (pendingScrollTimer) window.clearTimeout(pendingScrollTimer);
|
if (pendingScrollTimer) window.clearTimeout(pendingScrollTimer);
|
||||||
|
|||||||
Reference in New Issue
Block a user