Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Перепись приложения. Нативного. На JS. Done. | OdessaFrontend Meetup #10

13 views

Published on

KeepSolid Sign — приложение для подписи документов под все популярные платформы. Его ядро написано на C++ и однажды поступила задача сделать веб-версию. Тимофей Лавренюк рассказывает о том, через что получилось пройти, чтобы в итоге сделать веб-приложение не хуже нативного.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Перепись приложения. Нативного. На JS. Done. | OdessaFrontend Meetup #10

  1. 1. Перепись приложения. Нативного.Перепись приложения. Нативного. На JS. DoneНа JS. Done Тимофей Лавренюк @geek_timofey 1
  2. 2. О себеО себе 2
  3. 3. О проектеО проекте 3 . 1
  4. 4. 3 . 2
  5. 5. Архитектура 3 . 3
  6. 6. Архитектура 3 . 3
  7. 7. ЯдроЯдро Архитектура 3 . 3
  8. 8. ЯдроЯдро RPC ServerRPC ServerАрхитектура 3 . 3
  9. 9. ЯдроЯдро А что умеет?А что умеет? 3 . 4
  10. 10. ЯдроЯдро Работа с локальной БДА что умеет?А что умеет? 3 . 4
  11. 11. ЯдроЯдро ШифрованиеРабота с локальной БДА что умеет?А что умеет? 3 . 4
  12. 12. ЯдроЯдро ШифрованиеРабота с локальной БД Общение с RPC-сервером А что умеет?А что умеет? 3 . 4
  13. 13. ЯдроЯдро ШифрованиеРабота с локальной БД Работа с PDFОбщение с RPC-сервером А что умеет?А что умеет? 3 . 4
  14. 14. Настал 2017 годНастал 2017 год 3 . 5
  15. 15. Настал 2017 годНастал 2017 год Нужна web-версия... 3 . 5
  16. 16. СРОЧНО НУЖНАСРОЧНО НУЖНА WEB-ВЕРСИЯ !!!11WEB-ВЕРСИЯ !!!11 3 . 6
  17. 17. 3 . 7
  18. 18. WEB-ВерсияWEB-Версия 1.01.0 3 . 8
  19. 19. Старая архитектура 3 . 9
  20. 20. SPASPA Старая архитектура 3 . 9
  21. 21. SPASPA REST APIREST API ЯдроЯдро Старая архитектура 3 . 9
  22. 22. SPASPA REST APIREST API ЯдроЯдро RPC ServerRPC ServerСтарая архитектура 3 . 9
  23. 23. Старая WEB-версия 3 . 10
  24. 24. Не было режима автораНе было режима автора Старая WEB-версия 3 . 10
  25. 25. Не было режима автораНе было режима автора Зависило от интернетЗависило от интернет соединениясоединения Старая WEB-версия 3 . 10
  26. 26. Не было режима автораНе было режима автора Зависило от интернетЗависило от интернет соединениясоединения Был PHPБыл PHP Старая WEB-версия 3 . 10
  27. 27. 3 . 11
  28. 28. Полноценное web-приложениеПолноценное web-приложение Задача:Задача: 3 . 12
  29. 29. Полноценное web-приложениеПолноценное web-приложение Задача:Задача: Не уступающее нативным клиентам 3 . 12
  30. 30. 3 . 13
  31. 31. Увидел PHP 3 . 14
  32. 32. Пришлось переписать веб приложение 3 . 15
  33. 33. Новая WEB-версия 3 . 16
  34. 34. Есть режим автораЕсть режим автора Новая WEB-версия 3 . 16
  35. 35. Есть режим автораЕсть режим автора Работает в OfflineРаботает в Offline Новая WEB-версия 3 . 16
  36. 36. Есть режим автораЕсть режим автора Работает в OfflineРаботает в Offline Не уступает нативнымНе уступает нативным клиентамклиентам Новая WEB-версия 3 . 16
  37. 37. Криптография Offline + Web Worker Работа с PDFОбщение с RPC-сервером Framework Progressive Web App 4
  38. 38. КриптографияКриптография 5 . 1
  39. 39. Основная проблемаОсновная проблема 5 . 2
  40. 40. Основная проблемаОсновная проблема Никто не знает как работает шифрование в проекте 5 . 2
  41. 41. 5 . 3
  42. 42. 5 . 4
  43. 43. 5 . 5
  44. 44. RSARSA AESAES Основные алгоритмы 5 . 6
  45. 45. RSARSA 5 . 7
  46. 46. AESAES cipher = encrypt(block, key) // шифруем block с помощью key block = decrypt(cipher, key) // расшифровываем cipher с помощью key 5 . 8
  47. 47. Как реализовать алгоритмыКак реализовать алгоритмы шифрования?шифрования? 5 . 9
  48. 48. 5 . 10
  49. 49. JS библиотека 5 . 10
  50. 50. JS библиотека Web Crypto API 5 . 10
  51. 51. JS библиотека Web Crypto API OpenSSL -> WebAssembly 5 . 10
  52. 52. JS библиотекаJS библиотека 5 . 11
  53. 53. JS библиотекаJS библиотека + Изоморфный код 5 . 11
  54. 54. JS библиотекаJS библиотека + Поддержка RSA + Изоморфный код 5 . 11
  55. 55. JS библиотекаJS библиотека + Поддержка RSA + Изоморфный код - Производительность 5 . 11
  56. 56. JS библиотекаJS библиотека + Поддержка RSA + Изоморфный код - Производительность - Размер 5 . 11
  57. 57. Web Crypto APIWeb Crypto API 5 . 12
  58. 58. Web Crypto APIWeb Crypto API + Размер 5 . 12
  59. 59. Web Crypto APIWeb Crypto API + Размер + Производительность 5 . 12
  60. 60. Web Crypto APIWeb Crypto API - Неполная поддержка RSA + Размер + Производительность 5 . 12
  61. 61. Web Crypto APIWeb Crypto API - Неполная поддержка RSA - Изоморфный код + Размер + Производительность 5 . 12
  62. 62. Алгоритм PBKDF2Алгоритм PBKDF2 5 . 13
  63. 63. OpenSSL -> WebAssemblyOpenSSL -> WebAssembly 5 . 14
  64. 64. OpenSSL -> WebAssemblyOpenSSL -> WebAssembly + Изоморфный код 5 . 14
  65. 65. OpenSSL -> WebAssemblyOpenSSL -> WebAssembly + Изоморфный код + Производительность 5 . 14
  66. 66. OpenSSL -> WebAssemblyOpenSSL -> WebAssembly + Изоморфный код + Производительность - Размер 5 . 14
  67. 67. JS библиотекаJS библиотека 5 . 15
  68. 68. Node-ForgeNode-Forge JS библиотекаJS библиотека 5 . 15
  69. 69. Node-ForgeNode-Forge JS библиотекаJS библиотека Про улучшение оптимизации - в следующих слайдах... 5 . 15
  70. 70. NodeForge FailsNodeForge Fails 5 . 16
  71. 71. Offline + Web WorkerOffline + Web Worker 6 . 1
  72. 72. Что хранить в Offline?Что хранить в Offline? 6 . 2
  73. 73. СтатикаСтатика Что хранить в Offline?Что хранить в Offline? 6 . 2
  74. 74. СтатикаСтатика Что хранить в Offline?Что хранить в Offline? 6 . 2
  75. 75. СтатикаСтатика Данные пользователяДанные пользователя Что хранить в Offline?Что хранить в Offline? 6 . 2
  76. 76. СтатикаСтатика Данные пользователяДанные пользователя Что хранить в Offline?Что хранить в Offline? 6 . 2
  77. 77. СтатикаСтатика Данные пользователяДанные пользователя ФайлыФайлы Что хранить в Offline?Что хранить в Offline? 6 . 2
  78. 78. СтатикаСтатика Данные пользователяДанные пользователя ФайлыФайлы Что хранить в Offline?Что хранить в Offline? 6 . 2
  79. 79. СтатикаСтатика 7 . 1
  80. 80. Service WorkerService Worker 7 . 2
  81. 81. 7 . 3
  82. 82. 7 . 3
  83. 83. { "globDirectory": "dist", "globPatterns": [ "index.html", "*.js", "assets/**/*.{png,svg}", "assets/*.{png,svg}" ], "swSrc": "src/service-workers/main.worker.js", "swDest": "dist/service-worker.js" } workbox.config.js 7 . 4
  84. 84. { "assets": [ "src/assets", "src/service-workers", "src/manifest.json", { "glob": "workbox-sw.js", "input": "node_modules/workbox-sw/build", "output": "./workbox-3.5.0" }, { "glob": "workbox-core.dev.js", "input": "node_modules/workbox-core/build/", "output": "./workbox-3.5.0" }, { "glob": "workbox-precaching.dev.js", "input": "node_modules/workbox-precaching/build/", "output": "./workbox-3.5.0" } ], } angular.json 7 . 5
  85. 85. importScripts('workbox-3.5.0/workbox-sw.js'); workbox.setConfig({ debug: true, modulePathPrefix: 'workbox-3.5.0/' }); workbox.skipWaiting(); workbox.clientsClaim(); workbox.precaching.precacheAndRoute([]); main.worker.js 7 . 6
  86. 86. workbox injectManifest 7 . 7
  87. 87. workbox injectManifest workbox.precaching.precacheAndRoute([ { "url": "index.html", "revision": "4f8109353581284e76b88e568d642376" }, { "url": "0.js", "revision": "2af14762103b4c1f18e620bd30a53d2e" }, { "url": "main.js", "revision": "ef2fb7f5913e6614c9ee3621ad299d1b" } ]) service-worker.js 7 . 7
  88. 88. СтатикаСтатика 7 . 8
  89. 89. Данные пользователяДанные пользователя 8 . 1
  90. 90. Service WorkerService Worker 8 . 2
  91. 91. Persistent StoragePersistent Storage 8 . 3
  92. 92. 8 . 4
  93. 93. LocalStorageLocalStorage 8 . 4
  94. 94. LocalStorageLocalStorage Небольшой лимит 8 . 4
  95. 95. File APIFile API LocalStorageLocalStorage Небольшой лимит 8 . 4
  96. 96. File APIFile API FileWriter deprecated LocalStorageLocalStorage Небольшой лимит 8 . 4
  97. 97. File APIFile API FileWriter deprecated CookiesCookies LocalStorageLocalStorage Небольшой лимит 8 . 4
  98. 98. File APIFile API FileWriter deprecated CookiesCookies WATTT?? LocalStorageLocalStorage Небольшой лимит 8 . 4
  99. 99.  CacheStorage CacheStorage File APIFile API FileWriter deprecated CookiesCookies WATTT?? LocalStorageLocalStorage Небольшой лимит 8 . 4
  100. 100.  CacheStorage CacheStorage Нет гибкости File APIFile API FileWriter deprecated CookiesCookies WATTT?? LocalStorageLocalStorage Небольшой лимит 8 . 4
  101. 101.  CacheStorage CacheStorage Нет гибкости File APIFile API FileWriter deprecated CookiesCookies WATTT?? WebSQLWebSQL LocalStorageLocalStorage Небольшой лимит 8 . 4
  102. 102.  CacheStorage CacheStorage Нет гибкости File APIFile API FileWriter deprecated CookiesCookies WATTT?? WebSQLWebSQL Deprecated LocalStorageLocalStorage Небольшой лимит 8 . 4
  103. 103.  CacheStorage CacheStorage Нет гибкости File APIFile API FileWriter deprecated CookiesCookies WATTT?? WebSQLWebSQL Deprecated IndexedDBIndexedDB LocalStorageLocalStorage Небольшой лимит 8 . 4
  104. 104.  CacheStorage CacheStorage Нет гибкости File APIFile API FileWriter deprecated CookiesCookies WATTT?? WebSQLWebSQL Deprecated IndexedDBIndexedDB Большой лимит Хорошая поддержка Не Deprecated LocalStorageLocalStorage Небольшой лимит 8 . 4
  105. 105. IndexedDBIndexedDB 8 . 5
  106. 106. IndexedDBIndexedDB объектно-ориентированная база данныхобъектно-ориентированная база данных 8 . 5
  107. 107. IndexedDBIndexedDB объектно-ориентированная база данныхобъектно-ориентированная база данных хранит обьекты, проиндексированные с ключомхранит обьекты, проиндексированные с ключом 8 . 5
  108. 108. IndexedDBIndexedDB объектно-ориентированная база данныхобъектно-ориентированная база данных хранит обьекты, проиндексированные с ключомхранит обьекты, проиндексированные с ключом выполнение операций происходит асинхронновыполнение операций происходит асинхронно 8 . 5
  109. 109. IndexedDBIndexedDB объектно-ориентированная база данныхобъектно-ориентированная база данных хранит обьекты, проиндексированные с ключомхранит обьекты, проиндексированные с ключом выполнение операций происходит асинхронновыполнение операций происходит асинхронно умеет хранить JS-объекты и блобыумеет хранить JS-объекты и блобы 8 . 5
  110. 110. IndexedDBIndexedDB объектно-ориентированная база данныхобъектно-ориентированная база данных хранит обьекты, проиндексированные с ключомхранит обьекты, проиндексированные с ключом выполнение операций происходит асинхронновыполнение операций происходит асинхронно имеет жутко неудобное низкоуровневое APIимеет жутко неудобное низкоуровневое API умеет хранить JS-объекты и блобыумеет хранить JS-объекты и блобы 8 . 5
  111. 111. https://caniuse.bitsofco.de/embed/index.html? feat=indexeddb&periods=future_2,future_1,current&accessible- colours=false ПоддержкаПоддержка 8 . 6
  112. 112. DebugDebug 8 . 7
  113. 113. IndexedDB в боюIndexedDB в бою 9 . 1
  114. 114. 9 . 2
  115. 115. 1. Открыть или создать базу 9 . 2
  116. 116. 1. Открыть или создать базу var open = indexedDB.open("MyDatabase", 1); 9 . 2
  117. 117. 1. Открыть или создать базу var open = indexedDB.open("MyDatabase", 1); 2. Создать Schema 9 . 2
  118. 118. 1. Открыть или создать базу var open = indexedDB.open("MyDatabase", 1); 2. Создать Schema open.onupgradeneeded = () => { const db = open.result; const store = db.createObjectStore("MyObjectStore", {keyPath: "id"}); const index = store.createIndex("NameIndex", ["name.last", "name.first"]); }; 9 . 2
  119. 119. 9 . 3
  120. 120. 3. Создать транзакцию 9 . 3
  121. 121. 3. Создать транзакцию open.onsuccess = () => { const db = open.result const tx = db.transaction("MyObjectStore", "readwrite") const store = tx.objectStore("MyObjectStore"); const index = store.index("NameIndex"); } 9 . 3
  122. 122. 4. Запросить данные 3. Создать транзакцию open.onsuccess = () => { const db = open.result const tx = db.transaction("MyObjectStore", "readwrite") const store = tx.objectStore("MyObjectStore"); const index = store.index("NameIndex"); } 9 . 3
  123. 123. 4. Запросить данные const getJohn = store.get(12345); // по id const getBob = index.get(["Smith", "Bob"]); // через index 3. Создать транзакцию open.onsuccess = () => { const db = open.result const tx = db.transaction("MyObjectStore", "readwrite") const store = tx.objectStore("MyObjectStore"); const index = store.index("NameIndex"); } 9 . 3
  124. 124. 9 . 4
  125. 125. 5. Получить данные 9 . 4
  126. 126. 5. Получить данные getJohn.onsuccess = () => { console.log(getJohn.result.name.first); // => "John" }; getBob.onsuccess = () => { console.log(getBob.result.name.first); // => "Bob" }; 9 . 4
  127. 127. 5. Получить данные getJohn.onsuccess = () => { console.log(getJohn.result.name.first); // => "John" }; getBob.onsuccess = () => { console.log(getBob.result.name.first); // => "Bob" }; 5. Закрыть транзакцию 9 . 4
  128. 128. 5. Получить данные getJohn.onsuccess = () => { console.log(getJohn.result.name.first); // => "John" }; getBob.onsuccess = () => { console.log(getBob.result.name.first); // => "Bob" }; 5. Закрыть транзакцию tx.oncomplete = function() { db.close(); }; 9 . 4
  129. 129. Boilerblate!!!1Boilerblate!!!1 9 . 5
  130. 130. Недостатки чистого IndexedDBНедостатки чистого IndexedDB 9 . 6
  131. 131. Недостатки чистого IndexedDBНедостатки чистого IndexedDB Тяжело поддерживать 9 . 6
  132. 132. Недостатки чистого IndexedDBНедостатки чистого IndexedDB Тяжело поддерживать Не поддержки JOIN'ов 9 . 6
  133. 133. Недостатки чистого IndexedDBНедостатки чистого IndexedDB Тяжело поддерживать Не поддержки JOIN'ов Нельзя частично обновить документ 9 . 6
  134. 134. Недостатки чистого IndexedDBНедостатки чистого IndexedDB Тяжело поддерживать Не поддержки JOIN'ов Нельзя частично обновить документ Скудная сортировка 9 . 6
  135. 135. Недостатки чистого IndexedDBНедостатки чистого IndexedDB Тяжело поддерживать Не поддержки JOIN'ов Нельзя частично обновить документ Скудная сортировка Auto-commit транзакций 9 . 6
  136. 136. "Никто не использует IndexedDB в чистом виде" - Все JS-разработчики 9 . 7
  137. 137. "Никто не использует IndexedDB в чистом виде" - Все JS-разработчики https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API 9 . 7
  138. 138. 9 . 8
  139. 139. БиблиотекиБиблиотеки ОберткиОбертки 9 . 8
  140. 140. БиблиотекиБиблиотеки ОберткиОбертки DB EngineDB Engine 9 . 8
  141. 141. БиблиотекиБиблиотеки ОберткиОбертки Idb ZangoDb MiniMongo jsStore PouchDB Dexie LocalForage DB EngineDB Engine 9 . 8
  142. 142. БиблиотекиБиблиотеки ОберткиОбертки Idb ZangoDb MiniMongo jsStore PouchDB Dexie LocalForage DB EngineDB Engine YDN-DB AlaSQL Lovefield 9 . 8
  143. 143. 9 . 9
  144. 144. 9 . 10
  145. 145. КроссбраузерностьКроссбраузерность 9 . 10
  146. 146. КроссбраузерностьКроссбраузерность Chrome Firefox IE 11+, Edge Safari 10+ 9 . 10
  147. 147. SQL-подобный APISQL-подобный APIКроссбраузерностьКроссбраузерность Chrome Firefox IE 11+, Edge Safari 10+ 9 . 10
  148. 148. SQL-подобный APISQL-подобный API select, insert, update, delete group by, order by, limit, skip join КроссбраузерностьКроссбраузерность Chrome Firefox IE 11+, Edge Safari 10+ 9 . 10
  149. 149. SQL-подобный APISQL-подобный API select, insert, update, delete group by, order by, limit, skip join КроссбраузерностьКроссбраузерность Chrome Firefox IE 11+, Edge Safari 10+ Отличная производительностьОтличная производительность Оптимизация и анализ запросов 9 . 10
  150. 150. Создание SchemaСоздание Schema 9 . 11
  151. 151. Создание SchemaСоздание Schema const schemaBuilder = lf.schema.create('KEEPSOLID_SIGN_DB', 1.0); schemaBuilder.createTable('Documents') .addColumn('id', lf.Type.STRING) .addColumn('parentId', lf.Type.OBJECT) .addColumn('type', lf.Type.STRING) .addColumn('signOrder', lf.Type.NUMBER) .addColumn('encryptionKey', lf.Type.STRING) .addIndex('idxSignOrder', ['signOrder'], false, lf.Order.DESC); .addPrimaryKey(['id']); schemaBuilder.connect().then((db) => { // Можно работать с базой }); 9 . 11
  152. 152. Создание SchemaСоздание Schema const schemaBuilder = lf.schema.create('KEEPSOLID_SIGN_DB', 1.0); schemaBuilder.createTable('Documents') .addColumn('id', lf.Type.STRING) .addColumn('parentId', lf.Type.OBJECT) .addColumn('type', lf.Type.STRING) .addColumn('signOrder', lf.Type.NUMBER) .addColumn('encryptionKey', lf.Type.STRING) .addIndex('idxSignOrder', ['signOrder'], false, lf.Order.DESC); .addPrimaryKey(['id']); schemaBuilder.connect().then((db) => { // Можно работать с базой }); 9 . 11
  153. 153. Типы полейТипы полей 9 . 12
  154. 154. Типы полейТипы полей String 9 . 12
  155. 155. Типы полейТипы полей String Number 9 . 12
  156. 156. Типы полейТипы полей String Number Integer (32bit) 9 . 12
  157. 157. Типы полейТипы полей String Number Integer (32bit) Boolean 9 . 12
  158. 158. Типы полейТипы полей String Number Integer (32bit) Boolean Object 9 . 12
  159. 159. Типы полейТипы полей String Number Integer (32bit) Boolean Object Date 9 . 12
  160. 160. Типы полейТипы полей String Number Integer (32bit) Boolean Object Date Array Buffer 9 . 12
  161. 161. SQL-подобный APISQL-подобный API SQL Lovefield 9 . 13
  162. 162. SQL-подобный APISQL-подобный API SQL Lovefield SELECT * FROM Documents WHERE type = "TEMPLATE" 9 . 13
  163. 163. SQL-подобный APISQL-подобный API SQL Lovefield SELECT * FROM Documents WHERE type = "TEMPLATE" Database .select() .from(document) .where(document.type.eq('TEMPLATE')) .exec() 9 . 13
  164. 164. SQL-подобный APISQL-подобный API SQL Lovefield SELECT * FROM Documents WHERE type = "TEMPLATE" Database .select() .from(document) .where(document.type.eq('TEMPLATE')) .exec() SELECT encryptionKey FROM Documents WHERE signOrder >= 1 ORDER BY signOrder DESC LIMIT 10 9 . 13
  165. 165. SQL-подобный APISQL-подобный API SQL Lovefield SELECT * FROM Documents WHERE type = "TEMPLATE" Database .select() .from(document) .where(document.type.eq('TEMPLATE')) .exec() SELECT encryptionKey FROM Documents WHERE signOrder >= 1 ORDER BY signOrder DESC LIMIT 10 Database .select(document.encryptionKey) .from(document) .where(document.signOrder.gte(1)) .orderBy(document.signOrder, lf.Order.DESC) .limit(10) .exec() 9 . 13
  166. 166. SQL Lovefield 9 . 14
  167. 167. SQL Lovefield SELECT * FROM Documents d, Files f WHERE d.fileId = f.id AND d.id = '123' 9 . 14
  168. 168. SQL Lovefield SELECT * FROM Documents d, Files f WHERE d.fileId = f.id AND d.id = '123' Database .select() .from(document, file) .where(lf.op.and( document.fileId.eq(file.id), document.id.eq('123'), )) .exec() 9 . 14
  169. 169. SQL Lovefield SELECT * FROM Documents d, Files f WHERE d.fileId = f.id AND d.id = '123' Database .select() .from(document, file) .where(lf.op.and( document.fileId.eq(file.id), document.id.eq('123'), )) .exec() SELECT * FROM document INNER JOIN file ON document.fileId = file.id WHERE document.id = '123' 9 . 14
  170. 170. SQL Lovefield SELECT * FROM Documents d, Files f WHERE d.fileId = f.id AND d.id = '123' Database .select() .from(document, file) .where(lf.op.and( document.fileId.eq(file.id), document.id.eq('123'), )) .exec() SELECT * FROM document INNER JOIN file ON document.fileId = file.id WHERE document.id = '123' Database .select() .from(document) .innerJoin( file, document.fileId.eq(file.id) ) .where(document.id.eq('123')) .exec() 9 . 14
  171. 171. function idb_and (index1, keyRange1, index2, keyRange2, onfound, onfinish) { var openCursorRequest1 = index1.openCursor(keyRange1); var openCursorRequest2 = index2.openCursor(keyRange2); assert(index1.objectStore === index2.objectStore); var primKey = index1.objectStore.keyPath; var set = {}; var resolved = 0; function complete() { if (++resolved === 2) onfinish(); } function union(item) { var key = JSON.stringify(item[primKey]); if (!set.hasOwnProperty(key)) { set[key] = true; onfound(item); } } openCursorRequest1.onsuccess = function (event) { var cursor = event.target.result; if (cursor) { union(cursor.value); } else { complete(); } } openCursorRequest2.onsuccess = function (event) { var cursor = event.target.result; if (cursor) { union(cursor.value); } else { complete(); } } } Чистый API 9 . 15
  172. 172. function idb_and (index1, keyRange1, index2, keyRange2, onfound, onfinish) { var openCursorRequest1 = index1.openCursor(keyRange1); var openCursorRequest2 = index2.openCursor(keyRange2); assert(index1.objectStore === index2.objectStore); var primKey = index1.objectStore.keyPath; var set = {}; var resolved = 0; function complete() { if (++resolved === 2) onfinish(); } function union(item) { var key = JSON.stringify(item[primKey]); if (!set.hasOwnProperty(key)) { set[key] = true; onfound(item); } } openCursorRequest1.onsuccess = function (event) { var cursor = event.target.result; if (cursor) { union(cursor.value); } else { complete(); } } openCursorRequest2.onsuccess = function (event) { var cursor = event.target.result; if (cursor) { union(cursor.value); } else { complete(); } } } Чистый API 9 . 15
  173. 173. Интересные особенностиИнтересные особенности 9 . 16
  174. 174. Интересные особенностиИнтересные особенности Позволяет задавать Schema в YAML (SPAC) 9 . 16
  175. 175. Интересные особенностиИнтересные особенности Позволяет задавать Schema в YAML (SPAC) Типы Storage: IndexedDB, Memory, Firebase 9 . 16
  176. 176. Интересные особенностиИнтересные особенности Позволяет задавать Schema в YAML (SPAC) Типы Storage: IndexedDB, Memory, Firebase Поддерживает Import/Export в Javascript-объект 9 . 16
  177. 177. Интересные особенностиИнтересные особенности Позволяет задавать Schema в YAML (SPAC) Типы Storage: IndexedDB, Memory, Firebase Поддерживает Import/Export в Javascript-объект Поддерживает Data Observation 9 . 16
  178. 178. Data ObservationData Observation 9 . 17
  179. 179. Data ObservationData Observation var query = db.select() .from(documents) .where(documents.id.eq('1')); var handler = function(changes) { // Будет вызываться всегда, когда происходит изменение данных }; db.observe(query, handler); db.update(documents) .set(documents.title, 'New Title') .where(documents.id.eq('1')) .exec(); db.unobserve(query, handler); 9 . 17
  180. 180. НюансыНюансы 9 . 18
  181. 181. Изменение SchemaИзменение Schema 9 . 19
  182. 182. Вывод ошибокВывод ошибок 9 . 20
  183. 183. Вывод ошибокВывод ошибок Constraint error: (202) Attempted to insert NULL value to non-nullable field Documents.signOrder. 9 . 20
  184. 184. Хранение файловХранение файлов 10 . 1
  185. 185. 10 . 2
  186. 186. ArrayBuffer 10 . 2
  187. 187. Offline + Web WorkerOffline + Web Worker 11 . 1
  188. 188. 11 . 2
  189. 189. FrameworkFramework 12 . 1
  190. 190. АрхитектураАрхитектура 12 . 2
  191. 191. Redux + Side Effects + Entities + Selectors 12 . 3
  192. 192. Side EffectsSide Effects @Effect() documentCreate$ = this.actions$.pipe( ofType(DocumentActions.Types.DocumentCreate), map(action => action.payload.query), exhaustMap(query => this.dbService.createDocument(query) ), flatMap(() => [ new DocumentAction.DocumentCreateSuccess(); new DocumentAction.GenerateDocumentPreview(); ]) ); 12 . 4
  193. 193. EntitiesEntities export interface Document { id: string; name: string; } export interface State extends EntityState<Document> { activeDocumentId: number | null; } 12 . 5
  194. 194. EntitiesEntities export interface Document { id: string; name: string; } export interface State extends EntityState<Document> { activeDocumentId: number | null; } export const adapter: EntityAdapter<Document> = createEntityAdapter<Document>(); 12 . 5
  195. 195. EntitiesEntities export interface Document { id: string; name: string; } export interface State extends EntityState<Document> { activeDocumentId: number | null; } export const adapter: EntityAdapter<Document> = createEntityAdapter<Document>(); 12 . 5
  196. 196. Entity adapterEntity adapter addOne addMany addAll removeOne removeMany removeAll updateOne updateMany upsertOne upsertMany map 12 . 6
  197. 197. SelectorsSelectors export const getDocumentState = (state: AppState) => state.documents; export const getDocumentEntities = (state: DocumentState) => state.entities; export const getDocumentEntities = createSelector( getDocumentState, getDocumentEntities ); export const getAllDocuments = createSelector(getDocumentEntities, entities => Object.keys(entities).map(id => entities[id]) ); 12 . 7
  198. 198. BoilerplateBoilerplate 12 . 8
  199. 199. SchematicsSchematics ng generate action store entity reducer effect container feature 12 . 9
  200. 200. class GetDocumentPreviewAction implements IDocumentAction { readonly type = DocumentActionTypes.GetDocumentPreview; documentType: DocumentType; constructor(public payload: any) { } } class GetDocumentPreviewSuccessAction implements IDocumentAction { readonly type = DocumentActionTypes.GetDocumentPreviewSuccess; documentType: DocumentType; constructor(public payload: IFileWithData) { } } class GetDocumentPreviewFailureAction implements IDocumentAction { readonly type = DocumentActionTypes.GetDocumentPreviewFailure; documentType: DocumentType; } 12 . 10
  201. 201. https://github.com/pelotom/unionize import { unionize, ofType, UnionOf } from 'unionize'; const DocumentActions = unionize({ GET_DOCUMENT_PREVIEW: ofType<{ id: string }>(), GET_DOCUMENT_PREVIEW_SUCCESS: ofType<IFileWithData>() GET_DOCUMENT_PREVIEW_FAILURE: {} }); type DocumentAction = UnionOf<typeof DocumentActions>; 12 . 11
  202. 202. Коммуникация с Web WorkerКоммуникация с Web Worker 12 . 12
  203. 203. 12 . 13
  204. 204. Effect 12 . 13
  205. 205. Effect Action 12 . 13
  206. 206. Effect Action ReduxRedux 12 . 13
  207. 207. Effect Action ReduxRedux 12 . 13
  208. 208. Effect Action ReduxRedux ComutterComutter 12 . 13
  209. 209. Effect Action ReduxRedux ComutterComutter 12 . 13
  210. 210. Effect Action ReduxRedux Web WorkerComutterComutter 12 . 13
  211. 211. Effect Action ReduxRedux Web WorkerComutterComutter 12 . 13
  212. 212. Effect Action ReduxRedux Web Worker Success/Failure Action ComutterComutter 12 . 13
  213. 213. Effect Action ReduxRedux Web Worker Success/Failure Action ComutterComutter 12 . 13
  214. 214. Redux Devtools 12 . 14
  215. 215. ВыводыВыводы 13 . 1
  216. 216. 13 . 2
  217. 217. Спасибо за вниманиеСпасибо за внимание Тимофей Лавренюк @geek_timofey 14

×