Files
notesnook/packages/core/database/backup.js

116 lines
2.8 KiB
JavaScript
Raw Normal View History

import Hashes from "jshashes";
2020-11-24 12:56:03 +05:00
import { sendCheckUserStatusEvent } from "../common.js";
const md5 = new Hashes.MD5();
2020-10-28 10:58:07 +05:00
const invalidKeys = ["user", "t", "lastBackupTime"];
const validTypes = ["mobile", "web", "node"];
2020-09-13 13:24:24 +05:00
export default class Backup {
/**
*
* @param {import("../api/index.js").default} db
*/
constructor(db) {
this._db = db;
}
2020-10-03 12:26:39 +05:00
lastBackupTime() {
return this._db.context.read("lastBackupTime");
}
/**
*
* @param {"web"|"mobile"|"node"} type
* @param {boolean} encrypt
*/
async export(type, encrypt = false) {
if (encrypt && !(await sendCheckUserStatusEvent("backup:encrypt"))) return;
if (!validTypes.some((t) => t === type))
2020-11-24 12:53:52 +05:00
throw new Error("Invalid type. It must be one of 'mobile' or 'web'.");
const keys = (await this._db.context.getAllKeys()).filter(
(key) => !invalidKeys.some((t) => t === key)
);
2020-11-24 12:53:52 +05:00
let data = Object.fromEntries(await this._db.context.readMulti(keys));
2020-09-13 13:24:24 +05:00
if (encrypt) {
const key = await this._db.user.key();
2020-11-24 12:53:52 +05:00
data = await this._db.context.encrypt(key, JSON.stringify(data));
2020-09-13 13:24:24 +05:00
}
2020-10-03 12:26:39 +05:00
// save backup time
await this._db.context.write("lastBackupTime", Date.now());
return JSON.stringify({
2020-11-24 12:53:52 +05:00
version: 2,
type,
date: Date.now(),
2020-11-24 12:53:52 +05:00
data,
hash: md5.hex(JSON.stringify(data)),
hash_type: "md5",
});
2020-09-13 13:24:24 +05:00
}
/**
*
* @param {string} data the backup data
*/
2020-09-13 13:24:24 +05:00
async import(data) {
if (!data) return;
2020-09-13 13:24:24 +05:00
let backup = JSON.parse(data);
if (!this._validate(backup)) throw new Error("Invalid backup.");
let db = backup.data;
2020-09-13 13:24:24 +05:00
//check if we have encrypted data
if (db.salt && db.iv) {
2020-09-13 13:24:24 +05:00
const key = await this._db.user.key();
db = JSON.parse(await this._db.context.decrypt(key, db));
2020-09-13 13:24:24 +05:00
}
2020-11-24 12:53:52 +05:00
if (!this._verify(backup))
throw new Error("Backup file has been tempered, aborting...");
2020-11-24 12:53:52 +05:00
// TODO add a proper restoration system.
// for (let key in db) {
// let value = db[key];
// if (value && value.dateEdited) {
// value.dateEdited = Date.now();
// }
// const oldValue = await this._db.context.read(oldValue);
// let finalValue = oldValue || value;
// if (typeof value === "object") {
// finalValue = Array.isArray(value)
// ? [...value, ...oldValue]
// : { ...value, ...oldValue };
// }
// await this._db.context.write(key, finalValue);
// }
2020-09-13 13:24:24 +05:00
}
_validate(backup) {
return (
!!backup.date &&
!!backup.data &&
!!backup.type &&
validTypes.some((t) => t === backup.type)
);
}
2020-11-24 12:53:52 +05:00
_verify(backup) {
const { hash, hash_type, data: db } = backup;
switch (hash_type) {
case "md5": {
2020-11-24 12:53:52 +05:00
return hash === md5.hex(JSON.stringify(db));
}
default: {
return false;
}
}
}
2020-09-13 13:24:24 +05:00
}