Особенности переноса нативного приложения в 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
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