blob: da68ec852a17f2a804804718ab81efc8cf7022f9 [file] [edit]
// Copyright (C) 2025 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {exists} from './utils';
declare global {
interface Window {
showSaveFilePicker: (
options?: SaveFilePickerOptions,
) => Promise<FileSystemFileHandle>;
}
interface SaveFilePickerOptions {
suggestedName?: string;
types?: FilePickerAcceptType[];
excludeAcceptAllOption?: boolean;
}
interface FilePickerAcceptType {
description?: string;
accept: Record<string, string[]>;
}
}
// A representation of some content that can be downloaded.
type Content = string | Uint8Array<ArrayBuffer> | ArrayBuffer | File | Blob;
export type FilePickerOptions = {
types?: FilePickerAcceptType[];
};
/**
* Downloads some content to a file.
*
* @param args The arguments for the download.
* @param args.content The content to download.
* @param args.fileName The name of the file to download.
* @param args.mimeType The MIME type of the content.
* @param args.filePicker If provided, the file picker will be used to save the file.
*/
export async function download({
content,
fileName,
mimeType,
filePicker,
}: {
content: Content;
fileName: string;
mimeType?: string;
filePicker?: FilePickerOptions;
}) {
let blob: Blob;
if (content instanceof File || content instanceof Blob) {
blob = content;
} else {
const inferredMimeType =
typeof content === 'string' ? 'text/plain' : 'application/octet-stream';
blob = new Blob([content], {
type: mimeType ?? inferredMimeType,
});
}
if (filePicker && exists(window.showSaveFilePicker)) {
try {
const handle = await window.showSaveFilePicker({
suggestedName: fileName,
types: filePicker.types,
});
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
} catch (e) {
console.error(e);
// The user pressed cancel, do nothing.
}
} else {
// No file picker available or requested, fallback to the old method.
using url = createUrl(blob);
downloadUrl({url: url.value, fileName});
}
}
/**
* Initiate download of a resource identified by a URL.
*
* @param args The arguments for the download.
* @param args.fileName The name of the file to download.
* @param args.url The URL of the resource to download.
*/
export function downloadUrl({fileName, url}: {fileName: string; url: string}) {
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.target = '_blank';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
function createUrl(blob: Blob): Disposable & {readonly value: string} {
const url = URL.createObjectURL(blob);
return {
[Symbol.dispose]: () => URL.revokeObjectURL(url),
value: url,
};
}