Особенности переноса нативного приложения в WEB

Тимофей Лавренюк

@geek_timofey

О себе

Из Одессы

Более 8 лет в веб-разработке

Есть опыт создания нативных приложений

"Подсел" на прогрессивные web-приложения

Mozilla Tech Speaker

Предыстория

Offline Mode

Ядро

RPC Server

Архитектура

Ядро

Шифрование

Работа с локальной БД

Работа с PDF

Общение с RPC-сервером

А что умеет?

Настал 2017 год

Нужна web-версия...

СРОЧНО НУЖНА

WEB-ВЕРСИЯ !!!11

WEB-Версия 1.0

Ядро

SPA

REST API

RPC Server

Старая архитектура

Не было режима автора

Зависило от интернет соединения

Был PHP

Старая WEB-версия

Полноценное web-приложение

Задача:

Не уступающее нативным клиентам

Увидел PHP

Есть режим автора

Работает в Offline

Не уступает нативным клиентам

Новая WEB-версия

Без PHP

Криптография

Offline

Работа с PDF

Общение с RPC-сервером

Framework + Web Worker

Progressive Web App

А не скомпилить ли все ядро в WebAssembly?

  • Ядро монолитное
  • Есть некомпилируемые библиотеки 
  • Нет возможности задействовать C++ разработчика
  • Даже если бы получилось - ОГРОМНЫЙ вес ядра

Криптография

Offline

Работа с PDF

Общение с RPC-сервером

Framework + Web Worker

Progressive Web App

+

+

+

+

+

+

Общение с RPC-сервером

Ядро

SPA

Proxy

RPC Server

Архитектура веб приложения

?

Модель OSI

  • TCP - 4 уровень (Transport)
  • RPC - 5 уровень (Session)
  • TLS - 6 уровень (Presentation)

Протокольный путь

Основная проблема

Никто не знает как работает соединение с сервером

Protobuf

Protocol Buffers

Document.proto

Document.d.ts

ArrayBuffer

Client

Server

TS Model

C++ Model

NodeJS

//TODO: красивая визуализация

  • Создает TCP Соединение
  • Логика приема данных, переконнекта и тд.
  • Конвертация данных через Protobuf

Криптография

Offline

Работа с PDF

Общение с RPC-сервером

Framework + Web Worker

Progressive Web App

Криптография

Блочное шифрование

Основная проблема

Никто не знает как работает шифрование в проекте

RSA

AES

Основные алгоритмы

RSA

AES

cipher = encrypt(block, key) // шифруем block с помощью key
block = decrypt(cipher, key) // расшифровываем cipher с помощью key

Как реализовать алгоритмы шифрования?

  • JS библиотека
  • Web Crypto API
  • OpenSSL -> WebAssembly

JS библиотека

+ Поддержка RSA

+ Изоморфный код

- Производительность

- Размер

Web Crypto API

- Неполная поддержка RSA

- Изоморфный код

+ Размер

+ Производительность

OpenSSL -> WebAssembly

Алгоритм PBKDF2

web crypto api

OpenSSL -> WebAssembly

+ Изоморфный код

+ Производительность

- Размер

Node-Forge

JS библиотека

NodeForge и Фэйлы

1) Perfomance перед релизом

2) Edge и краш

Идентификаторы

Бонус

Идентификатор здорового человека

b3d38902-9d5b-45df-931b-580364a45c06

23TplPdS

65876348756

String

String

Int

128 bit Int

Идентификатор у нас

1473574375358436568436564568648623850000

Number.MAX_SAFE_INTEGER

=

9007199254740991 (2 53)

 340282366920938463463374607431768211456 (2128)

128bit MAX

=

Low Bit

High  Bit

Sign Bit

Long.js

"4713949395975284736:-4954997526967152640"

1473574375358436568436564568648623850000

{  
   "high":{  
      "high":34234234,
      "low":-437598348
   },
   "low":{  
      "high":97163629,
      "low":-748735
   }
}

"F8C044003D248000:69C74800E626C800"

Поддержка WebAssembly

Генерация 128 bit Int на клиенте

function generate32BitLong() {
  const uuid = uuidv4().replace(/-/g, '');

  const highHex = uuid.substr(0, 16);
  const lowHex = uuid.substr(16, 16);

  return Long.fromValue({
    high: parseInt(highHex, 16),
    low:  parseInt(lowHex, 16),
    unsigned: true
  });
}
message IdType {
    optional uint64          high                = 1;
    optional uint64          low                 = 2;
}

id_type.proto

function generateDocumentId() {
  return coresign.IdType.create({
    high: new Long({
        high: generate32BitLong(),
        low: generate32BitLong()
    }),
    low: new Long({
        high: generate32BitLong(),
        low: generate32BitLong()
    })
  });
};

BigInt

67+

65+

(flag)

54+

65+

10+

Криптография

Offline

Работа с PDF

Общение с RPC-сервером

Framework + Web Worker

Progressive Web App

Offline

Как мы разработали полностью Offline First приложение с Persistent Storage

Статика

Данные пользователя

Файлы

Что хранить в Offline?

Работа с цветом

Бонус

Описание цвета здорового человека

148 094 234 (RGB)

234B9A (HEX)

Описание цвета у нас

216809089 (32bit Int)

Как конвертировать Int в RGB

RED

GREEN

BLUE

int >>> 24 & 255
int >>> 8 & 255
int >>> 16 & 255

Bitwise operator

Битовые операции обращаются со своими операндами как с 32-х разрядными последовательностями нулей и единиц, а не как с десятичными, восьмеричными или шестнадцатиричными числами.

MDN

& | ^ ~ << >> <<<

Bitwise operator

216809089                        (base 10)
=
00001100111011000011111010000001 (base 2)

216809089 >>> 16                 (base 10)
=
00000000000000000000110011101100 (base 2)
=
3308                             (base 10)

255                              (base 10)
=
00000000000000000000000011111111 (base 2)

3308 & 255                       (base 10)
=
00000000000000000000000011101100 (base 2)
=
236                              (base 10)

++

Целочисленное переполнение

rgb(254, 182, 69) = -21609089

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

Сервер не воспринимает отрицательный Int

Известные примеры

Глюк в SimCity 2000.

Здесь дело в том, что бюджет игрока стал очень большим, а после перехода через 231 внезапно стал отрицательным. Игра оканчивается поражением.

Глюк из Diablo III.

Из-за одного из изменений патча 1.0.8, игровая экономика сломалась. Максимальную сумму для сделок повысили с 1 млн до 10 млн. Стоимость покупки переполнялась через 32-битный тип, а при отмене операции возвращалась полная сумма. То есть игрок оставался с прибылью в 232 игровой валюты

Angular Ivy Renderer

Криптография

Offline

Работа с PDF

Общение с RPC-сервером

Framework + Web Worker

Progressive Web App

Framework

Data Layer

UI

Redux

Web Worker

Indexed DB

Как мы разработали полностью Offline First приложение с Persistent Storage

Криптография

Offline

Работа с PDF

Общение с RPC-сервером

Framework + Web Worker

Progressive Web App

Работа с PDF

Работа с PDF

  • UI
  • Рендеринг документа
  • Экспорт документа с наложением аннотаций

UI + WASM Модуль

Дорого

Надо делать все своими руками

Рендеринг документа

С++ библиотека

PDF Hummus

А не скомпилить ли ее в WebAssembly?

Почему это не так просто?

WASM

WASM

Я

Я

В итоге:

pdf-lib.js

на клиенте

На сервере

C++ PDF Hummus

HummusJS

HummusRecipe

var cxt = pdfWriter.startPageContentContext(page);
cxt.q()
   .cm(2,0,0,2,0,0)
   .Q()
   .y(inX1,inY1,inX3,inY3)
   .h();
   .re(inLeft,inBottom,inWidth,inHeight);
   .q();
   .Q(); 
   .cm(inA,inB,inC,inD,inE,inF);
   .w(inLineWidth);
   .J(inLineCapStyle);
   .j(inLineJoinStyle);
   .M(inMiterLimit);

HummusJS

pdfDoc
    // edit 1st page
    .editPage(1)
    .text('Add some texts to an existing pdf file', 150, 300)
    .rectangle(20, 20, 40, 100)
    .comment('Add 1st comment annotaion', 200, 300)
    .image('/path/to/image.jpg', 20, 100,
        {width: 300, keepAspectRatio: true})
    .endPage()
    // edit 2nd page
    .editPage(2)
    .comment('Add 2nd comment annotaion', 200, 100)
    .endPage()
    // end and save
    .endPDF();

HummusRecipe

Вывод

WebAssembly не так прост, когда чужую библиотеку компилируешь

Рендеринг PDF на сервере

Бонус

Задача:

История пользования документом

Создание обьектов в PDF руками

PDFPage* pdfPage = new PDFPage();
pdfPage->SetMediaBox(PDFRectangle(0,0,595,842));
PageContentContext* pageContentContext =
    pdfWriter.StartPageContentContext(pdfPage);
// Start adding content to page
// Draw a Line, stroke
pageContentContext->q();
pageContentContext->w(2);
pageContentContext->K(0,0,1,0);
pageContentContext->m(10,500);
pageContentContext->l(30,700);
pageContentContext->s();
pageContentContext->Q();
// Draw a Polygon, stroke
pageContentContext->q();
pageContentContext->w(2);
pageContentContext->K(0,1,0,0);
pageContentContext->m(40,500);
pageContentContext->l(60,700);
pageContentContext->l(160,700);
pageContentContext->l(140,500);
pageContentContext->s();
pageContentContext->Q();

Решение:

Headless Chrome

const browser = await puppeteer.launch({
    headless: true
  });
  const page = await browser.newPage();
  await page.emulateMedia('screen');
  await page.goto(
    `data:text/html,${compiledTemplate}`, {
      waitUntil: 'networkidle2'
    }
  );

  const pdfBuffer = await page.pdf();

Генерация PDF с помощью Pupputeer

В итоге:

  • Создание PDF через HTML - OK
  • Экономия UI разработки  на отображения истории
  • Есть небольшие ограничения в дизайне
  • C CSS Print стилями вообще красота

Криптография

Offline

Работа с PDF

Общение с RPC-сервером

Framework + Web Worker

Progressive Web App

Прогрессивное веб приложение

Как сделать веб-приложение нативнее, а пользователя счастливее

Хранение Credentials

с помощью Credential Management API

Браузерные  API

  • Storage API
  • Permission API
  • Clipboard API
  • Web Payments API

KeepsSolid Sign Mac

Web Payment API

Бонус -  Apple Pay

 const canMakePayment = await paymentRequest.canMakePayment();

if (canMakePayment) {
    const paymentRequest = stripe.paymentRequest({
      country: 'US',
      currency: 'usd',
      total: {
        label: 'Demo',
        amount: 1000,
      }
    });
}

Поддержка

Stripe Fallback

Криптография

Offline

Работа с PDF

Общение с RPC-сервером

Framework + Web Worker

Progressive Web App

Выводы

Азы Computer Sience пригодились

  • Компьютерная арифметика
  • Компьютерные сети
  • Языки и компиляторы
  • Криптография

Что получилось перенести из натива в WEB-приложение:

  • Работа с большими числами
  • Алгоритмы криптографии
  • Работа с PDF
  • Хранение данных offline с помощью IndexedDB
  • "Многопоточные" операции благодаря Web Worker-ам
  • Прогрессивные "возможности" с помощью  браузерных API
  • Слождные операции через WASM модули

Чем WEB-приложение уступает нативу:

  • Производительность
  • Безопасность
  • Браузерные API
  • Javascript (Typescript)

Спасибо за внимание

Тимофей Лавренюк

@geek_timofey

Особенности переноса нативного приложения в Web. Lite

By Timofey Lavrenyuk

Особенности переноса нативного приложения в Web. Lite

  • 464