Версия 1.0

«Не разводите сырость»

Александр Сушко

«Сырые» данные?

Больше про возможность
работы в браузере напрямую
с содержимым файлов
пользователя или сервера.

Предыстория...

КЭМБ

ОФД

Контур.ОФД

Передаём данные о покупках
в налоговую прямо с кассы
в режиме реального времени.

Контур.ОФД

Проекту всего 2 года

Фронт на React

Я в команде всего месяц

Задача

Реализовать загрузку
на компьютер пользователя печатных форм.

<?xml version="1.0" encoding="windows-1251"?>
<Файл xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ИдФайл="ON_DOCNPNO_6321422260632101001_6321422260632101001_0093_20180704_f9123170cc014fe5bff51ccbb10f8aa6" ВерсПрог="Налог3-А" ВерсФорм="5.01" xsi:noNamespaceSchemaLocation="ON_DOCNPNO_1_886_00_05_01_01.xsd">
  <Документ КНД="1184002" ДатаДок="28.12.2017">
    <СвОтпрДок>
      <ОтпрНО КодНО="0093" НаимНО="0093" />
    </СвОтпрДок>
    <СвПолДок>
      <ОтпрЮЛ НаимОрг="ООО &quot;ШУЛЕР СЕРВИС РУС&quot;" ИННЮЛ="6321422260" КПП="632101001" />
    </СвПолДок>
    <СвНП>
      <НПЮЛ НаимОрг="ООО &quot;ШУЛЕР СЕРВИС РУС&quot;" ИННЮЛ="6321422260" КПП="632101001" />
    </СвНП>
    <Подписант Должн="Руководитель">
      <ФИО Фамилия="Reg" Имя="Ofd" Отчество="Test" />
    </Подписант>
    <ДокНапрИзНО ИдФайлЗаяв="KO_ZVLREGKKT_0093_0093_6321422260632101001_20180704_defd99c90e3e4035809c666366675770">
      <ИнфСообДок КНД_Док="1110211" НомФайлДок="167897" ДатаФайлДок="28.12.2017" КолФайл="1">
        <ИмяФайл>1110211_0093_6321422260632101001_2b4edfd4-d702-4a6e-91e4-20d06731e178_20180704_2b4edfd4-d702-4a6e-91e4-20d06731e178</ИмяФайл>
      </ИнфСообДок>
      <ИнфСообДок КНД_Док="1110211" НомФайлДок="167897" ДатаФайлДок="28.12.2017" КолФайл="1">
        <ИмяФайл>1110211_0093_6321422260632101001_3a3dde30-6c64-43ff-b129-79b0c471dee3_20180704_3a3dde30-6c64-43ff-b129-79b0c471dee3</ИмяФайл>
      </ИнфСообДок>
    </ДокНапрИзНО>
  </Документ>
</Файл>

Нюанс

Фронтенд

Бэкенд

«Принтер»

Фронтенд

Бэкенд

«Принтер»

Скачать документ

Подготавливаем документ

...



fetch("/api")
  .then(function(response) {
    return response.json();
  })
  .then(function(data) {
    window.location.href = data.fileUrl;
  });

From: бэкендер

Метод в АПИ запилил, дёргай.
Я в отпуск, вернусь через две недели.



// console.log(response);

{
  type: "application/pdf",
  size: 12345,
  fileContent: "SGVsbG8sIHdvcmxkIQ..."
}


// console.log(response);

{
  type: "application/pdf",
  size: 12345,
  fileContent: "SGVsbG8sIHdvcmxkIQ..."
}

public class Print
{
  public Guid Id { get; set; }

  public string Type { get; set; }
  
  public int Size { get; set; }
  
  public byte[] FileContent { get; set; }        
}

From: бэкендер

По поводу byte[]... Да, там над
АПИ обвязка, которая данные перегоняет в base64, так что
тебе содержимое летит.

Base64?


fetch("/api")
  .then(function(response) {
    return response.json();
  })
  .then(function(data) {
    const { type, fileContent } = data;
    window.location.href =
      `data:${type};base64,${fileContent}`;
  });

IE и Base64

Ограничение в 32,768 символов

«Только» картинки

Свои требования к кодированию спец.символов

async function downloadFile(filename) {
  const response = await api.getFile();
  const blob = new Blob([response.content], {type: response.type});

  if (isMsBlob) {
    window.navigator.msSaveBlob(blob, filename);
  } else {
    const link = document.createElement("a");
    link.href = window.URL.createObjectURL(blob);
    link.download = filename;
    document.body.appendChild(link);
    link.click();
    setTimeout(function() {
      window.URL.revokeObjectURL(link.href);
      document.body.removeChild(link);
    }, 0);
  }
}
const binString = window.atob(content);

const buffer =
  new Uint8Array(binString.length);

for (let i = 0; i < binString.length; i++) {
  buffer[i] = binString.charCodeAt(i);
}

const blob = new Blob([buffer], { type });

Ctrl + C     Ctrl + V

async function download(filename) {
  const response = await api.getFile();

  const binString = window.atob(response.content);
  const buffer = new Uint8Array(binString.length);
  for (let i = 0; i < binString.length; i++) {
    buffer[i] = binString.charCodeAt(i);
  }
  const blob = new Blob([buffer], { type: response.type });

  if (isMsBlob) {
    window.navigator.msSaveBlob(blob, filename);
  } else {
    const link = document.createElement("a");
    link.href = window.URL.createObjectURL(blob);
    link.download = filename;
    document.body.appendChild(link);
    link.click();
    setTimeout(function() {
      window.URL.revokeObjectURL(link.href);
      document.body.removeChild(link);
    }, 0);
  }
}

FileAPI

Пересказ

FileAPI это...

Стандарт, определяющий работу
с файлами пользователя
и их содержимым прямо
в браузере.

История

2006 – First Public Draft

2009 – Working Draft

2013 – Last Call

2017 – снова Working Draft

Поддержка

Firefox, Chrome, Safari

Edge 16+ (с оговорками)

IE 10+ (с оговорками)

FileAPI

FileList и File

FileReader

Blob

Методы расширения window.URL

FileList и File

Справка: FileList, File

FileList

FileList – список File. Всё!

const files =
  document.querySelector("input[type=\"file\"]").files;

for (let file of files) { /*...*/ }

files.item(0);

File

Blob с метаданными о файле.

{
  lastModified,
  lastModifiedDate,
  name
}

File

Blob с метаданными о файле.


const file = new File(blob, filename, options);

Оговорка: IE и Edge не умеют

FileList и File

blob:

blob:

Ссылка на Blob содержимое.


blob:https://example.org/9115d58c-bcda-ff47-86e5-083e...

blob:

Ссылка на Blob содержимое.


/* Создание */
const link = window.URL.createObjectURL(file || blob);

/* Удаление */
window.URL.revokeObjectURL(link);

blob:

Ссылка живёт в рамках document

Не хранит в себе данных

blob:

FileReader

FileReader

Позволяет читать содержимое файлов пользователя.


const filereader = new FileReader();

filereader.readAsText(file || blob);

FileReader

Умеет в асинхронность

Можно прервать чтение

Умеет показывать прогресс

FileReader

Blob

Blob

Объект, содержащий бинарные данные, сырые и неизменяемые.

const blob = new Blob(
  [ blob || arrayBuffer || string ],
  { type, endings }
);

blob.slice( start, end, type );

Blob

Blob и xhr, level 2


xhr.responseType = "blob";

xhr.addEventListener("load", function() {
  html5player.srcObject = xhr.response;
});

Blob и fetch


fetch("/video-stream.php")
  .then(function(response) {
    return response.blob();
  })
  .then(function(blob) {
    html5player.srcObject = blob;
  });

Blob и Canvas

const canvas = document.querySelector("canvas");

canvas.toBlob(function(blob) {
  const link = window.URL.createObjectURL(blob);
  image.src = link;
  image.addEventListener("load", function() {
    window.URL.revokeObjectURL(link);
  });
});

Blob

Нюансы Blob

Размер Blob ограничен

IE умеет Blob, но по своему

Так причём здесь сырость?

async function download(filename) {
  const response = await api.getFile();

  const binString = window.atob(response.content);
  const buffer = new Uint8Array(binString.length);
  for (let i = 0; i < binString.length; i++) {
    buffer[i] = binString.charCodeAt(i);
  }
  const blob = new Blob([buffer], { type: response.type });

  if (isMsBlob) {
    window.navigator.msSaveBlob(blob, filename);
  } else {
    const link = document.createElement("a");
    link.href = window.URL.createObjectURL(blob);
    link.download = filename;
    document.body.appendChild(link);
    link.click();
    setTimeout(function() {
      window.URL.revokeObjectURL(link.href);
      document.body.removeChild(link);
    }, 0);
  }
}

Фронтенд

Бэкенд

«Принтер»

Фронтенд

Бэкенд

«Принтер»

Хранилище

Сервер бог,
браузер
лох

Александр Сушко

Фронтенд-разработчик, СКБ Контур

Вопросы?

Ссылки

Секретный раздел

ArrayBuffer

Контейнер определенной длины для хранения двоичных данных

Массив в истинном его понимании

Не поддерживает чтение или запись

TypedArray

Массивоподобное представление буфера с бинарными данными.

Существует «на словах».

Способы отображения

Не разводите сырость

By Alexander Sushko

Не разводите сырость

Версия презентации для UralJS #8, 5 июля 2018

  • 1,829