97 lines
2.8 KiB
TypeScript
97 lines
2.8 KiB
TypeScript
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<NativeFile[]> {
|
|
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<string, string> = {},
|
|
) {
|
|
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);
|
|
}
|