Files
liberama/client/components/Reader/ServerStorage/ServerStorage.vue

225 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div></div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import _ from 'lodash';
import bookManager from '../share/bookManager';
import readerApi from '../../../api/reader';
import * as utils from '../../../share/utils';
import * as cryptoUtils from '../../../share/cryptoUtils';
const maxSetTries = 5;
export default @Component({
watch: {
profiles: function() {
this.saveProfiles();
},
},
})
class ServerStorage extends Vue {
created() {
this.commit = this.$store.commit;
}
async init() {
if (!this.serverStorageKey) {
//генерируем новый ключ
this.generateNewServerStorageKey();
}
this.hashedStorageKey = utils.toBase58(await cryptoUtils.sha256(this.serverStorageKey));
await this.loadProfiles();
}
get settings() {
return this.$store.state.reader.settings;
}
get serverStorageKey() {
return this.$store.state.reader.serverStorageKey;
}
get profiles() {
return this.$store.state.reader.profiles;
}
get profilesRev() {
return this.$store.state.reader.profilesRev;
}
get currentProfile() {
return this.$store.state.reader.currentProfile;
}
notifySuccessIfNeeded(rev1, rev2) {
if (rev1 != rev2)
this.$notify.success({message: 'Данные синхронизированы с сервером'});
}
warning(message) {
this.$notify.warning({message});
}
error(message) {
this.$notify.error({message});
}
async loadProfiles() {
if (!this.currentProfile)
return;
let prof = await this.storageGet({'profiles': {}});
if (prof.state == 'success') {
const oldRev = this.profilesRev;
prof = prof.items.profiles;
this.commit('reader/setProfiles', prof.data);
this.commit('reader/setProfilesRev', prof.rev);
this.oldProfiles = this.profiles;
this.notifySuccessIfNeeded(oldRev, prof.rev);
} else {
this.warning(`Неверный ответ сервера: ${prof.state}`);
}
}
async saveProfiles() {
if (!this.currentProfile)
return;
if (!this.savingProfiles) {
this.savingProfiles = true;
const diff = utils.getObjDiff(this.oldProfiles, this.profiles);
let result = {state: ''};
let tries = 0;
while (result.state != 'success' && tries < maxSetTries) {
result = await this.storageSet({'profiles': {rev: this.profilesRev + 1, data: this.profiles}});
if (result.state == 'reject') {
await this.loadProfiles();
const newProfiles = utils.applyObjDiff(this.profiles, diff);
this.commit('reader/setProfiles', newProfiles);
this.commit('reader/setProfilesRev', result.items.profiles.rev);
}
tries++;
}
this.commit('reader/setProfilesRev', this.profilesRev + 1);
if (tries >= maxSetTries) {
throw new Error('Не удалось отправить данные на сервер');
}
this.savingProfiles = false;
}
}
generateNewServerStorageKey() {
const key = utils.toBase58(utils.randomArray(32));
this.commit('reader/setServerStorageKey', key);
}
async storageCheck(items) {
return await this.storageApi('check', items);
}
async storageGet(items) {
return await this.storageApi('get', items);
}
async storageSet(items, force) {
return await this.storageApi('set', items, force);
}
async storageApi(action, items, force) {
const request = {action, items};
if (force)
request.force = true;
const encodedRequest = await this.encodeStorageItems(request);
return await this.decodeStorageItems(await readerApi.storage(encodedRequest));
}
async encodeStorageItems(request) {
if (!this.hashedStorageKey)
throw new Error('hashedStorageKey is empty');
if (!_.isObject(request.items))
throw new Error('items is not an object');
let result = Object.assign({}, request);
let items = {};
for (const id of Object.keys(request.items)) {
const item = request.items[id];
if (request.action == 'set' && !_.isObject(item.data))
throw new Error('encodeStorageItems: data is not an object');
let encoded = Object.assign({}, item);
if (item.data) {
const comp = utils.pako.deflate(JSON.stringify(item.data), {level: 1});
let encrypted = null;
try {
encrypted = await cryptoUtils.aesEncrypt(comp, this.serverStorageKey);
} catch (e) {
throw new Error('encrypt failed');
}
encoded.data = '1' + utils.toBase64(encrypted);
}
items[`${this.hashedStorageKey}.${utils.toBase58(id)}`] = encoded;
}
result.items = items;
return result;
}
async decodeStorageItems(response) {
if (!this.hashedStorageKey)
throw new Error('hashedStorageKey is empty');
let result = Object.assign({}, response);
let items = {};
if (response.items) {
if (!_.isObject(response.items))
throw new Error('items is not an object');
for (const id of Object.keys(response.items)) {
const item = response.items[id];
let decoded = Object.assign({}, item);
if (item.data) {
if (!_.isString(item.data) || !item.data.length)
throw new Error('decodeStorageItems: data is not a string');
if (item.data[0] !== '1')
throw new Error('decodeStorageItems: unknown data format');
const a = utils.fromBase64(item.data.substr(1));
let decrypted = null;
try {
decrypted = await cryptoUtils.aesDecrypt(a, this.serverStorageKey);
} catch (e) {
throw new Error('decrypt failed');
}
decoded.data = JSON.parse(utils.pako.inflate(decrypted, {to: 'string'}));
}
const ids = id.split('.');
if (!(ids.length == 2) || !(ids[0] == this.hashedStorageKey))
throw new Error(`decodeStorageItems: bad id - ${id}`);
items[utils.fromBase58(ids[1])] = decoded;
}
}
result.items = items;
return result;
}
}
//-----------------------------------------------------------------------------
</script>