Skip to content

Commit

Permalink
feat: add base64ToBlob, base64ToFile, blobToBase64, and fileToBase64
Browse files Browse the repository at this point in the history
  • Loading branch information
remarkablemark committed Jun 25, 2023
1 parent 5d55958 commit 1ca17ec
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 10 deletions.
24 changes: 22 additions & 2 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,28 @@ const config: Config = {
statements: 100,
},
},
preset: 'ts-jest',
testEnvironment: 'node',
projects: [
{
displayName: 'jsdom',
preset: 'ts-jest',
testEnvironment: 'jsdom',
testPathIgnorePatterns: [
'/node_modules/',
'base64-to-blob',
'base64-to-file',
],
},
{
displayName: 'node',
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: [
'/node_modules/',
'base64-to-file',
'file-to-base64',
],
},
],
};

export default config;
23 changes: 20 additions & 3 deletions module/index.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,27 @@ import { describe, it } from 'node:test';

import assert from 'assert';

import * as index from './index.mjs';
import {
base64ToBlob,
base64ToFile,
blobToBase64,
fileToBase64,
} from './index.mjs';

describe('index', () => {
it('exports object', () => {
assert.strictEqual(typeof index, 'object');
it('exports base64ToBlob', () => {
assert.strictEqual(typeof base64ToBlob, 'function');
});

it('exports base64ToFile', () => {
assert.strictEqual(typeof base64ToFile, 'function');
});

it('exports blobToBase64', () => {
assert.strictEqual(typeof blobToBase64, 'function');
});

it('exports fileToBase64', () => {
assert.strictEqual(typeof fileToBase64, 'function');
});
});
25 changes: 25 additions & 0 deletions src/base64-to-blob.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { base64ToBlob } from './base64-to-blob';

describe('base64ToBlob', () => {
it('exports function', () => {
expect(base64ToBlob).toBeInstanceOf(Function);
});

it('converts Base64 with no data to Blob', async () => {
const blob = await base64ToBlob('data:application/octet-stream;base64,');
expect(blob).toBeInstanceOf(Blob);
expect(blob.type).toBe('application/octet-stream');
});

it('converts Base64 without type to Blob', async () => {
const blob = await base64ToBlob('data:;base64,');
expect(blob).toBeInstanceOf(Blob);
expect(blob.type).toBe('text/plain;charset=us-ascii');
});

it('converts Base64 to Blob', async () => {
const blob = await base64ToBlob('data:text/plain;base64,YQ==');
expect(blob).toBeInstanceOf(Blob);
expect(blob.type).toBe('text/plain');
});
});
28 changes: 28 additions & 0 deletions src/base64-to-blob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Converts Base64 to Blob.
*
* @param base64 - Base64.
* @returns - Blob.
*/
export async function base64ToBlob(base64: string): Promise<Blob> {
const response = await fetch(base64);
let blob = await response.blob();
const mimeType = getMimeType(base64);
if (mimeType) {
// https://stackoverflow.com/a/50875615
blob = blob.slice(0, blob.size, mimeType);
}
return blob;
}

const mimeRegex = /^data:(.+);base64,/;

/**
* Gets MIME type from Base64.
*
* @param base64 - Base64.
* @returns - MIME type.
*/
function getMimeType(base64: string) {
return base64.match(mimeRegex)?.slice(1, 2).pop();
}
41 changes: 41 additions & 0 deletions src/base64-to-file.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { base64ToFile } from './base64-to-file';

describe('base64ToFile', () => {
it('exports function', () => {
expect(base64ToFile).toBeInstanceOf(Function);
});

it('converts Base64 with no data to File', async () => {
const file = await base64ToFile(
'data:application/octet-stream;base64,',
''
);
expect(file).toBeInstanceOf(File);
});

it('converts Base64 without type to File', async () => {
const file = await base64ToFile('data:;base64,', '');
expect(file).toBeInstanceOf(File);
expect(file.type).toBe('');
});

it('converts Base64 with type to File', async () => {
const file = await base64ToFile('data:text/plain;base64,YQ==', '');
expect(file).toBeInstanceOf(File);
expect(file.type).toBe('text/plain');
});

it('converts Base64 to File with filename', async () => {
const file = await base64ToFile('data:text/plain;base64,YQ==', 'filename');
expect(file.name).toBe('filename');
});

it('converts Base64 to File with type', async () => {
const file = await base64ToFile(
'data:application/octet-stream;base64,YQ==',
'',
{ type: 'text/plain ' }
);
expect(file.type).toBe('text/plain');
});
});
19 changes: 19 additions & 0 deletions src/base64-to-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* istanbul ignore file */

/**
* Converts Base64 to File.
*
* @param base64 - Base64.
* @param filename - Filename.
* @param options - Options.
* @returns - File.
*/
export async function base64ToFile(
base64: string,
filename: string,
options?: FilePropertyBag
): Promise<File> {
const response = await fetch(base64);
const blob = await response.blob();
return new File([blob], filename, options);
}
35 changes: 35 additions & 0 deletions src/blob-to-base64.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { blobToBase64 } from './blob-to-base64';

describe('blobToBase64', () => {
it('exports function', () => {
expect(blobToBase64).toBeInstanceOf(Function);
});

it('converts empty Blob without type to Base64', async () => {
const blob = new Blob([]);
expect(await blobToBase64(blob)).toBe(
'data:application/octet-stream;base64,'
);
});

it('converts Blob without type to Base64', async () => {
const blob = new Blob(['a']);
expect(await blobToBase64(blob)).toBe(
'data:application/octet-stream;base64,YQ=='
);
});

it('converts Blob with type to Base64', async () => {
const blob = new Blob(['a'], { type: 'text/plain' });
expect(await blobToBase64(blob)).toBe('data:text/plain;base64,YQ==');
});

it.skip('rejects if there is an error', async () => {
const blob = new Blob([]);
const spy = jest
.spyOn(global, 'FileReader')
.mockRejectedValueOnce('error' as never);
expect(blobToBase64(blob)).rejects.toBe('error');
spy.mockRestore();
});
});
64 changes: 64 additions & 0 deletions src/blob-to-base64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Converts Blob to Base64.
*
* @param blob - Blob.
* @returns - Base64.
*/
export async function blobToBase64(blob: Blob): Promise<string> {
return typeof FileReader === 'function'
? blobToBase64Browser(blob)
: blobToBase64Node(blob);
}

enum Event {
error = 'error',
loadend = 'loadend',
}

/**
* Converts Blob to Base64 in the browser.
*
* @param blob - Blob.
* @returns - Base64.
*/
async function blobToBase64Browser(blob: Blob): Promise<string> {
return new Promise((resolve, reject) => {
try {
const reader = new FileReader();

const handleLoadend = () => {
resolve(reader.result as string);
reader.removeEventListener(Event.loadend, handleLoadend);
};

reader.addEventListener(Event.loadend, handleLoadend);

/* istanbul ignore next */
const handleError = () => {
reject(reader.error);
reader.removeEventListener(Event.error, handleError);
};

reader.addEventListener(Event.error, handleError);

reader.readAsDataURL(blob);
} catch (error) {
/* istanbul ignore next */
reject(error);
}
});
}

const defaultMimeType = 'application/octet-stream';

/**
* Converts Blob to Base64 in Node.js.
*
* @param blob - Blob.
* @returns - Base64.
*/
async function blobToBase64Node(blob: Blob): Promise<string> {
const buffer = Buffer.from(await blob.text());
const mimeType = blob.type || defaultMimeType;
return `data:${mimeType};base64,${buffer.toString('base64')}`;
}
26 changes: 26 additions & 0 deletions src/file-to-base64.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { fileToBase64 } from './file-to-base64';

describe('fileToBase64', () => {
it('exports function', () => {
expect(fileToBase64).toBeInstanceOf(Function);
});

it('converts empty file without type to Base64', async () => {
const file = new File([], '');
expect(await fileToBase64(file)).toBe(
'data:application/octet-stream;base64,'
);
});

it('converts file without type to Base64', async () => {
const file = new File(['a'], '');
expect(await fileToBase64(file)).toBe(
'data:application/octet-stream;base64,YQ=='
);
});

it('converts file with type to Base64', async () => {
const file = new File(['a'], '', { type: 'text/plain' });
expect(await fileToBase64(file)).toBe('data:text/plain;base64,YQ==');
});
});
7 changes: 7 additions & 0 deletions src/file-to-base64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Converts File to Base64.
*
* @param file - File.
* @returns - Base64.
*/
export { blobToBase64 as fileToBase64 } from './blob-to-base64';
11 changes: 7 additions & 4 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as index from './index';
import { base64ToBlob, base64ToFile, blobToBase64, fileToBase64 } from '.';

it('exports object', () => {
expect(index).toEqual({});
});
it.each([base64ToBlob, base64ToFile, blobToBase64, fileToBase64])(
'exports %p',
(func) => {
expect(func).toBeInstanceOf(Function);
}
);
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export {};
export * from './base64-to-blob';
export * from './base64-to-file';
export * from './blob-to-base64';
export * from './file-to-base64';

0 comments on commit 1ca17ec

Please sign in to comment.