70 lines
2.6 KiB
TypeScript
70 lines
2.6 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
|
|
/**
|
|
* Normalisiert eine Domain für Hashing — muss zwischen Server und iOS-Extension
|
|
* IDENTISCH sein, sonst stimmen die Hashes nicht überein.
|
|
*
|
|
* Schritte:
|
|
* 1. trim, lowercase
|
|
* 2. http:// und https:// entfernen
|
|
* 3. Pfad / Query nach erstem `/` abschneiden
|
|
* 4. Optional `www.` Prefix entfernen (so dass `www.bet365.com` und `bet365.com`
|
|
* den gleichen Hash haben)
|
|
*/
|
|
export function normalizeDomain(input: string): string {
|
|
return input
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/^https?:\/\//, "")
|
|
.replace(/\/.*$/, "")
|
|
.replace(/^www\./, "");
|
|
}
|
|
|
|
/**
|
|
* SHA-256 → erste 8 Bytes als big-endian UInt64.
|
|
* Wenn salt gesetzt ist, wird `<salt>:<domain>` gehasht (für user-spezifische
|
|
* Custom-Domains, damit gleiche Domain bei zwei Usern unterschiedliche
|
|
* Hashes ergibt → keine Cross-User-Korrelation möglich).
|
|
*/
|
|
export function hashDomain(domain: string, salt = ""): bigint {
|
|
const normalized = normalizeDomain(domain);
|
|
const input = salt ? `${salt}:${normalized}` : normalized;
|
|
const digest = createHash("sha256").update(input, "utf8").digest();
|
|
return digest.readBigUInt64BE(0);
|
|
}
|
|
|
|
/**
|
|
* Hasht eine Liste von Domains, sortiert die Hashes aufsteigend, und gibt
|
|
* sie als Binary-Buffer zurück (8 Bytes pro Hash, big-endian).
|
|
*
|
|
* Format der Binary-Datei für die iOS-Extension:
|
|
* ┌────────────────┬────────────────┬─────────────────┐
|
|
* │ Hash 0 (8 B) │ Hash 1 (8 B) │ Hash 2 (8 B) │ ...
|
|
* └────────────────┴────────────────┴─────────────────┘
|
|
* sorted ascending → binary-search möglich, O(log n).
|
|
*/
|
|
export function buildHashListBinary(domains: string[], salt = ""): Buffer {
|
|
const hashes = new BigUint64Array(domains.length);
|
|
for (let i = 0; i < domains.length; i++) {
|
|
hashes[i] = hashDomain(domains[i], salt);
|
|
}
|
|
hashes.sort();
|
|
|
|
// BigUint64Array ist platform-endian — wir brauchen explicit big-endian
|
|
// damit Server und iOS-Extension dasselbe Format lesen.
|
|
const buf = Buffer.alloc(hashes.length * 8);
|
|
for (let i = 0; i < hashes.length; i++) {
|
|
buf.writeBigUInt64BE(hashes[i], i * 8);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* ETag aus dem Binary-Content (sha256 der Bytes, hex-encoded).
|
|
* Client cached darauf basierend → wenn Server-DB sich nicht ändert,
|
|
* spart Bandbreite + Battery.
|
|
*/
|
|
export function etagFor(buf: Buffer): string {
|
|
return `"${createHash("sha256").update(buf).digest("hex").slice(0, 16)}"`;
|
|
}
|