ServerStorage.vue 27 KB

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