Release Helper Tools 0.1.0
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { assertBoundedText, assertPositiveSafeInteger } from "./limits";
|
||||
|
||||
export interface ObjectUrlLease {
|
||||
readonly url: string;
|
||||
readonly revoked: boolean;
|
||||
revoke(): void;
|
||||
}
|
||||
|
||||
export function sanitizeDownloadFilename(
|
||||
input: string,
|
||||
fallback = "download.bin",
|
||||
maximumLength = 180,
|
||||
): string {
|
||||
assertPositiveSafeInteger(maximumLength, "Maximum filename length");
|
||||
const cleaned = cleanFilename(input);
|
||||
const fallbackName = cleanFilename(fallback) || "download.bin";
|
||||
const safe = cleaned || fallbackName;
|
||||
if (safe.length <= maximumLength) return safe;
|
||||
const dot = safe.lastIndexOf(".");
|
||||
const extension = dot > 0 && safe.length - dot <= 16 ? safe.slice(dot) : "";
|
||||
let stem = safe.slice(0, Math.max(1, maximumLength - extension.length));
|
||||
const finalCodeUnit = stem.charCodeAt(stem.length - 1);
|
||||
if (finalCodeUnit >= 0xd800 && finalCodeUnit <= 0xdbff)
|
||||
stem = stem.slice(0, -1);
|
||||
return `${stem || "_"}${extension}`;
|
||||
}
|
||||
|
||||
function cleanFilename(input: string): string {
|
||||
const normalized = assertBoundedText(
|
||||
input,
|
||||
4096,
|
||||
"Filename length",
|
||||
).normalize("NFC");
|
||||
const cleaned = Array.from(normalized, (character) => {
|
||||
const codePoint = character.codePointAt(0) ?? 0;
|
||||
return codePoint <= 31 ||
|
||||
(codePoint >= 127 && codePoint <= 159) ||
|
||||
(codePoint >= 0x202a && codePoint <= 0x202e) ||
|
||||
(codePoint >= 0x2066 && codePoint <= 0x2069) ||
|
||||
'/\\:*?"<>|'.includes(character)
|
||||
? "_"
|
||||
: character;
|
||||
})
|
||||
.join("")
|
||||
.replace(/\s+/gu, " ")
|
||||
.replace(/^\.+|[. ]+$/gu, "")
|
||||
.trim();
|
||||
return /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(cleaned)
|
||||
? `_${cleaned}`
|
||||
: cleaned;
|
||||
}
|
||||
|
||||
export function createObjectUrlLease(
|
||||
blob: Blob,
|
||||
urlApi: Pick<typeof URL, "createObjectURL" | "revokeObjectURL"> = URL,
|
||||
): ObjectUrlLease {
|
||||
const url = urlApi.createObjectURL(blob);
|
||||
let revoked = false;
|
||||
return {
|
||||
url,
|
||||
get revoked() {
|
||||
return revoked;
|
||||
},
|
||||
revoke() {
|
||||
if (revoked) return;
|
||||
revoked = true;
|
||||
urlApi.revokeObjectURL(url);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function triggerBlobDownload(
|
||||
blob: Blob,
|
||||
filename: string,
|
||||
ownerDocument: Document = document,
|
||||
): ObjectUrlLease {
|
||||
const lease = createObjectUrlLease(blob);
|
||||
const anchor = ownerDocument.createElement("a");
|
||||
anchor.href = lease.url;
|
||||
anchor.download = sanitizeDownloadFilename(filename);
|
||||
anchor.rel = "noopener";
|
||||
anchor.hidden = true;
|
||||
ownerDocument.body.append(anchor);
|
||||
try {
|
||||
anchor.click();
|
||||
} finally {
|
||||
anchor.remove();
|
||||
globalThis.setTimeout(() => lease.revoke(), 0);
|
||||
}
|
||||
return lease;
|
||||
}
|
||||
Reference in New Issue
Block a user