kura
Version:
The FileSystem API abstraction library.
306 lines (270 loc) • 7.94 kB
text/typescript
import * as ba from "base64-arraybuffer";
import {
DEFAULT_BLOB_PROPS,
DEFAULT_CONTENT_TYPE,
} from "./FileSystemConstants";
import { dataUrlToBase64 } from "./FileSystemUtil";
const CHUNK_SIZE = 96 * 1024;
function decode(str: string) {
return ba.decode(str);
}
function encode(buffer: ArrayBuffer) {
return ba.encode(buffer);
}
export function isBlob(value: unknown): value is Blob {
return value instanceof Blob || toString.call(value) === "[object Blob]";
}
export function isUint8Array(value: unknown): value is Uint8Array {
return (
value instanceof Uint8Array ||
toString.call(value) === "[object Uint8Array]"
);
}
export function isBuffer(value: any): value is Buffer {
/* eslint-disable */
return (
typeof value?.constructor?.isBuffer === "function" &&
value.constructor.isBuffer(value)
);
/* eslint-enable */
}
function concatArrayBuffers(chunks: ArrayBuffer[], byteLength: number) {
const u8 = new Uint8Array(byteLength);
let pos = 0;
for (const chunk of chunks) {
u8.set(new Uint8Array(chunk), pos);
pos += chunk.byteLength;
}
return u8.buffer;
}
async function blobToArrayBufferUsingReadAsArrayBuffer(blob: Blob) {
if (blob.size === 0) {
return new ArrayBuffer(0);
}
let byteLength = 0;
const chunks: ArrayBuffer[] = [];
for (let start = 0, end = blob.size; start < end; start += CHUNK_SIZE) {
const blobChunk = blob.slice(start, start + CHUNK_SIZE);
const chunk = await new Promise<ArrayBuffer>((resolve, reject) => {
const reader = new FileReader();
reader.onerror = (ev) => {
reject(reader.error || ev);
};
reader.onload = () => {
const chunk = reader.result as ArrayBuffer;
byteLength += chunk.byteLength;
resolve(chunk);
};
reader.readAsArrayBuffer(blobChunk);
});
chunks.push(chunk);
}
return concatArrayBuffers(chunks, byteLength);
}
async function blobToArrayBufferUsingReadAsDataUrl(blob: Blob) {
const base64 = await blobToBase64(blob);
if (!base64) {
return new ArrayBuffer(0);
}
return base64ToArrayBuffer(base64);
}
async function blobToBuffer(blob: Blob) {
const arrayBuffer = await blobToArrayBuffer(blob);
return Buffer.from(arrayBuffer);
}
async function blobToArrayBuffer(blob: Blob) {
if (blob.size === 0) {
return new ArrayBuffer(0);
}
let buffer: ArrayBuffer;
if (navigator && navigator.product === "ReactNative") {
buffer = await blobToArrayBufferUsingReadAsDataUrl(blob);
} else {
buffer = await blobToArrayBufferUsingReadAsArrayBuffer(blob);
}
return buffer;
}
function base64ToBuf(base64: string) {
return Buffer.from(base64, "base64");
}
function base64ToBuffer(base64: string) {
const chunks: Buffer[] = [];
let byteLength = 0;
for (let start = 0, end = base64.length; start < end; start += CHUNK_SIZE) {
const base64chunk = base64.substr(start, CHUNK_SIZE);
const chunk = base64ToBuf(base64chunk);
byteLength += chunk.byteLength;
chunks.push(chunk);
}
const buffer = Buffer.alloc(byteLength);
let pos = 0;
for (const chunk of chunks) {
buffer.set(chunk, pos);
pos += chunk.byteLength;
}
return buffer;
}
function base64ToArrayBuffer(base64: string) {
let byteLength = 0;
const chunks: ArrayBuffer[] = [];
for (let start = 0, end = base64.length; start < end; start += CHUNK_SIZE) {
const base64chunk = base64.substr(start, CHUNK_SIZE);
const chunk = decode(base64chunk);
byteLength += chunk.byteLength;
chunks.push(chunk);
}
return concatArrayBuffers(chunks, byteLength);
}
function uint8ArrayToArrayBuffer(view: Uint8Array) {
const viewLength = view.length;
const buffer = view.buffer;
if (viewLength === buffer.byteLength) {
return buffer;
}
const newBuffer = new ArrayBuffer(viewLength);
const newView = new Uint8Array(newBuffer);
for (let i = 0; i < viewLength; i++) {
newView[i] = view[i];
}
return newBuffer;
}
export async function toBuffer(
content: Blob | BufferSource | string
): Promise<Buffer> {
if (!content) {
return Buffer.from([]);
}
let buffer: Buffer;
if (typeof content === "string") {
buffer = base64ToBuffer(content);
} else if (isBlob(content)) {
buffer = await blobToBuffer(content);
} else if (isBuffer(content)) {
buffer = content;
} else if (ArrayBuffer.isView(content)) {
buffer = Buffer.from(
content.buffer,
content.byteOffset,
content.byteLength
);
} else {
buffer = Buffer.from(content);
}
return buffer;
}
export async function toArrayBuffer(
content: Blob | BufferSource | string
): Promise<ArrayBuffer> {
if (!content) {
return new ArrayBuffer(0);
}
let buffer: ArrayBuffer;
if (typeof content === "string") {
buffer = base64ToArrayBuffer(content);
} else if (isBlob(content)) {
buffer = await blobToArrayBuffer(content);
} else if (isBuffer(content)) {
buffer = content.buffer.slice(
content.byteOffset,
content.byteOffset + content.byteLength
);
} else if (ArrayBuffer.isView(content)) {
buffer = uint8ArrayToArrayBuffer(content as Uint8Array);
} else {
buffer = content;
}
return buffer;
}
function base64ToBlob(base64: string, type = DEFAULT_CONTENT_TYPE): Blob {
try {
const buffer = base64ToArrayBuffer(base64);
return new Blob([buffer], { type });
} catch (e) {
console.warn(e, base64);
return new Blob([], DEFAULT_BLOB_PROPS);
}
}
export function toBlob(content: Blob | BufferSource | string): Blob {
if (!content) {
return new Blob([], DEFAULT_BLOB_PROPS);
}
let blob: Blob;
if (typeof content === "string") {
blob = base64ToBlob(content);
} else if (isBlob(content)) {
blob = content;
} else {
blob = new Blob([content]);
}
return blob;
}
async function blobToBase64(blob: Blob): Promise<string> {
if (blob.size === 0) {
return "";
}
const chunks: string[] = [];
for (let start = 0, end = blob.size; start < end; start += CHUNK_SIZE) {
const blobChunk = blob.slice(start, start + CHUNK_SIZE);
const chunk = await new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onerror = function (ev) {
reject(reader.error || ev);
};
reader.onload = function () {
const base64 = dataUrlToBase64(reader.result as string);
resolve(base64);
};
reader.readAsDataURL(blobChunk);
});
chunks.push(chunk);
}
return chunks.join("");
}
function arrayBufferToBase64(ab: ArrayBuffer): string {
return encode(ab);
}
function uint8ArrayToBase64(u8: Uint8Array): string {
const chunks: string[] = [];
for (let start = 0, end = u8.byteLength; start < end; start += CHUNK_SIZE) {
const u8Chunk = u8.slice(start, start + CHUNK_SIZE);
const abChunk = uint8ArrayToArrayBuffer(u8Chunk);
const chunk = arrayBufferToBase64(abChunk);
chunks.push(chunk);
}
const base64 = chunks.join("");
return base64;
}
function bufferToBase64(buffer: Buffer) {
const chunks: string[] = [];
for (
let start = 0, end = buffer.byteLength;
start < end;
start += CHUNK_SIZE
) {
const bufferChunk = buffer.slice(start, start + CHUNK_SIZE);
const chunk = bufferChunk.toString("base64");
chunks.push(chunk);
}
const base64 = chunks.join("");
return base64;
}
export async function toBase64(
content: Blob | BufferSource | string
): Promise<string> {
if (!content) {
return "";
}
let base64: string;
if (typeof content === "string") {
base64 = content;
} else if (isBlob(content)) {
base64 = await blobToBase64(content);
} else if (isBuffer(content)) {
base64 = bufferToBase64(content);
} else if (ArrayBuffer.isView(content)) {
base64 = uint8ArrayToBase64(content as Uint8Array);
} else {
base64 = uint8ArrayToBase64(new Uint8Array(content));
}
return base64;
}