Аватары: убрать инициалы при наличии txId и усилить загрузку старых файлов

This commit is contained in:
AidarKC 2026-04-26 02:39:21 +03:00
parent 4c1aeeeac8
commit df7f38bd0a
4 changed files with 84 additions and 18 deletions

View File

@ -1,2 +1,2 @@
client.version=1.2.6 client.version=1.2.7
server.version=1.2.6 server.version=1.2.7

View File

@ -6,8 +6,8 @@
<link rel="manifest" href="./manifest.webmanifest" /> <link rel="manifest" href="./manifest.webmanifest" />
<title>Shine UI Demo</title> <title>Shine UI Demo</title>
<script> <script>
window.__SHINE_BUILD_HASH__ = '20260426102000'; window.__SHINE_BUILD_HASH__ = '20260426105500';
window.__SHINE_CLIENT_VERSION__ = '1.2.6'; window.__SHINE_CLIENT_VERSION__ = '1.2.7';
</script> </script>
<script> <script>
(function attachStylesWithBuildHash() { (function attachStylesWithBuildHash() {

View File

@ -1,5 +1,5 @@
import { state } from '../state.js'; import { state } from '../state.js';
import { validateArweaveTxId } from '../services/arweave-file-service.js'; import { buildArweaveDataUrl, validateArweaveTxId } from '../services/arweave-file-service.js';
import { getCachedAvatarObjectUrl } from '../services/arweave-avatar-cache-service.js'; import { getCachedAvatarObjectUrl } from '../services/arweave-avatar-cache-service.js';
function normalizeLogin(value) { function normalizeLogin(value) {
@ -52,6 +52,7 @@ export function renderUserAvatar({
if (!validateArweaveTxId(txId)) { if (!validateArweaveTxId(txId)) {
return wrap; return wrap;
} }
fallback.hidden = true;
const img = document.createElement('img'); const img = document.createElement('img');
img.alt = 'Аватар'; img.alt = 'Аватар';
@ -63,25 +64,55 @@ export function renderUserAvatar({
const gateway = state?.entrySettings?.arweaveServer; const gateway = state?.entrySettings?.arweaveServer;
void getCachedAvatarObjectUrl({ gateway, txId }) void getCachedAvatarObjectUrl({ gateway, txId })
.then((objectUrl) => { .then((objectUrl) => {
img.onload = () => { const directUrl = buildArweaveDataUrl({ gateway, txId });
fallback.hidden = true; let triedDirectUrl = false;
img.hidden = false; let objectUrlReleased = false;
const releaseObjectUrl = () => {
if (objectUrlReleased) return;
objectUrlReleased = true;
if (objectUrl.startsWith('blob:')) { if (objectUrl.startsWith('blob:')) {
URL.revokeObjectURL(objectUrl); URL.revokeObjectURL(objectUrl);
} }
}; };
img.onload = () => {
fallback.hidden = true;
img.hidden = false;
releaseObjectUrl();
};
img.onerror = () => { img.onerror = () => {
if (!triedDirectUrl) {
triedDirectUrl = true;
releaseObjectUrl();
img.src = directUrl;
return;
}
releaseObjectUrl();
img.hidden = true; img.hidden = true;
fallback.hidden = false; fallback.hidden = false;
if (objectUrl.startsWith('blob:')) {
URL.revokeObjectURL(objectUrl);
}
}; };
img.src = objectUrl; img.src = objectUrl;
}) })
.catch(() => { .catch(() => {
img.hidden = true; let directUrl = '';
fallback.hidden = false; try {
directUrl = buildArweaveDataUrl({ gateway, txId });
} catch {
directUrl = '';
}
if (!directUrl) {
img.hidden = true;
fallback.hidden = false;
return;
}
img.onload = () => {
fallback.hidden = true;
img.hidden = false;
};
img.onerror = () => {
img.hidden = true;
fallback.hidden = false;
};
img.src = directUrl;
}); });
return wrap; return wrap;

View File

@ -103,12 +103,47 @@ async function fetchAvatarBlob({ gateway, txId }) {
if (!response.ok) { if (!response.ok) {
throw new Error(`Не удалось загрузить аватар (${response.status} ${response.statusText})`); throw new Error(`Не удалось загрузить аватар (${response.status} ${response.statusText})`);
} }
const blob = await response.blob(); const sourceBlob = await response.blob();
const type = String(blob?.type || '').toLowerCase(); const sourceType = String(sourceBlob?.type || '').toLowerCase();
if (!type.startsWith('image/')) { if (sourceType.startsWith('image/')) {
throw new Error('Arweave-файл не является изображением'); return sourceBlob;
} }
return blob;
const bytes = new Uint8Array(await sourceBlob.arrayBuffer());
const detectedType = detectImageMime(bytes);
if (detectedType) {
return new Blob([bytes], { type: detectedType });
}
// Старые аватары могли быть загружены без корректного Content-Type.
// Возвращаем исходный blob, чтобы браузер попробовал декодировать сам.
return sourceBlob;
}
function detectImageMime(bytes) {
if (!(bytes instanceof Uint8Array) || bytes.length < 12) return '';
if (
bytes[0] === 0x89
&& bytes[1] === 0x50
&& bytes[2] === 0x4e
&& bytes[3] === 0x47
&& bytes[4] === 0x0d
&& bytes[5] === 0x0a
&& bytes[6] === 0x1a
&& bytes[7] === 0x0a
) return 'image/png';
if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) return 'image/jpeg';
if (
bytes[0] === 0x52
&& bytes[1] === 0x49
&& bytes[2] === 0x46
&& bytes[3] === 0x46
&& bytes[8] === 0x57
&& bytes[9] === 0x45
&& bytes[10] === 0x42
&& bytes[11] === 0x50
) return 'image/webp';
return '';
} }
async function getBlobFromCacheOrGateway({ gateway, txId }) { async function getBlobFromCacheOrGateway({ gateway, txId }) {