ServerStorage.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. <template>
  2. <div class="hidden"></div>
  3. </template>
  4. <script>
  5. //-----------------------------------------------------------------------------
  6. import vueComponent from '../../vueComponent.js';
  7. import _ from 'lodash';
  8. import bookManager from '../share/bookManager';
  9. import readerApi from '../../../api/reader';
  10. import * as utils from '../../../share/utils';
  11. import * as cryptoUtils from '../../../share/cryptoUtils';
  12. import LockQueue from '../../../share/LockQueue';
  13. import localForage from 'localforage';
  14. const ssCacheStore = localForage.createInstance({
  15. name: 'ssCacheStore'
  16. });
  17. const componentOptions = {
  18. watch: {
  19. serverSyncEnabled: function() {
  20. this.serverSyncEnabledChanged();
  21. },
  22. serverStorageKey: function() {
  23. this.serverStorageKeyChanged(true);
  24. },
  25. settings: function() {
  26. this.debouncedSaveSettings();
  27. },
  28. profiles: function() {
  29. this.saveProfiles();
  30. },
  31. currentProfile: function() {
  32. this.currentProfileChanged(true);
  33. },
  34. libs: function() {
  35. this.debouncedSaveLibs();
  36. },
  37. },
  38. };
  39. class ServerStorage {
  40. _options = componentOptions;
  41. created() {
  42. this.inited = false;
  43. this.keyInited = false;
  44. this.commit = this.$store.commit;
  45. this.prevServerStorageKey = null;
  46. this.lock = new LockQueue(100);
  47. this.$root.generateNewServerStorageKey = () => {this.generateNewServerStorageKey()};
  48. this.debouncedSaveSettings = _.debounce(() => {
  49. this.saveSettings();
  50. }, 500);
  51. this.debouncedSaveLibs = _.debounce(() => {
  52. this.saveLibs();
  53. }, 500);
  54. this.debouncedNotifySuccess = _.debounce(() => {
  55. this.success('Данные синхронизированы с сервером');
  56. }, 1000);
  57. this.oldProfiles = {};
  58. this.oldSettings = {};
  59. this.oldLibs = {};
  60. }
  61. async init() {
  62. try {
  63. this.cachedRecent = await ssCacheStore.getItem('recent');
  64. if (!this.cachedRecent)
  65. await this.cleanCachedRecent('cachedRecent');
  66. this.cachedRecentPatch = await ssCacheStore.getItem('recent-patch');
  67. if (!this.cachedRecentPatch)
  68. await this.cleanCachedRecent('cachedRecentPatch');
  69. this.cachedRecentMod = await ssCacheStore.getItem('recent-mod');
  70. if (!this.cachedRecentMod)
  71. await this.cleanCachedRecent('cachedRecentMod');
  72. if (!this.serverStorageKey) {
  73. //генерируем новый ключ
  74. await this.generateNewServerStorageKey();
  75. } else {
  76. await this.serverStorageKeyChanged();
  77. }
  78. } finally {
  79. this.inited = true;
  80. }
  81. }
  82. async setCachedRecent(value) {
  83. await ssCacheStore.setItem('recent', value);
  84. this.cachedRecent = value;
  85. }
  86. async setCachedRecentPatch(value) {
  87. await ssCacheStore.setItem('recent-patch', value);
  88. this.cachedRecentPatch = value;
  89. }
  90. async setCachedRecentMod(value) {
  91. await ssCacheStore.setItem('recent-mod', value);
  92. this.cachedRecentMod = value;
  93. }
  94. async cleanCachedRecent(whatToClean) {
  95. if (whatToClean == 'cachedRecent' || whatToClean == 'all')
  96. await this.setCachedRecent({rev: 0, data: {}});
  97. if (whatToClean == 'cachedRecentPatch' || whatToClean == 'all')
  98. await this.setCachedRecentPatch({rev: 0, data: {}});
  99. if (whatToClean == 'cachedRecentMod' || whatToClean == 'all')
  100. await this.setCachedRecentMod({rev: 0, data: {}});
  101. }
  102. async generateNewServerStorageKey() {
  103. const key = utils.toBase58(utils.randomArray(32));
  104. this.commit('reader/setServerStorageKey', key);
  105. await this.serverStorageKeyChanged(true);
  106. }
  107. async serverSyncEnabledChanged() {
  108. if (this.serverSyncEnabled) {
  109. this.prevServerStorageKey = null;
  110. if (!this.serverStorageKey) {
  111. //генерируем новый ключ
  112. await this.generateNewServerStorageKey();
  113. } else {
  114. await this.serverStorageKeyChanged(true);
  115. }
  116. }
  117. }
  118. async serverStorageKeyChanged(force) {
  119. if (this.prevServerStorageKey != this.serverStorageKey) {
  120. this.prevServerStorageKey = this.serverStorageKey;
  121. this.hashedStorageKey = utils.toBase58(cryptoUtils.sha256(this.serverStorageKey));
  122. this.keyInited = true;
  123. await this.loadProfiles(force);
  124. this.checkCurrentProfile();
  125. await this.currentProfileChanged(force);
  126. await this.loadLibs(force);
  127. if (force)
  128. await this.cleanCachedRecent('all');
  129. const loadSuccess = await this.loadRecent();
  130. if (loadSuccess && force) {
  131. await this.saveRecent();
  132. }
  133. }
  134. }
  135. async currentProfileChanged(force) {
  136. if (!this.currentProfile)
  137. return;
  138. await this.loadSettings(force);
  139. }
  140. get serverSyncEnabled() {
  141. return this.$store.state.reader.serverSyncEnabled;
  142. }
  143. get settings() {
  144. return this.$store.state.reader.settings;
  145. }
  146. get settingsRev() {
  147. return this.$store.state.reader.settingsRev;
  148. }
  149. get serverStorageKey() {
  150. return this.$store.state.reader.serverStorageKey;
  151. }
  152. get profiles() {
  153. return this.$store.state.reader.profiles;
  154. }
  155. get profilesRev() {
  156. return this.$store.state.reader.profilesRev;
  157. }
  158. get currentProfile() {
  159. return this.$store.state.reader.currentProfile;
  160. }
  161. get showServerStorageMessages() {
  162. return this.settings.showServerStorageMessages;
  163. }
  164. get libs() {
  165. return this.$store.state.reader.libs;
  166. }
  167. get libsRev() {
  168. return this.$store.state.reader.libsRev;
  169. }
  170. checkCurrentProfile() {
  171. if (!this.profiles[this.currentProfile]) {
  172. this.commit('reader/setCurrentProfile', '');
  173. }
  174. }
  175. success(message) {
  176. if (this.showServerStorageMessages)
  177. this.$root.notify.success(message);
  178. }
  179. warning(message) {
  180. if (this.showServerStorageMessages && !this.offlineModeActive)
  181. this.$root.notify.warning(message);
  182. }
  183. error(message) {
  184. if (this.showServerStorageMessages && !this.offlineModeActive) {
  185. this.errorMessageCounter = (this.errorMessageCounter ? this.errorMessageCounter + 1 : 1);
  186. const hint = (this.errorMessageCounter < 2 ? '' :
  187. '<div><br>Надоело это сообщение? Добавьте в настройках кнопку "Автономный режим" ' +
  188. '<i class="la la-unlink" style="font-size: 20px; color: white"></i> на панель инструментов и активируйте ее.</div>'
  189. );
  190. this.$root.notify.error(message + hint);
  191. }
  192. }
  193. async loadSettings(force = false, doNotifySuccess = true) {
  194. if (!this.keyInited || !this.serverSyncEnabled || !this.currentProfile)
  195. return;
  196. const setsId = `settings-${this.currentProfile}`;
  197. const oldRev = this.settingsRev[setsId] || 0;
  198. //проверим ревизию на сервере
  199. if (!force) {
  200. try {
  201. const revs = await this.storageCheck({[setsId]: {}});
  202. if (revs.state == 'success' && revs.items[setsId].rev == oldRev) {
  203. return;
  204. }
  205. } catch(e) {
  206. this.error(`Ошибка соединения с сервером: ${e.message}`);
  207. return;
  208. }
  209. }
  210. let sets = null;
  211. try {
  212. sets = await this.storageGet({[setsId]: {}});
  213. } catch(e) {
  214. this.error(`Ошибка соединения с сервером: ${e.message}`);
  215. return;
  216. }
  217. if (sets.state == 'success') {
  218. sets = sets.items[setsId];
  219. if (sets.rev == 0)
  220. sets.data = {};
  221. this.oldSettings = _.cloneDeep(sets.data);
  222. this.commit('reader/setSettings', sets.data);
  223. this.commit('reader/setSettingsRev', {[setsId]: sets.rev});
  224. if (doNotifySuccess)
  225. this.debouncedNotifySuccess();
  226. } else {
  227. this.warning(`Неверный ответ сервера: ${sets.state}`);
  228. }
  229. }
  230. async saveSettings() {
  231. if (!this.keyInited || !this.serverSyncEnabled || !this.currentProfile || this.savingSettings)
  232. return;
  233. const diff = utils.getObjDiff(this.oldSettings, this.settings);
  234. if (utils.isEmptyObjDiff(diff))
  235. return;
  236. this.savingSettings = true;
  237. try {
  238. const setsId = `settings-${this.currentProfile}`;
  239. let result = {state: ''};
  240. const oldRev = this.settingsRev[setsId] || 0;
  241. try {
  242. result = await this.storageSet({[setsId]: {rev: oldRev + 1, data: this.settings}});
  243. } catch(e) {
  244. this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
  245. }
  246. if (result.state == 'reject') {
  247. await this.loadSettings(true, false);
  248. this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
  249. } else if (result.state == 'success') {
  250. this.oldSettings = _.cloneDeep(this.settings);
  251. this.commit('reader/setSettingsRev', {[setsId]: this.settingsRev[setsId] + 1});
  252. }
  253. } finally {
  254. this.savingSettings = false;
  255. }
  256. }
  257. async loadProfiles(force = false, doNotifySuccess = true) {
  258. if (!this.keyInited || !this.serverSyncEnabled)
  259. return;
  260. const oldRev = this.profilesRev;
  261. //проверим ревизию на сервере
  262. if (!force) {
  263. try {
  264. const revs = await this.storageCheck({profiles: {}});
  265. if (revs.state == 'success' && revs.items.profiles.rev == oldRev) {
  266. return;
  267. }
  268. } catch(e) {
  269. this.error(`Ошибка соединения с сервером: ${e.message}`);
  270. return;
  271. }
  272. }
  273. let prof = null;
  274. try {
  275. prof = await this.storageGet({profiles: {}});
  276. } catch(e) {
  277. this.error(`Ошибка соединения с сервером: ${e.message}`);
  278. return;
  279. }
  280. if (prof.state == 'success') {
  281. prof = prof.items.profiles;
  282. if (prof.rev == 0)
  283. prof.data = {};
  284. this.oldProfiles = _.cloneDeep(prof.data);
  285. this.commit('reader/setProfiles', prof.data);
  286. this.commit('reader/setProfilesRev', prof.rev);
  287. this.checkCurrentProfile();
  288. if (doNotifySuccess)
  289. this.debouncedNotifySuccess();
  290. } else {
  291. this.warning(`Неверный ответ сервера: ${prof.state}`);
  292. }
  293. }
  294. async saveProfiles() {
  295. if (!this.keyInited || !this.serverSyncEnabled || this.savingProfiles)
  296. return;
  297. const diff = utils.getObjDiff(this.oldProfiles, this.profiles);
  298. if (utils.isEmptyObjDiff(diff))
  299. return;
  300. //обнуляются профили во время разработки при hotReload, подстраховка
  301. if (!this.$store.state.reader.allowProfilesSave) {
  302. console.error('Сохранение профилей не санкционировано');
  303. return;
  304. }
  305. this.savingProfiles = true;
  306. try {
  307. let result = {state: ''};
  308. try {
  309. result = await this.storageSet({profiles: {rev: this.profilesRev + 1, data: this.profiles}});
  310. } catch(e) {
  311. this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
  312. }
  313. if (result.state == 'reject') {
  314. await this.loadProfiles(true, false);
  315. this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
  316. } else if (result.state == 'success') {
  317. this.oldProfiles = _.cloneDeep(this.profiles);
  318. this.commit('reader/setProfilesRev', this.profilesRev + 1);
  319. }
  320. } finally {
  321. this.savingProfiles = false;
  322. }
  323. }
  324. async loadLibs(force = false, doNotifySuccess = true) {
  325. if (!this.keyInited || !this.serverSyncEnabled)
  326. return;
  327. const oldRev = this.libsRev;
  328. //проверим ревизию на сервере
  329. if (!force) {
  330. try {
  331. const revs = await this.storageCheck({libs: {}});
  332. if (revs.state == 'success' && revs.items.libs.rev == oldRev) {
  333. return;
  334. }
  335. } catch(e) {
  336. this.error(`Ошибка соединения с сервером: ${e.message}`);
  337. return;
  338. }
  339. }
  340. let libs = null;
  341. try {
  342. libs = await this.storageGet({libs: {}});
  343. } catch(e) {
  344. this.error(`Ошибка соединения с сервером: ${e.message}`);
  345. return;
  346. }
  347. if (libs.state == 'success') {
  348. libs = libs.items.libs;
  349. if (libs.rev == 0)
  350. libs.data = {};
  351. this.oldLibs = _.cloneDeep(libs.data);
  352. this.commit('reader/setLibs', libs.data);
  353. this.commit('reader/setLibsRev', libs.rev);
  354. if (doNotifySuccess)
  355. this.debouncedNotifySuccess();
  356. } else {
  357. this.warning(`Неверный ответ сервера: ${libs.state}`);
  358. }
  359. }
  360. async saveLibs() {
  361. if (!this.keyInited || !this.serverSyncEnabled || this.savingLibs)
  362. return;
  363. const diff = utils.getObjDiff(this.oldLibs, this.libs);
  364. if (utils.isEmptyObjDiff(diff))
  365. return;
  366. this.savingLibs = true;
  367. try {
  368. let result = {state: ''};
  369. try {
  370. result = await this.storageSet({libs: {rev: this.libsRev + 1, data: this.libs}});
  371. } catch(e) {
  372. this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
  373. }
  374. if (result.state == 'reject') {
  375. await this.loadLibs(true, false);
  376. this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
  377. } else if (result.state == 'success') {
  378. this.oldLibs = _.cloneDeep(this.libs);
  379. this.commit('reader/setLibsRev', this.libsRev + 1);
  380. }
  381. } finally {
  382. this.savingLibs = false;
  383. }
  384. }
  385. async loadRecent(skipRevCheck = false, doNotifySuccess = true) {
  386. if (!this.keyInited || !this.serverSyncEnabled || this.loadingRecent)
  387. return;
  388. this.loadingRecent = true;
  389. try {
  390. //проверим ревизию на сервере
  391. let query = {recent: {}, recentPatch: {}, recentMod: {}};
  392. let revs = null;
  393. if (!skipRevCheck) {
  394. try {
  395. revs = await this.storageCheck(query);
  396. if (revs.state == 'success') {
  397. if (revs.items.recent.rev != this.cachedRecent.rev) {
  398. //no changes
  399. } else if (revs.items.recentPatch.rev != this.cachedRecentPatch.rev) {
  400. query = {recentPatch: {}, recentMod: {}};
  401. } else if (revs.items.recentMod.rev != this.cachedRecentMod.rev) {
  402. query = {recentMod: {}};
  403. } else
  404. return true;
  405. }
  406. } catch(e) {
  407. this.error(`Ошибка соединения с сервером: ${e.message}`);
  408. return;
  409. }
  410. }
  411. let recent = null;
  412. try {
  413. recent = await this.storageGet(query);
  414. } catch(e) {
  415. this.error(`Ошибка соединения с сервером: ${e.message}`);
  416. return;
  417. }
  418. if (recent.state == 'success') {
  419. let newRecent = recent.items.recent;
  420. let newRecentPatch = recent.items.recentPatch;
  421. let newRecentMod = recent.items.recentMod;
  422. if (!newRecent) {
  423. newRecent = _.cloneDeep(this.cachedRecent);
  424. }
  425. if (!newRecentPatch) {
  426. newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
  427. }
  428. if (!newRecentMod) {
  429. newRecentMod = _.cloneDeep(this.cachedRecentMod);
  430. }
  431. if (newRecent.rev == 0) newRecent.data = {};
  432. if (newRecentPatch.rev == 0) newRecentPatch.data = {};
  433. if (newRecentMod.rev == 0) newRecentMod.data = {};
  434. let result = Object.assign({}, newRecent.data, newRecentPatch.data);
  435. const md = newRecentMod.data;
  436. if (md.key && result[md.key])
  437. result[md.key] = utils.applyObjDiff(result[md.key], md.mod, {isAddChanged: true});
  438. /*if (!bookManager.loaded) {
  439. this.warning('Ожидание загрузки списка книг перед синхронизацией');
  440. while (!bookManager.loaded) await utils.sleep(100);
  441. }*/
  442. if (newRecent.rev != this.cachedRecent.rev)
  443. await this.setCachedRecent(newRecent);
  444. if (newRecentPatch.rev != this.cachedRecentPatch.rev)
  445. await this.setCachedRecentPatch(newRecentPatch);
  446. if (newRecentMod.rev != this.cachedRecentMod.rev)
  447. await this.setCachedRecentMod(newRecentMod);
  448. await bookManager.setRecent(result);
  449. } else {
  450. this.warning(`Неверный ответ сервера: ${recent.state}`);
  451. return;
  452. }
  453. if (doNotifySuccess)
  454. this.debouncedNotifySuccess();
  455. } finally {
  456. this.loadingRecent = false;
  457. }
  458. return true;
  459. }
  460. async saveRecent(itemKeys, recurse) {
  461. while (!this.inited)
  462. await utils.sleep(100);
  463. if (!this.keyInited || !this.serverSyncEnabled)
  464. return;
  465. let needRecurseCall = false;
  466. await this.lock.get();
  467. try {
  468. const bm = bookManager;
  469. let needSaveRecent = false;
  470. let needSaveRecentPatch = false;
  471. let needSaveRecentMod = false;
  472. //newRecentMod
  473. let newRecentMod = {};
  474. let oneItemKey = null;
  475. if (itemKeys && itemKeys.length == 1)
  476. oneItemKey = itemKeys[0];
  477. if (oneItemKey && this.cachedRecentPatch.data[oneItemKey] && this.prevItemKey == oneItemKey) {
  478. newRecentMod = _.cloneDeep(this.cachedRecentMod);
  479. newRecentMod.rev++;
  480. newRecentMod.data.key = oneItemKey;
  481. newRecentMod.data.mod = utils.getObjDiff(this.cachedRecentPatch.data[oneItemKey], bm.recent[oneItemKey]);
  482. needSaveRecentMod = true;
  483. }
  484. this.prevItemKey = oneItemKey;
  485. //newRecentPatch
  486. let newRecentPatch = {};
  487. if (itemKeys && !needSaveRecentMod) {
  488. newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
  489. newRecentPatch.rev++;
  490. for (const key of itemKeys) {
  491. newRecentPatch.data[key] = _.cloneDeep(bm.recent[key]);
  492. }
  493. const applyMod = this.cachedRecentMod.data;
  494. if (applyMod && applyMod.key && newRecentPatch.data[applyMod.key])
  495. newRecentPatch.data[applyMod.key] = utils.applyObjDiff(newRecentPatch.data[applyMod.key], applyMod.mod, {isAddChanged: true});
  496. newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
  497. needSaveRecentPatch = true;
  498. needSaveRecentMod = true;
  499. }
  500. //newRecent
  501. let newRecent = {};
  502. if (!itemKeys || (needSaveRecentPatch && Object.keys(newRecentPatch.data).length > 10)) {
  503. newRecent = {rev: this.cachedRecent.rev + 1, data: _.cloneDeep(bm.recent)};
  504. newRecentPatch = {rev: this.cachedRecentPatch.rev + 1, data: {}};
  505. newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
  506. needSaveRecent = true;
  507. needSaveRecentPatch = true;
  508. needSaveRecentMod = true;
  509. }
  510. //query
  511. let query = {};
  512. if (needSaveRecent) {
  513. query = {recent: newRecent, recentPatch: newRecentPatch, recentMod: newRecentMod};
  514. } else if (needSaveRecentPatch) {
  515. query = {recentPatch: newRecentPatch, recentMod: newRecentMod};
  516. } else {
  517. query = {recentMod: newRecentMod};
  518. }
  519. //сохранение
  520. let result = {state: ''};
  521. try {
  522. result = await this.storageSet(query);
  523. } catch(e) {
  524. this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
  525. }
  526. if (result.state == 'reject') {
  527. const res = await this.loadRecent(false, false);
  528. if (res)
  529. this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
  530. if (!recurse && itemKeys) {
  531. needRecurseCall = true;
  532. }
  533. } else if (result.state == 'success') {
  534. if (needSaveRecent && newRecent.rev)
  535. await this.setCachedRecent(newRecent);
  536. if (needSaveRecentPatch && newRecentPatch.rev)
  537. await this.setCachedRecentPatch(newRecentPatch);
  538. if (needSaveRecentMod && newRecentMod.rev)
  539. await this.setCachedRecentMod(newRecentMod);
  540. }
  541. } finally {
  542. this.lock.ret();
  543. }
  544. if (needRecurseCall)
  545. await this.saveRecent(itemKeys, true);
  546. }
  547. async storageCheck(items) {
  548. return await this.storageApi('check', items);
  549. }
  550. async storageGet(items) {
  551. return await this.storageApi('get', items);
  552. }
  553. async storageSet(items, force) {
  554. return await this.storageApi('set', items, force);
  555. }
  556. async storageApi(action, items, force) {
  557. const request = {action, items};
  558. if (force)
  559. request.force = true;
  560. const encodedRequest = await this.encodeStorageItems(request);
  561. return await this.decodeStorageItems(await readerApi.storage(encodedRequest));
  562. }
  563. async encodeStorageItems(request) {
  564. if (!this.hashedStorageKey)
  565. throw new Error('hashedStorageKey is empty');
  566. if (!_.isObject(request.items))
  567. throw new Error('items is not an object');
  568. let result = Object.assign({}, request);
  569. let items = {};
  570. for (const id of Object.keys(request.items)) {
  571. const item = request.items[id];
  572. if (request.action == 'set' && !_.isObject(item.data))
  573. throw new Error('encodeStorageItems: data is not an object');
  574. let encoded = Object.assign({}, item);
  575. if (item.data) {
  576. const comp = utils.pako.deflate(JSON.stringify(item.data), {level: 1});
  577. let encrypted = null;
  578. try {
  579. encrypted = cryptoUtils.aesEncrypt(comp, this.serverStorageKey);
  580. } catch (e) {
  581. throw new Error('encrypt failed');
  582. }
  583. encoded.data = '1' + utils.toBase64(encrypted);
  584. }
  585. items[`${this.hashedStorageKey}.${utils.toBase58(id)}`] = encoded;
  586. }
  587. result.items = items;
  588. return result;
  589. }
  590. async decodeStorageItems(response) {
  591. if (!this.hashedStorageKey)
  592. throw new Error('hashedStorageKey is empty');
  593. let result = Object.assign({}, response);
  594. let items = {};
  595. if (response.items) {
  596. if (!_.isObject(response.items))
  597. throw new Error('items is not an object');
  598. for (const id of Object.keys(response.items)) {
  599. const item = response.items[id];
  600. let decoded = Object.assign({}, item);
  601. if (item.data) {
  602. if (!_.isString(item.data) || !item.data.length)
  603. throw new Error('decodeStorageItems: data is not a string');
  604. if (item.data[0] !== '1')
  605. throw new Error('decodeStorageItems: unknown data format');
  606. const a = utils.fromBase64(item.data.substr(1));
  607. let decrypted = null;
  608. try {
  609. decrypted = cryptoUtils.aesDecrypt(a, this.serverStorageKey);
  610. } catch (e) {
  611. throw new Error('decrypt failed');
  612. }
  613. decoded.data = JSON.parse(utils.pako.inflate(decrypted, {to: 'string'}));
  614. }
  615. const ids = id.split('.');
  616. if (!(ids.length == 2) || !(ids[0] == this.hashedStorageKey))
  617. throw new Error(`decodeStorageItems: bad id - ${id}`);
  618. items[utils.fromBase58(ids[1]).toString()] = decoded;
  619. }
  620. }
  621. result.items = items;
  622. return result;
  623. }
  624. }
  625. export default vueComponent(ServerStorage);
  626. //-----------------------------------------------------------------------------
  627. </script>