Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | 1x 1x 6608x 6608x 6608x 6608x 6608x 6608x 6609x 6609x 6609x 1x 1x 1x 1x 6608x 6608x 6609x 6609x 6609x 6608x 1x 690586x 690586x 79008x 79008x 690586x 561373x 561371x 561371x 79008x 79008x 1x 1x 131x 131x 1x 1x 5x 5x 1x 9912x 9912x 1x 127x 127x 10x 10x 10x 10x 10x 71x 71x 61x 71x 10x 10x 10x 10x 10x 2x 2x 2x 1x 4541x 4541x 2x 2x 2x 2x | import { Sha256 } from './sha256.js';
import type { Hash } from './types.js';
const HEX = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
function toHex(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer);
let out = '';
for (let i = 0; i < bytes.length; i++) out += HEX[bytes[i] as number];
return out;
}
function subtle(): SubtleCrypto {
const c = globalThis.crypto;
if (!c?.subtle) {
throw new Error(
'vfs-sync requires Web Crypto (globalThis.crypto.subtle). ' +
'In browsers this means a secure context (https or localhost).',
);
}
return c.subtle;
}
/** Content address of a blob. */
export async function sha256(data: Uint8Array): Promise<Hash> {
// Copy into a plain ArrayBuffer: a Uint8Array view may cover only part of
// a larger buffer, and digest() would otherwise hash the whole thing.
const buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
return toHex(await subtle().digest('SHA-256', buf as ArrayBuffer));
}
/**
* JSON with deterministic key order, so the same logical object always
* produces the same hash on every peer.
*/
export function canonicalJSON(value: unknown): string {
if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null';
if (Array.isArray(value)) return `[${value.map(canonicalJSON).join(',')}]`;
const obj = value as Record<string, unknown>;
const parts: string[] = [];
for (const key of Object.keys(obj).sort()) {
if (obj[key] === undefined) continue;
parts.push(`${JSON.stringify(key)}:${canonicalJSON(obj[key])}`);
}
return `{${parts.join(',')}}`;
}
const encoder = new TextEncoder();
export function encodeJSON(value: unknown): Uint8Array {
return encoder.encode(canonicalJSON(value));
}
const decoder = new TextDecoder();
export function decodeJSON<T>(data: Uint8Array): T {
return JSON.parse(decoder.decode(data)) as T;
}
export function encodeText(text: string): Uint8Array {
return encoder.encode(text);
}
export function decodeText(data: Uint8Array): string {
return decoder.decode(data);
}
/**
* Content address of a blob that is too big to hold — same digest as
* {@link sha256}, computed a chunk at a time so nothing but the current chunk
* is ever resident. Consumes the stream.
*/
export async function sha256Stream(stream: ReadableStream<Uint8Array>): Promise<Hash> {
const hasher = new Sha256();
const reader = stream.getReader();
try {
for (;;) {
const { done, value } = await reader.read();
if (done) break;
if (value) hasher.update(value);
}
} finally {
reader.releaseLock();
}
return hasher.digest();
}
/** Content address of a tree or commit. */
export async function hashJSON(value: unknown): Promise<Hash> {
return sha256(encodeJSON(value));
}
export function randomId(): string {
const c = globalThis.crypto;
if (c?.randomUUID) return c.randomUUID();
const bytes = new Uint8Array(16);
c.getRandomValues(bytes);
return Array.from(bytes, (b) => HEX[b]).join('');
}
|