Blob - Manipulando dados binários em javascript
Tags: javascript, blob, binary, clipboard
Published: 2022-06-19 19:17:05

Essa semana, me deparei com uma situação nova enquanto desenvolvia uma extensão para google chrome com o Quasar Framework. Precisava guardar imagens e links na área de transferência (clipboard) para serem colados posteriormente num _rich text editor_. Mais especificamente, fotos do Instagram e um link para o perfil do autor.

Após alguma pesquisa, vi que a solução passava pela utilização da classe Blob, da File Api.

### TLDR;
A solução que utilizei foi enviar pra área de transferência um Blob contendo o html formatado com as imagens e links. Para isso, o atributo src das imagens precisa conter data urls em vez de urls comuns.

Estes foram os passos utilizados:

1. Obter a imagem e convertê-la em data url:

```javascript
const img = document.createElement('img');
const response = await fetch('https://via.placeholder.com/350x150');

// a response do fetch api já te devolve um blob
const blob = await response.blob(); 

// FileReader é responsável por gerar a data url em base64
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = () => {
	img.src = reader.result;
}
// insere a imagem no body, apenas para exemplificar.
document.body.appendChild(img);

```

2. Transformar o html com a imagem em blob:

```javascript
const htmlBlob = new Blob([document.body.innerHTML], {type: 'text/html'});
```

3. Escrever o html na área de transferência via navigator api:

```javascript
// note que pode ser necessário solicitar permissão pra escrever na clipboard
const { state } = await navigator.permissions.query({
					name: 'clipboard-write',
       });
if(state === 'granted'){
	await navigator.clipboard.write([
			new ClipboardItem({ 'text/html': htmlBlob })
	]);
}
```

### O que é um Blob?

Do inglês Binary Large Object, um blob representa um conjunto de dados binários, normalmente associados a um MIME type (text/html; image/png; application/pdf etc.).

Podemos entender “dado binário”, como o oposto de dado puramente textual (text/plain). No fim das contas, ambos são bits armazenados em memória. Porém, dados textuais precisam de um encoding para “fazer sentido”, a exemplo do UTF-8 ou do ANSI.

Os blobs, por si só, não permitem muita interação com seu conjunto de dados. Usualmente são utilizados junto com demais classes, como a URL, que permite a criação de urls apontando para um blob, útil para usar em elementos como , que aceita uma url como seu source.

Anatomia do Blob:

- size

Tamanho total, em bytes. Note que, isso está mais para o length de um array do quê para o length de uma string. Isso porque existem caracteres que ocupam mais de um byte.

```javascript
new Blob(["à"], {type: "text/plain"})
// Blob {size: 2, type: 'text/plain'}

new Blob(["a"], {type: "text/plain"})
// Blob {size: 1, type: 'text/plain'}

```
- type

MIME type associado ao blob.

### Blob URLs

Como dito acima, é possível gerar uma url referenciando o conteúdo de um blob. Isso pode ser feito de duas maneiras:

### createObjectURL

A primeira é através da classe URL, chamando o método `createObjectURL( blob )`. Esse método retorna uma url no formato `blob:https://site-de-origem-do-blob/identificador-do-blob`. Esse recurso fica armazenado numa área especial do browser, enquanto o objeto que criou o blob existir. Por isso, não é possível acessar o conteúdo do blob se a aba onde ele foi gerado for fechada, por exemplo. Além disso, é necessário chamar o `revokeObjectURL( blobUrl )` tão logo o recurso não seja mais necessário. Caso contrário, o blob não será removido da memória pelo garbage collector enquanto o recurso que o criou existir. Isso é um problema, a depender do tamanho/quantidade de blobs em memória.

```javascript
// blob de string
const blob = new Blob(["uma string qualquer"], {type: "text/plain"});
const url = URL.createObjectURL(blob);

console.log(blob);
// Blob {size: 19, type: 'text/plain'}

window.open(url, '_blank');

// libera os bytes do blob reservados na memória pelo browser
URL.revokeObjectURL(url);

```

![Print do blob de texto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y9vy6ar3j13qoc1392eb.png)

```javascript
// blob de um png
fetch('https://www.google.com.br/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png')
.then(response => response.blob())
.then(blob => {
		console.log(blob);
		// Blob {size: 5969, type: 'image/png'}
    const url = URL.createObjectURL(blob);
    window.open(url, '_blank');
})
```

![Print do blob de png](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8qzcjs4rmth1pekjksks.png)

### readAsDataURL
A segunda forma de obter o blob como url é gerar uma Data URL com seu conteúdo. Data URLs são como arquivos embutidos, representados por uma URL. São formadas por 4 partes: `data:[<mediatype>][;base64],<data>`. data: é o prefixo, assim como blob:; mediatype é o MIME type do dado; base64 indica se os dados foram codificados em base64, que é uma representação textual de dados binários. É possível não usar base64, mas os dados tem que ser URL Encoded, no entanto, isso foge do nosso escopo no momento.

Para transformar um blob em Data URL, utilizamos a classe FileReader, e seu método readAsDataURL.

```javascript
// blob de um png
fetch('https://www.google.com.br/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png')
.then(response => response.blob())
.then(blob => {
		console.log(blob);
		// Blob {size: 5969, type: 'image/png'}
    
		const fileReader = new FileReader();
		fileReader.onload = () => {
			console.log(fileReader.result)
			// data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARAAAABcCAYAAACm5+q2AAAXGElEQVR4Ae1dC5QcVZm+OtOBwC6CwiqCCBIQkAWSqpqEk[...]
		
		}
		fileReader.readAsDataURL(blob);
})
```

A url gerada pode ser atribuída ao src de qualquer elemento.

### Download de Arquivos

Algumas [restrições de segurança](https://blog.mozilla.org/security/2017/11/27/blocking-top-level-navigations-data-urls-firefox-59/) se aplicam às data urls. Como exemplo, não é possível forçar o download de arquivos nesse formato, nem abrir uma data url em uma nova aba. Os browsers tratam elas, de fato, como uma “origin” diferente. Tudo isso está relacionado ao bloqueio do chamado top level navigation. Dessa forma, a maneira mais conveniente de efetuar download programático é utilizar uma blob url.

```javascript
fetch('https://via.placeholder.com/350x150')
.then(response => response.blob())
.then(blob => {
    const link = document.createElement('a');
		link.download = 'blob.png';
		link.href = URL.createObjectURL(blob);
		link.click();
		URL.revokeObjectURL(link.href);
})
```

### Considerações Finais
Usar um ou outro método de obtenção de URL fica a critério de cada aplicação. No meu caso, as imagens precisavam ser embutidas em outros documentos, em um editor fora do navegador. Dessa forma, não seria possível usar uma blob url. Note, entretanto, que o processo de gerar a Data Url pode ser mais lento que apenas referenciar uma área de memória, como faz o browser com a blob url. Além disso, as especificações de tamanho máximo do conteúdo contido numa data url variam de acordo ao navegador, partindo de cerca de 65535 caracteres até 2GB.

Para saber mais a respeito, recomendo a leitura de: [https://javascript.info/blob](https://javascript.info/blob)
[https://dev.to/kennethlum/understanding-what-a-blob-is-35ga](https://dev.to/kennethlum/understanding-what-a-blob-is-35ga)