diff options
Diffstat (limited to 'apps/web/src/services/storage/quota.ts')
| -rw-r--r-- | apps/web/src/services/storage/quota.ts | 290 |
1 files changed, 145 insertions, 145 deletions
diff --git a/apps/web/src/services/storage/quota.ts b/apps/web/src/services/storage/quota.ts index 1eb6028e..4d1adad5 100644 --- a/apps/web/src/services/storage/quota.ts +++ b/apps/web/src/services/storage/quota.ts @@ -1,145 +1,145 @@ -const BYTE_UNITS = ["B", "KB", "MB", "GB", "TB"] as const; - -export const STORAGE_HEADROOM_RESERVE_BYTES = 50 * 1024 * 1024; - -export interface StorageQuotaStatus { - quotaBytes: number | null; - usageBytes: number | null; - headroomBytes: number | null; - availableBytes: number | null; -} - -export interface StorageCapacityCheckResult { - canStore: boolean; - reason: "enough-space" | "insufficient-space" | "estimate-unavailable"; - availableBytes: number | null; -} - -function normalizeByteValue({ value }: { value: unknown }): number | null { - if (typeof value !== "number" || !Number.isFinite(value) || value < 0) { - return null; - } - - return value; -} - -export function formatStorageBytes({ bytes }: { bytes: number }): string { - if (!Number.isFinite(bytes) || bytes <= 0) { - return "0 B"; - } - - let value = bytes; - let unitIndex = 0; - - while (value >= 1024 && unitIndex < BYTE_UNITS.length - 1) { - value /= 1024; - unitIndex += 1; - } - - const precision = value >= 10 || unitIndex === 0 ? 0 : 1; - return `${value.toFixed(precision)} ${BYTE_UNITS[unitIndex]}`; -} - -export async function readStorageQuotaStatus(): Promise<StorageQuotaStatus> { - if ( - typeof navigator === "undefined" || - !navigator.storage || - typeof navigator.storage.estimate !== "function" - ) { - return { - quotaBytes: null, - usageBytes: null, - headroomBytes: null, - availableBytes: null, - }; - } - - const estimate = await navigator.storage.estimate(); - const quotaBytes = normalizeByteValue({ value: estimate.quota }); - const usageBytes = normalizeByteValue({ value: estimate.usage }); - - if (quotaBytes === null || usageBytes === null) { - return { - quotaBytes, - usageBytes, - headroomBytes: null, - availableBytes: null, - }; - } - - const headroomBytes = Math.max(quotaBytes - usageBytes, 0); - const availableBytes = Math.max( - headroomBytes - STORAGE_HEADROOM_RESERVE_BYTES, - 0, - ); - - return { - quotaBytes, - usageBytes, - headroomBytes, - availableBytes, - }; -} - -export function evaluateStorageCapacity({ - requiredBytes, - quotaStatus, -}: { - requiredBytes: number; - quotaStatus: StorageQuotaStatus; -}): StorageCapacityCheckResult { - if (quotaStatus.availableBytes === null) { - return { - canStore: true, - reason: "estimate-unavailable", - availableBytes: null, - }; - } - - if (requiredBytes > quotaStatus.availableBytes) { - return { - canStore: false, - reason: "insufficient-space", - availableBytes: quotaStatus.availableBytes, - }; - } - - return { - canStore: true, - reason: "enough-space", - availableBytes: quotaStatus.availableBytes, - }; -} - -export class StorageQuotaExceededError extends Error { - requiredBytes: number; - - constructor({ requiredBytes }: { requiredBytes: number }) { - super( - `Not enough browser storage to save a ${formatStorageBytes({ bytes: requiredBytes })} file.`, - ); - - this.name = "StorageQuotaExceededError"; - this.requiredBytes = requiredBytes; - } -} - -export function isStorageQuotaExceededError({ - error, -}: { - error: unknown; -}): boolean { - if (error instanceof StorageQuotaExceededError) { - return true; - } - - if (!(error instanceof Error)) { - return false; - } - - return ( - error.name === "QuotaExceededError" || - error.name === "NS_ERROR_DOM_QUOTA_REACHED" || - error.message.toLowerCase().includes("quota") - ); -} +const BYTE_UNITS = ["B", "KB", "MB", "GB", "TB"] as const;
+
+export const STORAGE_HEADROOM_RESERVE_BYTES = 50 * 1024 * 1024;
+
+export interface StorageQuotaStatus {
+ quotaBytes: number | null;
+ usageBytes: number | null;
+ headroomBytes: number | null;
+ availableBytes: number | null;
+}
+
+export interface StorageCapacityCheckResult {
+ canStore: boolean;
+ reason: "enough-space" | "insufficient-space" | "estimate-unavailable";
+ availableBytes: number | null;
+}
+
+function normalizeByteValue({ value }: { value: unknown }): number | null {
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
+ return null;
+ }
+
+ return value;
+}
+
+export function formatStorageBytes({ bytes }: { bytes: number }): string {
+ if (!Number.isFinite(bytes) || bytes <= 0) {
+ return "0 B";
+ }
+
+ let value = bytes;
+ let unitIndex = 0;
+
+ while (value >= 1024 && unitIndex < BYTE_UNITS.length - 1) {
+ value /= 1024;
+ unitIndex += 1;
+ }
+
+ const precision = value >= 10 || unitIndex === 0 ? 0 : 1;
+ return `${value.toFixed(precision)} ${BYTE_UNITS[unitIndex]}`;
+}
+
+export async function readStorageQuotaStatus(): Promise<StorageQuotaStatus> {
+ if (
+ typeof navigator === "undefined" ||
+ !navigator.storage ||
+ typeof navigator.storage.estimate !== "function"
+ ) {
+ return {
+ quotaBytes: null,
+ usageBytes: null,
+ headroomBytes: null,
+ availableBytes: null,
+ };
+ }
+
+ const estimate = await navigator.storage.estimate();
+ const quotaBytes = normalizeByteValue({ value: estimate.quota });
+ const usageBytes = normalizeByteValue({ value: estimate.usage });
+
+ if (quotaBytes === null || usageBytes === null) {
+ return {
+ quotaBytes,
+ usageBytes,
+ headroomBytes: null,
+ availableBytes: null,
+ };
+ }
+
+ const headroomBytes = Math.max(quotaBytes - usageBytes, 0);
+ const availableBytes = Math.max(
+ headroomBytes - STORAGE_HEADROOM_RESERVE_BYTES,
+ 0,
+ );
+
+ return {
+ quotaBytes,
+ usageBytes,
+ headroomBytes,
+ availableBytes,
+ };
+}
+
+export function evaluateStorageCapacity({
+ requiredBytes,
+ quotaStatus,
+}: {
+ requiredBytes: number;
+ quotaStatus: StorageQuotaStatus;
+}): StorageCapacityCheckResult {
+ if (quotaStatus.availableBytes === null) {
+ return {
+ canStore: true,
+ reason: "estimate-unavailable",
+ availableBytes: null,
+ };
+ }
+
+ if (requiredBytes > quotaStatus.availableBytes) {
+ return {
+ canStore: false,
+ reason: "insufficient-space",
+ availableBytes: quotaStatus.availableBytes,
+ };
+ }
+
+ return {
+ canStore: true,
+ reason: "enough-space",
+ availableBytes: quotaStatus.availableBytes,
+ };
+}
+
+export class StorageQuotaExceededError extends Error {
+ requiredBytes: number;
+
+ constructor({ requiredBytes }: { requiredBytes: number }) {
+ super(
+ `Not enough browser storage to save a ${formatStorageBytes({ bytes: requiredBytes })} file.`,
+ );
+
+ this.name = "StorageQuotaExceededError";
+ this.requiredBytes = requiredBytes;
+ }
+}
+
+export function isStorageQuotaExceededError({
+ error,
+}: {
+ error: unknown;
+}): boolean {
+ if (error instanceof StorageQuotaExceededError) {
+ return true;
+ }
+
+ if (!(error instanceof Error)) {
+ return false;
+ }
+
+ return (
+ error.name === "QuotaExceededError" ||
+ error.name === "NS_ERROR_DOM_QUOTA_REACHED" ||
+ error.message.toLowerCase().includes("quota")
+ );
+}
|
