ServerStorage.vue 25 KB

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