ServerStorage.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  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. get offlineModeActive() {
  171. return this.$store.state.reader.offlineModeActive;
  172. }
  173. checkCurrentProfile() {
  174. if (!this.profiles[this.currentProfile]) {
  175. this.commit('reader/setCurrentProfile', '');
  176. }
  177. }
  178. success(message) {
  179. if (this.showServerStorageMessages)
  180. this.$root.notify.success(message);
  181. }
  182. warning(message) {
  183. if (this.showServerStorageMessages && !this.offlineModeActive)
  184. this.$root.notify.warning(message);
  185. }
  186. error(message) {
  187. if (this.showServerStorageMessages && !this.offlineModeActive) {
  188. this.errorMessageCounter = (this.errorMessageCounter ? this.errorMessageCounter + 1 : 1);
  189. const hint = (this.errorMessageCounter < 2 ? '' :
  190. '<div><br>Надоело это сообщение? Добавьте в настройках кнопку "Автономный режим" ' +
  191. '<i class="la la-unlink" style="font-size: 20px; color: white"></i> на панель инструментов и активируйте ее.</div>'
  192. );
  193. this.$root.notify.error(message + hint);
  194. }
  195. }
  196. async loadSettings(force = false, doNotifySuccess = true) {
  197. if (!this.keyInited || !this.serverSyncEnabled || !this.currentProfile)
  198. return;
  199. const setsId = `settings-${this.currentProfile}`;
  200. const oldRev = this.settingsRev[setsId] || 0;
  201. //проверим ревизию на сервере
  202. if (!force) {
  203. try {
  204. const revs = await this.storageCheck({[setsId]: {}});
  205. if (revs.state == 'success' && revs.items[setsId].rev == oldRev) {
  206. return;
  207. }
  208. } catch(e) {
  209. this.error(`Ошибка соединения с сервером: ${e.message}`);
  210. return;
  211. }
  212. }
  213. let sets = null;
  214. try {
  215. sets = await this.storageGet({[setsId]: {}});
  216. } catch(e) {
  217. this.error(`Ошибка соединения с сервером: ${e.message}`);
  218. return;
  219. }
  220. if (sets.state == 'success') {
  221. sets = sets.items[setsId];
  222. if (sets.rev == 0)
  223. sets.data = {};
  224. this.oldSettings = _.cloneDeep(sets.data);
  225. this.commit('reader/setSettings', sets.data);
  226. this.commit('reader/setSettingsRev', {[setsId]: sets.rev});
  227. if (doNotifySuccess)
  228. this.debouncedNotifySuccess();
  229. } else {
  230. this.warning(`Неверный ответ сервера: ${sets.state}`);
  231. }
  232. }
  233. async saveSettings() {
  234. if (!this.keyInited || !this.serverSyncEnabled || !this.currentProfile || this.savingSettings)
  235. return;
  236. const diff = utils.getObjDiff(this.oldSettings, this.settings);
  237. if (utils.isEmptyObjDiff(diff))
  238. return;
  239. this.savingSettings = true;
  240. try {
  241. const setsId = `settings-${this.currentProfile}`;
  242. let result = {state: ''};
  243. const oldRev = this.settingsRev[setsId] || 0;
  244. try {
  245. result = await this.storageSet({[setsId]: {rev: oldRev + 1, data: this.settings}});
  246. } catch(e) {
  247. this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
  248. }
  249. if (result.state == 'reject') {
  250. await this.loadSettings(true, false);
  251. this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
  252. } else if (result.state == 'success') {
  253. this.oldSettings = _.cloneDeep(this.settings);
  254. this.commit('reader/setSettingsRev', {[setsId]: this.settingsRev[setsId] + 1});
  255. }
  256. } finally {
  257. this.savingSettings = false;
  258. }
  259. }
  260. async loadProfiles(force = false, doNotifySuccess = true) {
  261. if (!this.keyInited || !this.serverSyncEnabled)
  262. return;
  263. const oldRev = this.profilesRev;
  264. //проверим ревизию на сервере
  265. if (!force) {
  266. try {
  267. const revs = await this.storageCheck({profiles: {}});
  268. if (revs.state == 'success' && revs.items.profiles.rev == oldRev) {
  269. return;
  270. }
  271. } catch(e) {
  272. this.error(`Ошибка соединения с сервером: ${e.message}`);
  273. return;
  274. }
  275. }
  276. let prof = null;
  277. try {
  278. prof = await this.storageGet({profiles: {}});
  279. } catch(e) {
  280. this.error(`Ошибка соединения с сервером: ${e.message}`);
  281. return;
  282. }
  283. if (prof.state == 'success') {
  284. prof = prof.items.profiles;
  285. if (prof.rev == 0)
  286. prof.data = {};
  287. this.oldProfiles = _.cloneDeep(prof.data);
  288. this.commit('reader/setProfiles', prof.data);
  289. this.commit('reader/setProfilesRev', prof.rev);
  290. this.checkCurrentProfile();
  291. if (doNotifySuccess)
  292. this.debouncedNotifySuccess();
  293. } else {
  294. this.warning(`Неверный ответ сервера: ${prof.state}`);
  295. }
  296. }
  297. async saveProfiles() {
  298. if (!this.keyInited || !this.serverSyncEnabled || this.savingProfiles)
  299. return;
  300. const diff = utils.getObjDiff(this.oldProfiles, this.profiles);
  301. if (utils.isEmptyObjDiff(diff))
  302. return;
  303. //обнуляются профили во время разработки при hotReload, подстраховка
  304. if (!this.$store.state.reader.allowProfilesSave) {
  305. console.error('Сохранение профилей не санкционировано');
  306. return;
  307. }
  308. this.savingProfiles = true;
  309. try {
  310. let result = {state: ''};
  311. try {
  312. result = await this.storageSet({profiles: {rev: this.profilesRev + 1, data: this.profiles}});
  313. } catch(e) {
  314. this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
  315. }
  316. if (result.state == 'reject') {
  317. await this.loadProfiles(true, false);
  318. this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
  319. } else if (result.state == 'success') {
  320. this.oldProfiles = _.cloneDeep(this.profiles);
  321. this.commit('reader/setProfilesRev', this.profilesRev + 1);
  322. }
  323. } finally {
  324. this.savingProfiles = false;
  325. }
  326. }
  327. async loadLibs(force = false, doNotifySuccess = true) {
  328. if (!this.keyInited || !this.serverSyncEnabled)
  329. return;
  330. const oldRev = this.libsRev;
  331. //проверим ревизию на сервере
  332. if (!force) {
  333. try {
  334. const revs = await this.storageCheck({libs: {}});
  335. if (revs.state == 'success' && revs.items.libs.rev == oldRev) {
  336. return;
  337. }
  338. } catch(e) {
  339. this.error(`Ошибка соединения с сервером: ${e.message}`);
  340. return;
  341. }
  342. }
  343. let libs = null;
  344. try {
  345. libs = await this.storageGet({libs: {}});
  346. } catch(e) {
  347. this.error(`Ошибка соединения с сервером: ${e.message}`);
  348. return;
  349. }
  350. if (libs.state == 'success') {
  351. libs = libs.items.libs;
  352. if (libs.rev == 0)
  353. libs.data = {};
  354. this.oldLibs = _.cloneDeep(libs.data);
  355. this.commit('reader/setLibs', libs.data);
  356. this.commit('reader/setLibsRev', libs.rev);
  357. if (doNotifySuccess)
  358. this.debouncedNotifySuccess();
  359. } else {
  360. this.warning(`Неверный ответ сервера: ${libs.state}`);
  361. }
  362. }
  363. async saveLibs() {
  364. if (!this.keyInited || !this.serverSyncEnabled || this.savingLibs)
  365. return;
  366. const diff = utils.getObjDiff(this.oldLibs, this.libs);
  367. if (utils.isEmptyObjDiff(diff))
  368. return;
  369. this.savingLibs = true;
  370. try {
  371. let result = {state: ''};
  372. try {
  373. result = await this.storageSet({libs: {rev: this.libsRev + 1, data: this.libs}});
  374. } catch(e) {
  375. this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
  376. }
  377. if (result.state == 'reject') {
  378. await this.loadLibs(true, false);
  379. this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
  380. } else if (result.state == 'success') {
  381. this.oldLibs = _.cloneDeep(this.libs);
  382. this.commit('reader/setLibsRev', this.libsRev + 1);
  383. }
  384. } finally {
  385. this.savingLibs = false;
  386. }
  387. }
  388. async loadRecent(skipRevCheck = false, doNotifySuccess = true) {
  389. if (!this.keyInited || !this.serverSyncEnabled || this.loadingRecent)
  390. return;
  391. this.loadingRecent = true;
  392. try {
  393. //проверим ревизию на сервере
  394. let query = {recent: {}, recentPatch: {}, recentMod: {}};
  395. let revs = null;
  396. if (!skipRevCheck) {
  397. try {
  398. revs = await this.storageCheck(query);
  399. if (revs.state == 'success') {
  400. if (revs.items.recent.rev != this.cachedRecent.rev) {
  401. //no changes
  402. } else if (revs.items.recentPatch.rev != this.cachedRecentPatch.rev) {
  403. query = {recentPatch: {}, recentMod: {}};
  404. } else if (revs.items.recentMod.rev != this.cachedRecentMod.rev) {
  405. query = {recentMod: {}};
  406. } else
  407. return true;
  408. }
  409. } catch(e) {
  410. this.error(`Ошибка соединения с сервером: ${e.message}`);
  411. return;
  412. }
  413. }
  414. let recent = null;
  415. try {
  416. recent = await this.storageGet(query);
  417. } catch(e) {
  418. this.error(`Ошибка соединения с сервером: ${e.message}`);
  419. return;
  420. }
  421. if (recent.state == 'success') {
  422. let newRecent = recent.items.recent;
  423. let newRecentPatch = recent.items.recentPatch;
  424. let newRecentMod = recent.items.recentMod;
  425. if (!newRecent) {
  426. newRecent = _.cloneDeep(this.cachedRecent);
  427. }
  428. if (!newRecentPatch) {
  429. newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
  430. }
  431. if (!newRecentMod) {
  432. newRecentMod = _.cloneDeep(this.cachedRecentMod);
  433. }
  434. if (newRecent.rev == 0) newRecent.data = {};
  435. if (newRecentPatch.rev == 0) newRecentPatch.data = {};
  436. if (newRecentMod.rev == 0) newRecentMod.data = {};
  437. let result = Object.assign({}, newRecent.data, newRecentPatch.data);
  438. const md = newRecentMod.data;
  439. if (md.key && result[md.key])
  440. result[md.key] = utils.applyObjDiff(result[md.key], md.mod, {isAddChanged: true});
  441. /*if (!bookManager.loaded) {
  442. this.warning('Ожидание загрузки списка книг перед синхронизацией');
  443. while (!bookManager.loaded) await utils.sleep(100);
  444. }*/
  445. if (newRecent.rev != this.cachedRecent.rev)
  446. await this.setCachedRecent(newRecent);
  447. if (newRecentPatch.rev != this.cachedRecentPatch.rev)
  448. await this.setCachedRecentPatch(newRecentPatch);
  449. if (newRecentMod.rev != this.cachedRecentMod.rev)
  450. await this.setCachedRecentMod(newRecentMod);
  451. await bookManager.setRecent(result);
  452. } else {
  453. this.warning(`Неверный ответ сервера: ${recent.state}`);
  454. return;
  455. }
  456. if (doNotifySuccess)
  457. this.debouncedNotifySuccess();
  458. } finally {
  459. this.loadingRecent = false;
  460. }
  461. return true;
  462. }
  463. async saveRecent(itemKeys, recurse) {
  464. while (!this.inited)
  465. await utils.sleep(100);
  466. if (!this.keyInited || !this.serverSyncEnabled)
  467. return;
  468. let needRecurseCall = false;
  469. await this.lock.get();
  470. try {
  471. const bm = bookManager;
  472. let needSaveRecent = false;
  473. let needSaveRecentPatch = false;
  474. let needSaveRecentMod = false;
  475. //newRecentMod
  476. let newRecentMod = {};
  477. let oneItemKey = null;
  478. if (itemKeys && itemKeys.length == 1)
  479. oneItemKey = itemKeys[0];
  480. if (oneItemKey && this.cachedRecentPatch.data[oneItemKey] && this.prevItemKey == oneItemKey) {
  481. newRecentMod = _.cloneDeep(this.cachedRecentMod);
  482. newRecentMod.rev++;
  483. newRecentMod.data.key = oneItemKey;
  484. newRecentMod.data.mod = utils.getObjDiff(this.cachedRecentPatch.data[oneItemKey], bm.recent[oneItemKey]);
  485. needSaveRecentMod = true;
  486. }
  487. this.prevItemKey = oneItemKey;
  488. //newRecentPatch
  489. let newRecentPatch = {};
  490. if (itemKeys && !needSaveRecentMod) {
  491. newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
  492. newRecentPatch.rev++;
  493. for (const key of itemKeys) {
  494. newRecentPatch.data[key] = _.cloneDeep(bm.recent[key]);
  495. }
  496. const applyMod = this.cachedRecentMod.data;
  497. if (applyMod && applyMod.key && newRecentPatch.data[applyMod.key])
  498. newRecentPatch.data[applyMod.key] = utils.applyObjDiff(newRecentPatch.data[applyMod.key], applyMod.mod, {isAddChanged: true});
  499. newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
  500. needSaveRecentPatch = true;
  501. needSaveRecentMod = true;
  502. }
  503. //newRecent
  504. let newRecent = {};
  505. if (!itemKeys || (needSaveRecentPatch && Object.keys(newRecentPatch.data).length > 10)) {
  506. newRecent = {rev: this.cachedRecent.rev + 1, data: _.cloneDeep(bm.recent)};
  507. newRecentPatch = {rev: this.cachedRecentPatch.rev + 1, data: {}};
  508. newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
  509. needSaveRecent = true;
  510. needSaveRecentPatch = true;
  511. needSaveRecentMod = true;
  512. }
  513. //query
  514. let query = {};
  515. if (needSaveRecent) {
  516. query = {recent: newRecent, recentPatch: newRecentPatch, recentMod: newRecentMod};
  517. } else if (needSaveRecentPatch) {
  518. query = {recentPatch: newRecentPatch, recentMod: newRecentMod};
  519. } else {
  520. query = {recentMod: newRecentMod};
  521. }
  522. //сохранение
  523. let result = {state: ''};
  524. try {
  525. result = await this.storageSet(query);
  526. } catch(e) {
  527. this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
  528. }
  529. if (result.state == 'reject') {
  530. const res = await this.loadRecent(false, false);
  531. if (res)
  532. this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
  533. if (!recurse && itemKeys) {
  534. needRecurseCall = true;
  535. }
  536. } else if (result.state == 'success') {
  537. if (needSaveRecent && newRecent.rev)
  538. await this.setCachedRecent(newRecent);
  539. if (needSaveRecentPatch && newRecentPatch.rev)
  540. await this.setCachedRecentPatch(newRecentPatch);
  541. if (needSaveRecentMod && newRecentMod.rev)
  542. await this.setCachedRecentMod(newRecentMod);
  543. }
  544. } finally {
  545. this.lock.ret();
  546. }
  547. if (needRecurseCall)
  548. await this.saveRecent(itemKeys, true);
  549. }
  550. async storageCheck(items) {
  551. return await this.storageApi('check', items);
  552. }
  553. async storageGet(items) {
  554. return await this.storageApi('get', items);
  555. }
  556. async storageSet(items, force) {
  557. return await this.storageApi('set', items, force);
  558. }
  559. async storageApi(action, items, force) {
  560. const request = {action, items};
  561. if (force)
  562. request.force = true;
  563. const encodedRequest = await this.encodeStorageItems(request);
  564. return await this.decodeStorageItems(await readerApi.storage(encodedRequest));
  565. }
  566. async encodeStorageItems(request) {
  567. if (!this.hashedStorageKey)
  568. throw new Error('hashedStorageKey is empty');
  569. if (!_.isObject(request.items))
  570. throw new Error('items is not an object');
  571. let result = Object.assign({}, request);
  572. let items = {};
  573. for (const id of Object.keys(request.items)) {
  574. const item = request.items[id];
  575. if (request.action == 'set' && !_.isObject(item.data))
  576. throw new Error('encodeStorageItems: data is not an object');
  577. let encoded = Object.assign({}, item);
  578. if (item.data) {
  579. const comp = utils.pako.deflate(JSON.stringify(item.data), {level: 1});
  580. let encrypted = null;
  581. try {
  582. encrypted = cryptoUtils.aesEncrypt(comp, this.serverStorageKey);
  583. } catch (e) {
  584. throw new Error('encrypt failed');
  585. }
  586. encoded.data = '1' + utils.toBase64(encrypted);
  587. }
  588. items[`${this.hashedStorageKey}.${utils.toBase58(id)}`] = encoded;
  589. }
  590. result.items = items;
  591. return result;
  592. }
  593. async decodeStorageItems(response) {
  594. if (!this.hashedStorageKey)
  595. throw new Error('hashedStorageKey is empty');
  596. let result = Object.assign({}, response);
  597. let items = {};
  598. if (response.items) {
  599. if (!_.isObject(response.items))
  600. throw new Error('items is not an object');
  601. for (const id of Object.keys(response.items)) {
  602. const item = response.items[id];
  603. let decoded = Object.assign({}, item);
  604. if (item.data) {
  605. if (!_.isString(item.data) || !item.data.length)
  606. throw new Error('decodeStorageItems: data is not a string');
  607. if (item.data[0] !== '1')
  608. throw new Error('decodeStorageItems: unknown data format');
  609. const a = utils.fromBase64(item.data.substr(1));
  610. let decrypted = null;
  611. try {
  612. decrypted = cryptoUtils.aesDecrypt(a, this.serverStorageKey);
  613. } catch (e) {
  614. throw new Error('decrypt failed');
  615. }
  616. decoded.data = JSON.parse(utils.pako.inflate(decrypted, {to: 'string'}));
  617. }
  618. const ids = id.split('.');
  619. if (!(ids.length == 2) || !(ids[0] == this.hashedStorageKey))
  620. throw new Error(`decodeStorageItems: bad id - ${id}`);
  621. items[utils.fromBase58(ids[1]).toString()] = decoded;
  622. }
  623. }
  624. result.items = items;
  625. return result;
  626. }
  627. }
  628. export default vueComponent(ServerStorage);
  629. //-----------------------------------------------------------------------------
  630. </script>