All files utils.ts

4.34% Statements 3/69
100% Branches 0/0
0% Functions 0/2
4.34% Lines 3/69

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 841x 1x 1x                                                                                                                                                                  
import crypto from 'node:crypto';
import fs from 'node:fs';
import axios, { type AxiosResponse, type AxiosRequestConfig } from 'axios';
 
/**
 * 获取 hash 文件的值
 */
export async function getHashText(
    hashUrl: string,
    axiosOptions: AxiosRequestConfig = {}
): Promise<{ hash: string; hashAlg: string; error?: Error }> {
    try {
        let hash = (
            await axios.get(hashUrl, {
                ...axiosOptions,
                responseType: 'text'
            })
        ).data;
        let hashAlg = 'sha256';
        if (hash.includes('-')) {
            const t = hash.split('-');
            hash = t.pop() as string;
            hashAlg = t.join('-');
        }
        if (!crypto.getHashes().includes(hashAlg)) {
            return {
                hash: '',
                hashAlg: '',
                error: new Error(`Unsupported hash algorithm ${hashAlg}`)
            };
        }
        return { hash, hashAlg };
    } catch (error: any) {
        return { hash: '', hashAlg: '', error };
    }
}
 
/**
 * 获取文件。如果有hash则校验hash
 */
export async function downloadFile(
    url: string,
    filePath: string,
    hash: string,
    hashAlg: string,
    axiosOptions: AxiosRequestConfig = {}
): Promise<null | { errType?: 'axios' | 'file' | 'hash'; error?: Error }> {
    // TODO: 断点续传
    let result: AxiosResponse;
    try {
        result = await axios.get(url, {
            ...axiosOptions,
            responseType: 'stream'
        });
    } catch (error: any) {
        return { errType: 'axios', error };
    }
 
    const hashStream = hash ? crypto.createHash(hashAlg) : null;
    const fileStream = fs.createWriteStream(filePath);
 
    const streamPromise = new Promise<void>((resolve, reject) => {
        fileStream.on('finish', resolve);
        fileStream.on('error', reject);
    });
    result.data.on('data', (chunk: crypto.BinaryLike) => {
        hashStream?.update(chunk);
        fileStream.write(chunk);
    });
    result.data.on('end', () => fileStream.end());
 
    try {
        await streamPromise;
    } catch (error: any) {
        return { errType: 'file', error };
    }
 
    if (hash && hashStream?.digest('hex') !== hash) {
        return { errType: 'hash', error: new Error('Hash not match') };
    }
 
    return null;
}