import * as Crypto from "expo-crypto"; import * as DocumentPicker from "expo-document-picker"; import { File, Paths } from "expo-file-system"; import * as FileSystem from "expo-file-system/legacy"; import * as Linking from "expo-linking"; import * as Sharing from "expo-sharing"; export type NativeFile = { uri: string; name: string; mimeType: string; size: number; }; type PickedAsset = { uri: string; name: string; mimeType?: string | null; size?: number | null; }; export function toNativeFile(asset: PickedAsset): NativeFile { return { uri: asset.uri, name: asset.name, mimeType: asset.mimeType ?? "application/octet-stream", size: asset.size ?? 0, }; } export async function pickFiles({ multiple = false, mimeTypes, }: { multiple?: boolean; mimeTypes?: string[] | undefined; } = {}): Promise { const result = await DocumentPicker.getDocumentAsync({ copyToCacheDirectory: true, multiple, type: mimeTypes?.length ? mimeTypes : "*/*", }); if (result.canceled) return []; return result.assets.map((asset) => { const file = toNativeFile(asset); if (file.size > 0) return file; const actualSize = new File(file.uri).size; return { ...file, size: typeof actualSize === "number" && Number.isFinite(actualSize) ? actualSize : file.size, }; }); } export async function sha256(file: NativeFile) { const bytes = await new File(file.uri).bytes(); const digest = await Crypto.digest(Crypto.CryptoDigestAlgorithm.SHA256, bytes); const hex = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join(""); return `sha256:${hex}`; } export async function uploadFile( url: string, file: NativeFile, headers: Record = {}, ) { const task = FileSystem.createUploadTask(url, file.uri, { httpMethod: "PUT", headers: { "Content-Type": file.mimeType, ...headers, }, uploadType: FileSystem.FileSystemUploadType.BINARY_CONTENT, }); const result = await task.uploadAsync(); if (!result || result.status < 200 || result.status >= 300) { throw new Error("Не удалось загрузить файл в хранилище"); } } export async function downloadAndOpen(url: string, suggestedName = "document") { try { const safeName = suggestedName.replace(/[^\p{L}\p{N}._-]+/gu, "_") || "document"; const target = new File(Paths.cache, `${Date.now()}-${safeName}`).uri; const result = await FileSystem.downloadAsync(url, target); if (await Sharing.isAvailableAsync()) { await Sharing.shareAsync(result.uri); return; } } catch { // Системный браузер остаётся безопасным запасным вариантом. } await Linking.openURL(url); }