SHA256
Добавить user_settings и синхронизацию настроек
This commit is contained in:
@@ -234,6 +234,12 @@ function buildThreadRoute(messageRef, selector) {
|
||||
});
|
||||
}
|
||||
|
||||
function buildChannelSettingsKey(selector, channelName) {
|
||||
const ownerBch = String(selector?.ownerBlockchainName || '').trim();
|
||||
const name = String(channelName || '').trim();
|
||||
return `${ownerBch}/${name}`;
|
||||
}
|
||||
|
||||
function firstNonEmptyText(...candidates) {
|
||||
for (const candidate of candidates) {
|
||||
if (typeof candidate !== 'string') continue;
|
||||
@@ -1374,6 +1380,8 @@ async function loadFromApi(route, channelId) {
|
||||
const messages = Array.isArray(payload.messages) ? payload.messages : [];
|
||||
let reverseChannelMissingWarning = '';
|
||||
let mergedMessages = [...messages];
|
||||
const unreadCount = Number(channel?.unreadCount || 0);
|
||||
const messagesCount = Number(channel?.messagesCount || mergedMessages.length || 0);
|
||||
|
||||
const currentLogin = currentSessionLogin;
|
||||
const ownerLogin = String(payload.channel?.ownerLogin || '').trim();
|
||||
@@ -1445,6 +1453,8 @@ async function loadFromApi(route, channelId) {
|
||||
posts,
|
||||
metaEvents: Array.isArray(payload?.metaEvents) ? payload.metaEvents : [],
|
||||
reverseChannelMissingWarning,
|
||||
unreadCount,
|
||||
messagesCount,
|
||||
isOwnChannel,
|
||||
isSubscribed,
|
||||
selector,
|
||||
@@ -1905,6 +1915,13 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
|
||||
screen.append(reverseWarning);
|
||||
}
|
||||
|
||||
if (Number(channelData.unreadCount || 0) > 0) {
|
||||
const unreadLine = document.createElement('div');
|
||||
unreadLine.className = 'card channel-unread-line';
|
||||
unreadLine.textContent = `Не прочитано: ${channelData.unreadCount}`;
|
||||
screen.append(unreadLine);
|
||||
}
|
||||
|
||||
const actionButton = document.createElement('button');
|
||||
actionButton.className = 'destructive-btn channel-main-action';
|
||||
actionButton.textContent = 'Подписаться на канал';
|
||||
@@ -2294,6 +2311,19 @@ export function render({ navigate, route, chrome }) {
|
||||
try {
|
||||
const apiData = await loadFromApi(route, channelId);
|
||||
activeSelector = apiData?.selector || null;
|
||||
const lastSeenCount = Number(apiData?.messagesCount || (Array.isArray(apiData?.posts) ? apiData.posts.length : 0) || 0);
|
||||
const settingKey = buildChannelSettingsKey(apiData?.selector, apiData?.channel?.name);
|
||||
if (settingKey && state.session.login && state.session.storagePwdInMemory) {
|
||||
void authService.upsertUserSetting({
|
||||
login: state.session.login,
|
||||
settingType: 1,
|
||||
settingKey,
|
||||
timeMs: Date.now(),
|
||||
valueText: '',
|
||||
valueNum: lastSeenCount,
|
||||
storagePwd: state.session.storagePwdInMemory,
|
||||
}).catch(() => {});
|
||||
}
|
||||
const titleLabel = apiData?.channel?.displayTitle || apiData?.channel?.name || 'channel';
|
||||
const entrypointPosts = getEntrypointPosts(apiData?.posts);
|
||||
const openEntrypointHistory = () => {
|
||||
|
||||
@@ -153,6 +153,10 @@ function makeClientPlatform() {
|
||||
return 'Web';
|
||||
}
|
||||
|
||||
function escapeUserSettingPart(value = '') {
|
||||
return String(value ?? '').replace(/\\/g, '\\\\').replace(/\|/g, '\\|');
|
||||
}
|
||||
|
||||
function hexToBytes(hex) {
|
||||
const clean = String(hex || '').trim().toLowerCase();
|
||||
if (!clean || clean.length % 2 !== 0) throw new Error('Некорректный hex');
|
||||
@@ -2898,6 +2902,59 @@ export class AuthService {
|
||||
return response.payload || {};
|
||||
}
|
||||
|
||||
async upsertUserSetting({
|
||||
login,
|
||||
settingType,
|
||||
settingKey,
|
||||
timeMs,
|
||||
valueText = '',
|
||||
valueNum = 0,
|
||||
storagePwd,
|
||||
syncDelivery = false,
|
||||
}) {
|
||||
const cleanLogin = String(login || '').trim();
|
||||
const cleanSettingKey = String(settingKey || '').trim();
|
||||
const cleanValueText = String(valueText ?? '');
|
||||
const cleanTimeMs = Number(timeMs);
|
||||
const cleanSettingType = Number(settingType);
|
||||
const cleanValueNum = Number(valueNum ?? 0);
|
||||
if (!cleanLogin || !cleanSettingKey) throw new Error('Не переданы login/settingKey');
|
||||
if (!Number.isFinite(cleanTimeMs) || cleanTimeMs <= 0) throw new Error('Не передан корректный timeMs');
|
||||
if (!Number.isFinite(cleanSettingType)) throw new Error('Не передан корректный settingType');
|
||||
if (!storagePwd) throw new Error('Не передан storagePwd для подписи UpsertUserSetting.');
|
||||
|
||||
const secrets = await loadEncryptedUserSecrets(cleanLogin, storagePwd);
|
||||
const clientPrivPkcs8 = String(secrets?.clientKey || '').trim();
|
||||
if (!clientPrivPkcs8) throw new Error('Не найден приватный clientKey');
|
||||
const privateKey = await importPkcs8Ed25519(clientPrivPkcs8);
|
||||
const clientKey = await publicKeyB64FromPkcs8Ed25519(clientPrivPkcs8);
|
||||
|
||||
const preimage = [
|
||||
'SHiNe/UserSettings:',
|
||||
escapeUserSettingPart(cleanLogin),
|
||||
String(cleanSettingType),
|
||||
escapeUserSettingPart(cleanSettingKey),
|
||||
String(Math.trunc(cleanTimeMs)),
|
||||
escapeUserSettingPart(cleanValueText),
|
||||
String(Math.trunc(cleanValueNum)),
|
||||
].join('|');
|
||||
const signature = await signBase64(privateKey, preimage);
|
||||
|
||||
const response = await this.ws.request('UpsertUserSetting', {
|
||||
login: cleanLogin,
|
||||
setting_type: Math.trunc(cleanSettingType),
|
||||
setting_key: cleanSettingKey,
|
||||
time_ms: Math.trunc(cleanTimeMs),
|
||||
value_text: cleanValueText,
|
||||
value_num: Math.trunc(cleanValueNum),
|
||||
client_key: clientKey,
|
||||
signature,
|
||||
sync_delivery: !!syncDelivery,
|
||||
});
|
||||
if (response.status !== 200) throw opError('UpsertUserSetting', response);
|
||||
return response.payload || {};
|
||||
}
|
||||
|
||||
async getTestFreeAvatarQuota() {
|
||||
const response = await this.ws.request('TestGetFreeAvatarQuota', {});
|
||||
if (response.status !== 200) throw opError('TestGetFreeAvatarQuota', response);
|
||||
|
||||
Reference in New Issue
Block a user