Checkpoint: первая рабочая версия звонков, сигналинг будет переделан

This commit is contained in:
AidarKC
2026-05-02 18:13:22 +03:00
parent b05da86197
commit 310863faec
20 changed files with 572 additions and 37 deletions
@@ -1,4 +1,9 @@
import { buildArweaveDataUrl, validateArweaveTxId } from './arweave-file-service.js';
import {
buildArweaveDataUrl,
sha256HexFromArrayBuffer,
validateArweaveTxId,
validateSha256Hex,
} from './arweave-file-service.js';
const DB_NAME = 'shine-ui-avatar-cache';
const DB_VERSION = 1;
@@ -55,6 +60,14 @@ async function putRecord(record) {
});
}
async function verifyBlobSha256(blob, expectedSha256Hex) {
const expected = String(expectedSha256Hex || '').trim().toLowerCase();
if (!validateSha256Hex(expected)) return true;
if (!(blob instanceof Blob)) return false;
const actual = await sha256HexFromArrayBuffer(await blob.arrayBuffer());
return actual === expected;
}
async function getAllRecords() {
return withStore('readonly', (store, _tx, resolve, reject) => {
const req = store.getAll();
@@ -146,22 +159,41 @@ function detectImageMime(bytes) {
return '';
}
async function getBlobFromCacheOrGateway({ gateway, txId }) {
async function getBlobFromCacheOrGateway({ gateway, txId, expectedSha256Hex = '' }) {
const expected = String(expectedSha256Hex || '').trim().toLowerCase();
try {
const cached = await getRecord(txId);
if (cached?.blob instanceof Blob) {
return cached.blob;
if (!validateSha256Hex(expected)) return cached.blob;
const cacheSha = String(cached?.sha256Hex || '').trim().toLowerCase();
if (cacheSha && cacheSha === expected) {
return cached.blob;
}
const ok = await verifyBlobSha256(cached.blob, expected);
if (ok) {
return cached.blob;
}
// кэш повреждён или не совпадает с ожидаемым хэшем: удаляем и перекачиваем
await deleteRecords([txId]);
}
} catch {
// ignore IndexedDB errors and fallback to fetch
}
const blob = await fetchAvatarBlob({ gateway, txId });
if (validateSha256Hex(expected)) {
const ok = await verifyBlobSha256(blob, expected);
if (!ok) {
throw new Error('SHA256_MISMATCH');
}
}
const computedSha256Hex = await sha256HexFromArrayBuffer(await blob.arrayBuffer());
const record = {
txId,
blob,
contentType: String(blob.type || 'application/octet-stream'),
sizeBytes: Number(blob.size || 0),
sha256Hex: computedSha256Hex,
cachedAtMs: Date.now(),
};
try {
@@ -173,12 +205,16 @@ async function getBlobFromCacheOrGateway({ gateway, txId }) {
return blob;
}
export async function getCachedAvatarObjectUrl({ gateway, txId }) {
export async function getCachedAvatarObjectUrl({ gateway, txId, expectedSha256Hex = '' }) {
const cleanTxId = String(txId || '').trim();
if (!validateArweaveTxId(cleanTxId)) {
throw new Error('Некорректный Transaction ID Arweave');
}
const blob = await getBlobFromCacheOrGateway({ gateway, txId: cleanTxId });
const blob = await getBlobFromCacheOrGateway({
gateway,
txId: cleanTxId,
expectedSha256Hex,
});
return URL.createObjectURL(blob);
}