diff --git a/packages/core/__tests__/vault.test.js b/packages/core/__tests__/vault.test.js index f6e3f30e6..b4e74ad14 100644 --- a/packages/core/__tests__/vault.test.js +++ b/packages/core/__tests__/vault.test.js @@ -7,7 +7,7 @@ beforeEach(async () => { test("create vault", () => databaseTest().then(async (db) => { await expect(db.vault.create("password")).resolves.toBe(true); - const vaultKey = await db.context.read("vaultKey"); + const vaultKey = await db.storage.read("vaultKey"); expect(vaultKey).toBeDefined(); expect(vaultKey.iv).toBeDefined(); expect(vaultKey.cipher).toBeDefined(); diff --git a/packages/core/api/index.js b/packages/core/api/index.js index 699abd9d6..55bed9c92 100644 --- a/packages/core/api/index.js +++ b/packages/core/api/index.js @@ -1,5 +1,6 @@ import Notes from "../collections/notes"; import Storage from "../database/storage"; +import FileStorage from "../database/fs"; import Notebooks from "../collections/notebooks"; import Trash from "../collections/trash"; import Tags from "../collections/tags"; @@ -28,12 +29,12 @@ var NNEventSource; class Database { /** * - * @param {any} context + * @param {any} storage * @param {EventSource} eventsource */ - constructor(context, eventsource, fs) { - this.context = new Storage(context); - this.fs = fs; + constructor(storage, eventsource, fs) { + this.storage = new Storage(storage); + this.fs = new FileStorage(fs, storage); NNEventSource = eventsource; this._syncTimeout = 0; } @@ -62,10 +63,10 @@ class Database { }); EV.subscribe(EVENTS.databaseUpdated, this._onDBWrite.bind(this)); - this.session = new Session(this.context); + this.session = new Session(this.storage); await this._validate(); - this.user = new UserManager(this); + this.user = new UserManager(this.storage); this.syncer = new Sync(this); this.vault = new Vault(this); this.conflicts = new Conflicts(this); @@ -152,7 +153,7 @@ class Database { await this.user.logout(true, "Password Changed"); break; case "emailConfirmed": - const token = await this.context.read("token"); + const token = await this.storage.read("token"); await this.user.tokenManager._refreshToken(token); await this.user.fetchUser(true); EV.publish(EVENTS.userEmailConfirmed); @@ -166,7 +167,7 @@ class Database { } async lastSynced() { - return this.context.read("lastSynced"); + return this.storage.read("lastSynced"); } async _onDBWrite(item) { diff --git a/packages/core/api/migrations.js b/packages/core/api/migrations.js index 83457084c..5a5259f03 100644 --- a/packages/core/api/migrations.js +++ b/packages/core/api/migrations.js @@ -13,8 +13,8 @@ class Migrations { async init() { this.dbVersion = - (await this._db.context.read("v")) || CURRENT_DATABASE_VERSION; - this._db.context.write("v", this.dbVersion); + (await this._db.storage.read("v")) || CURRENT_DATABASE_VERSION; + this._db.storage.write("v", this.dbVersion); } async migrate() { @@ -55,7 +55,7 @@ class Migrations { }, ]; await this._migrator.migrate(collections, (item) => item, this.dbVersion); - await this._db.context.write("v", CURRENT_DATABASE_VERSION); + await this._db.storage.write("v", CURRENT_DATABASE_VERSION); EV.publish(EVENTS.databaseMigrated, { prev: this.dbVersion, diff --git a/packages/core/api/monographs.js b/packages/core/api/monographs.js index e56d9a9ce..1f0e93db5 100644 --- a/packages/core/api/monographs.js +++ b/packages/core/api/monographs.js @@ -13,17 +13,17 @@ class Monographs { async deinit() { this.monographs = []; - await this._db.context.write("monographs", this.monographs); + await this._db.storage.write("monographs", this.monographs); } async init() { const user = await this._db.user.getUser(); const token = await this._db.user.tokenManager.getAccessToken(); if (!user || !token || !user.isEmailConfirmed) return; - let monographs = await this._db.context.read("monographs", true); + let monographs = await this._db.storage.read("monographs", true); try { monographs = await http.get(`${Constants.API_HOST}/monographs`, token); - await this._db.context.write("monographs", monographs); + await this._db.storage.write("monographs", monographs); } catch (e) { console.error(e); } @@ -77,7 +77,7 @@ class Monographs { }; if (opts.password) { - monograph.encryptedContent = await this._db.context.encrypt( + monograph.encryptedContent = await this._db.storage.encrypt( { password: opts.password }, JSON.stringify({ type: content.type, data: content.data }) ); diff --git a/packages/core/api/outbox.js b/packages/core/api/outbox.js index 5db4092f0..65e6e3fd8 100644 --- a/packages/core/api/outbox.js +++ b/packages/core/api/outbox.js @@ -9,7 +9,7 @@ class Outbox { } async init() { - this.outbox = (await this._db.context.read("outbox")) || {}; + this.outbox = (await this._db.storage.read("outbox")) || {}; for (var id in this.outbox) { const data = this.outbox[id]; @@ -18,7 +18,7 @@ class Outbox { case "change_password": const key = await this._db.user.getEncryptionKey(); const { email } = await this._db.user.getUser(); - await this._db.context.deriveCryptoKey(`_uk_@${email}`, { + await this._db.storage.deriveCryptoKey(`_uk_@${email}`, { password: data.newPassword, salt: key.salt, }); @@ -31,14 +31,14 @@ class Outbox { async add(id, data, action) { this.outbox[id] = data; - await this._db.context.write("outbox", this.outbox); + await this._db.storage.write("outbox", this.outbox); await action(); await this.delete(id); } delete(id) { delete this.outbox[id]; - return this._db.context.write("outbox", this.outbox); + return this._db.storage.write("outbox", this.outbox); } } export default Outbox; diff --git a/packages/core/api/session.js b/packages/core/api/session.js index 0d6480a7d..e6e9bb08b 100644 --- a/packages/core/api/session.js +++ b/packages/core/api/session.js @@ -4,15 +4,15 @@ class Session { * @param {import("../database/storage").default} context */ constructor(context) { - this._context = context; + this._storage = context; } get() { - return this._context.read("t"); + return this._storage.read("t"); } set() { - return this._context.write("t", Date.now()); + return this._storage.write("t", Date.now()); } async valid() { diff --git a/packages/core/api/settings.js b/packages/core/api/settings.js index 12bcc82f8..0cc457a9c 100644 --- a/packages/core/api/settings.js +++ b/packages/core/api/settings.js @@ -14,7 +14,7 @@ class Settings { } async init() { - var settings = await this._db.context.read("settings"); + var settings = await this._db.storage.read("settings"); this._initSettings(settings); await this._saveSettings(); @@ -138,7 +138,7 @@ class Settings { } async _saveSettings() { - await this._db.context.write("settings", this._settings); + await this._db.storage.write("settings", this._settings); } } export default Settings; diff --git a/packages/core/api/sync/collector.js b/packages/core/api/sync/collector.js index 98bf2c2ab..fae23c244 100644 --- a/packages/core/api/sync/collector.js +++ b/packages/core/api/sync/collector.js @@ -32,7 +32,7 @@ class Collector { _serialize(item) { if (!item) return null; - return this._db.context.encrypt(this.key, JSON.stringify(item)); + return this._db.storage.encrypt(this.key, JSON.stringify(item)); } _encrypt(array) { diff --git a/packages/core/api/sync/conflicts.js b/packages/core/api/sync/conflicts.js index 14f37f391..78c8c353f 100644 --- a/packages/core/api/sync/conflicts.js +++ b/packages/core/api/sync/conflicts.js @@ -9,12 +9,12 @@ class Conflicts { async recalculate() { if (this._db.notes.conflicted.length <= 0) { - await this._db.context.write("hasConflicts", false); + await this._db.storage.write("hasConflicts", false); } } async check() { - let hasConflicts = await this._db.context.read("hasConflicts"); + let hasConflicts = await this._db.storage.read("hasConflicts"); if (hasConflicts) { const mergeConflictError = new Error( "Merge conflicts detected. Please resolve all conflicts to continue syncing." diff --git a/packages/core/api/sync/index.js b/packages/core/api/sync/index.js index 3208e2306..38a2a170d 100644 --- a/packages/core/api/sync/index.js +++ b/packages/core/api/sync/index.js @@ -41,7 +41,7 @@ export default class Sync { this._db = db; this._collector = new Collector(this._db); this._merger = new Merger(this._db); - this._tokenManager = new TokenManager(this._db); + this._tokenManager = new TokenManager(this._db.storage); this._isSyncing = false; } @@ -53,7 +53,7 @@ export default class Sync { } async _performChecks() { - let lastSynced = (await this._db.context.read("lastSynced")) || 0; + let lastSynced = (await this._db.storage.read("lastSynced")) || 0; let token = await this._tokenManager.getAccessToken(); // update the conflicts status and if find any, throw @@ -66,7 +66,7 @@ export default class Sync { async start(full, force) { if (this._isSyncing) return false; - if (force) await this._db.context.write("lastSynced", 0); + if (force) await this._db.storage.write("lastSynced", 0); let { lastSynced, token } = await this._performChecks(); try { @@ -84,7 +84,7 @@ export default class Sync { // merge the server response await this._merger.merge(serverResponse, lastSynced); - await this._downloadAttachments(token); + // await this._downloadAttachments(token); } // check for conflicts and throw @@ -95,7 +95,7 @@ export default class Sync { // update our lastSynced time if (lastSynced) { - await this._db.context.write("lastSynced", lastSynced); + await this._db.storage.write("lastSynced", lastSynced); } return true; @@ -123,7 +123,7 @@ export default class Sync { // update our lastSynced time if (lastSynced) { - await this._db.context.write("lastSynced", lastSynced); + await this._db.storage.write("lastSynced", lastSynced); } EV.publish(EVENTS.appRefreshRequested); @@ -137,7 +137,7 @@ export default class Sync { // last edited (but unsynced) time resulting in edited notes // not getting synced. // if (serverResponse.lastSynced) { - // await this._db.context.write("lastSynced", serverResponse.lastSynced); + // await this._db.storage.write("lastSynced", serverResponse.lastSynced); // } } @@ -154,37 +154,24 @@ export default class Sync { async _uploadAttachments(token) { const attachments = this._db.attachments.pending; console.log("Uploading attachments", this._db.attachments.pending); - for (let attachment of attachments) { + for (var i = 0; i < attachments.length; ++i) { + const attachment = attachments[i]; + EV.publish(EVENTS.attachmentsLoading, { + type: "upload", + total: attachments.length, + current: i, + }); const { hash } = attachment.metadata; - const url = await this._getPresignedURL(hash, token, "PUT"); - const uploadResult = await this._db.fs.uploadFile(hash, { url }); - if (!uploadResult) throw new Error("Failed to upload file."); + + const isUploaded = await this._db.fs.uploadFile(hash); + if (!isUploaded) throw new Error("Failed to upload file."); + await this._db.attachments.markAsUploaded(attachment.id); } - } - - async _downloadAttachments(token) { - const attachments = this._db.attachments.media; - console.log("Downloading attachments", attachments); - for (let attachment of attachments) { - const { hash } = attachment.metadata; - const url = `${Constants.API_HOST}/s3?name=${hash}`; - const downloadResult = await this._db.fs.downloadFile(hash, { - url, - headers: { Authorization: `Bearer ${token}` }, - }); - if (!downloadResult) throw new Error("Failed to download file."); - } - } - - async _getPresignedURL(filename, token, verb) { - const response = await fetch(`${Constants.API_HOST}/s3?name=${filename}`, { - method: verb, - headers: { - Authorization: `Bearer ${token}`, - }, + EV.publish(EVENTS.attachmentsLoading, { + type: "upload", + total: attachments.length, + current: attachments.length, }); - if (response.ok) return await response.text(); - throw new Error("Couldn't get presigned url."); } } diff --git a/packages/core/api/sync/merger.js b/packages/core/api/sync/merger.js index e28112dca..ee2ed207e 100644 --- a/packages/core/api/sync/merger.js +++ b/packages/core/api/sync/merger.js @@ -25,7 +25,7 @@ class Merger { async _deserialize(item, migrate = true) { const deserialized = JSON.parse( - await this._db.context.decrypt(this.key, item) + await this._db.storage.decrypt(this.key, item) ); deserialized.remote = true; if (!migrate) return deserialized; @@ -147,7 +147,7 @@ class Merger { // otherwise we trigger the conflicts await this._db.content.add({ ...local, conflicted: remote }); await this._db.notes.add({ id: local.noteId, conflicted: true }); - await this._db.context.write("hasConflicts", true); + await this._db.storage.write("hasConflicts", true); } } ); diff --git a/packages/core/api/token-manager.js b/packages/core/api/token-manager.js index fee82ba69..6addc6ed4 100644 --- a/packages/core/api/token-manager.js +++ b/packages/core/api/token-manager.js @@ -13,14 +13,14 @@ var isRefreshingToken = false; class TokenManager { /** * - * @param {import("./index").default} db + * @param {import("../database/storage").default} storage */ - constructor(db) { - this._db = db; + constructor(storage) { + this._storage = storage; } async getToken(renew = true, forceRenew = false) { - let token = await this._db.context.read("token"); + let token = await this._storage.read("token"); if (!token) return; if (forceRenew || (renew && this._isTokenExpired(token))) { await this._refreshToken(token); @@ -107,7 +107,7 @@ class TokenManager { saveToken(tokenResponse) { let token = { ...tokenResponse, t: Date.now() }; - return this._db.context.write("token", token); + return this._storage.write("token", token); } async getAccessTokenFromAuthorizationCode(userId, authCode) { diff --git a/packages/core/api/user-manager.js b/packages/core/api/user-manager.js index c519e9ca6..ab7632a8d 100644 --- a/packages/core/api/user-manager.js +++ b/packages/core/api/user-manager.js @@ -22,11 +22,11 @@ const ENDPOINTS = { class UserManager { /** * - * @param {import("./index").default} db + * @param {import("../database/storage").default} storage */ - constructor(db) { - this._db = db; - this.tokenManager = new TokenManager(db); + constructor(storage) { + this._storage = storage; + this.tokenManager = new TokenManager(storage); } async init() { @@ -36,7 +36,7 @@ class UserManager { } async signup(email, password) { - const hashedPassword = await this._db.context.hash(password, email); + const hashedPassword = await this._storage.hash(password, email); await http.post(`${constants.API_HOST}${ENDPOINTS.signup}`, { email, password: hashedPassword, @@ -48,7 +48,7 @@ class UserManager { async login(email, password, hashedPassword) { if (!hashedPassword) { - hashedPassword = await this._db.context.hash(password, email); + hashedPassword = await this._storage.hash(password, email); } await this.tokenManager.saveToken( @@ -63,7 +63,7 @@ class UserManager { const user = await this.fetchUser(); setUserPersonalizationBytes(user.salt); - await this._db.context.deriveCryptoKey(`_uk_@${user.email}`, { + await this._storage.deriveCryptoKey(`_uk_@${user.email}`, { password, salt: user.salt, }); @@ -83,7 +83,7 @@ class UserManager { } catch (e) { console.error(e); } finally { - await this._db.context.clear(); + await this._storage.clear(); EV.publish(EVENTS.userLoggedOut, reason); EV.publish(EVENTS.appRefreshRequested); } @@ -91,11 +91,11 @@ class UserManager { setUser(user) { if (!user) return; - return this._db.context.write("user", user); + return this._storage.write("user", user); } getUser() { - return this._db.context.read("user"); + return this._storage.read("user"); } async deleteUser(password) { @@ -104,7 +104,7 @@ class UserManager { const user = await this.getUser(); await http.post( `${constants.API_HOST}${ENDPOINTS.deleteUser}`, - { password: await this._db.context.hash(password, user.email) }, + { password: await this._storage.hash(password, user.email) }, token ); await this.logout(false, "Account deleted."); @@ -155,7 +155,7 @@ class UserManager { async getEncryptionKey() { const user = await this.getUser(); if (!user) return; - const key = await this._db.context.getCryptoKey(`_uk_@${user.email}`); + const key = await this._storage.getCryptoKey(`_uk_@${user.email}`); return { key, salt: user.salt }; } @@ -181,8 +181,8 @@ class UserManager { const user = await this.getUser(); if (!user) return false; const key = await this.getEncryptionKey(); - const cipher = await this._db.context.encrypt(key, "notesnook"); - const plainText = await this._db.context.decrypt({ password }, cipher); + const cipher = await this._storage.encrypt(key, "notesnook"); + const plainText = await this._storage.decrypt({ password }, cipher); return plainText === "notesnook"; } catch (e) { return false; @@ -197,12 +197,12 @@ class UserManager { const { email, salt } = await this.getUser(); var hashedData = {}; if (data.old_password) - hashedData.old_password = await this._db.context.hash( + hashedData.old_password = await this._storage.hash( data.old_password, email ); if (data.new_password) - hashedData.new_password = await this._db.context.hash( + hashedData.new_password = await this._storage.hash( data.new_password, email ); @@ -215,20 +215,21 @@ class UserManager { }, token ); - await this._db.outbox.add( - type, - { newPassword: data.new_password }, - async () => { - await this._db.sync(true); + // TODO + // await this._db.outbox.add( + // type, + // { newPassword: data.new_password }, + // async () => { + // await this._db.sync(true); - await this._db.context.deriveCryptoKey(`_uk_@${email}`, { - password: data.new_password, - salt, - }); + // await this._storage.deriveCryptoKey(`_uk_@${email}`, { + // password: data.new_password, + // salt, + // }); - await this._db.sync(false, true); - } - ); + // await this._db.sync(false, true); + // } + // ); return true; } } diff --git a/packages/core/api/vault.js b/packages/core/api/vault.js index 31f5951dc..56f08adfd 100644 --- a/packages/core/api/vault.js +++ b/packages/core/api/vault.js @@ -27,7 +27,7 @@ export default class Vault { */ constructor(db) { this._db = db; - this._context = db.context; + this._storage = db.storage; this._key = "svvaads1212#2123"; this._vaultPassword = null; this.ERRORS = { @@ -48,13 +48,13 @@ export default class Vault { async create(password) { if (!(await sendCheckUserStatusEvent(CHECK_IDS.vaultAdd))) return; - const vaultKey = await this._context.read("vaultKey"); + const vaultKey = await this._storage.read("vaultKey"); if (!vaultKey || !vaultKey.cipher || !vaultKey.iv) { - const encryptedData = await this._context.encrypt( + const encryptedData = await this._storage.encrypt( { password }, this._key ); - await this._context.write("vaultKey", encryptedData); + await this._storage.write("vaultKey", encryptedData); this._password = password; } return true; @@ -67,10 +67,10 @@ export default class Vault { * @returns {Promise} */ async unlock(password) { - const vaultKey = await this._context.read("vaultKey"); + const vaultKey = await this._storage.read("vaultKey"); if (!(await this.exists(vaultKey))) throw new Error(this.ERRORS.noVault); try { - await this._context.decrypt({ password }, vaultKey); + await this._storage.decrypt({ password }, vaultKey); } catch (e) { throw new Error(this.ERRORS.wrongPassword); } @@ -84,7 +84,7 @@ export default class Vault { for (var note of lockedNotes) { await this._unlockNote(note, oldPassword, true); } - await this._context.remove("vaultKey"); + await this._storage.remove("vaultKey"); await this.create(newPassword); for (var note of lockedNotes) { await this._lockNote(note, newPassword); @@ -106,7 +106,7 @@ export default class Vault { ...this._db.notes.locked.map((note) => note.id) ); } - await this._context.remove("vaultKey"); + await this._storage.remove("vaultKey"); this._password = null; } @@ -155,7 +155,7 @@ export default class Vault { } async exists(vaultKey) { - if (!vaultKey) vaultKey = await this._context.read("vaultKey"); + if (!vaultKey) vaultKey = await this._storage.read("vaultKey"); return vaultKey && vaultKey.cipher && vaultKey.iv; } @@ -179,7 +179,7 @@ export default class Vault { /** @private */ async _encryptContent(contentId, content, type, password) { - let encryptedContent = await this._context.encrypt( + let encryptedContent = await this._storage.encrypt( { password }, JSON.stringify(content) ); @@ -191,7 +191,7 @@ export default class Vault { async _decryptContent(contentId, password) { let encryptedContent = await this._db.content.raw(contentId); - let decryptedContent = await this._context.decrypt( + let decryptedContent = await this._storage.decrypt( { password }, encryptedContent.data ); @@ -257,12 +257,12 @@ export default class Vault { /** @inner */ async _getKey() { - if (await this.exists()) return await this._context.read("vaultKey"); + if (await this.exists()) return await this._storage.read("vaultKey"); } /** @inner */ async _setKey(vaultKey) { if (!vaultKey) return; - await this._context.write("vaultKey", vaultKey); + await this._storage.write("vaultKey", vaultKey); } } diff --git a/packages/core/collections/attachments.js b/packages/core/collections/attachments.js index a61a97cb4..7d73b7c7b 100644 --- a/packages/core/collections/attachments.js +++ b/packages/core/collections/attachments.js @@ -1,7 +1,9 @@ import Collection from "./collection"; import id from "../utils/id"; -import { deleteItem } from "../utils/array"; +import { deleteItem, hasItem } from "../utils/array"; import hosts from "../utils/constants"; +import { EV, EVENTS } from "../common"; +import dataurl from "../utils/dataurl"; export default class Attachments extends Collection { constructor(db, name, cached) { @@ -9,12 +11,13 @@ export default class Attachments extends Collection { this.key = null; } - async _initEncryptionKey() { + async _getEncryptionKey() { if (!this.key) this.key = await this._db.user.getEncryptionKey(); if (!this.key) throw new Error( "Failed to get user encryption key. Cannot cache attachments." ); + return this.key; } /** @@ -78,13 +81,9 @@ export default class Attachments extends Collection { const attachment = this.all.find((a) => a.metadata.hash === hash); if (!attachment || !deleteItem(attachment.noteIds, noteId)) return; if (!attachment.noteIds.length) { + const isDeleted = await this._db.fs.deleteFile(attachment.metadata.hash); + if (!isDeleted) return; attachment.dateDeleted = Date.now(); - const token = await this._db.user.tokenManager.getToken(); - const result = await this.fs.deleteFile(attachment.metadata.hash, { - url: `${hosts.API_HOST}/s3?name=${attachment.metadata.hash}`, - headers: { Authorization: `Bearer ${token}` }, - }); - if (!result) return; } return this._collection.updateItem(attachment); } @@ -92,10 +91,10 @@ export default class Attachments extends Collection { async get(hash) { const attachment = this.all.find((a) => a.metadata.hash === hash); if (!attachment) return; - await this._initEncryptionKey(); + const data = await this._db.fs.readEncrypted( attachment.metadata.hash, - this.key, + await this._getEncryptionKey(), { iv: attachment.iv, salt: attachment.salt, @@ -104,8 +103,7 @@ export default class Attachments extends Collection { outputType: "base64", } ); - attachment.data = data; - return attachment; + return { data, ...attachment }; } async attachment(id) { @@ -121,11 +119,49 @@ export default class Attachments extends Collection { } async save(data, type) { - await this._initEncryptionKey(); - return await this._db.fs.writeEncrypted(null, { + return await this._db.fs.writeEncrypted( + null, data, type, - key: this.key, + await this._getEncryptionKey() + ); + } + + async download(noteId) { + const attachments = this.media.filter((attachment) => + hasItem(attachment.noteIds, noteId) + ); + console.log("Downloading attachments", attachments); + for (let i = 0; i < attachments.length; i++) { + EV.publish(EVENTS.attachmentsLoading, { + type: "download", + total: attachments.length, + current: i, + }); + + const { hash } = attachments[i].metadata; + + const attachmentExists = await this._db.fs.exists(hash); + if (attachmentExists) continue; + + const isDownloaded = await this._db.fs.downloadFile(hash); + if (!isDownloaded) continue; + + const attachment = await this.get(hash); + if (!attachment) continue; + + EV.publish(EVENTS.mediaAttachmentDownloaded, { + hash, + src: dataurl.fromObject({ + type: attachment.metadata.type, + data: attachment.data, + }), + }); + } + EV.publish(EVENTS.attachmentsLoading, { + type: "download", + total: attachments.length, + current: attachments.length, }); } diff --git a/packages/core/collections/collection.js b/packages/core/collections/collection.js index bbfd54f4c..cd2a3100d 100644 --- a/packages/core/collections/collection.js +++ b/packages/core/collections/collection.js @@ -30,8 +30,8 @@ class Collection { */ constructor(db, name, cached) { this._db = db; - if (cached) this._collection = new CachedCollection(this._db.context, name); - else this._collection = new IndexedCollection(this._db.context, name); + if (cached) this._collection = new CachedCollection(this._db.storage, name); + else this._collection = new IndexedCollection(this._db.storage, name); } } export default Collection; diff --git a/packages/core/common.js b/packages/core/common.js index 8cad21c35..18d7ae2d0 100644 --- a/packages/core/common.js +++ b/packages/core/common.js @@ -37,6 +37,8 @@ export const EVENTS = { appRefreshRequested: "app:refreshRequested", noteRemoved: "note:removed", tokenRefreshed: "token:refreshed", + attachmentsLoading: "attachments:loading", + mediaAttachmentDownloaded: "attachments:mediaDownloaded", }; export const CURRENT_DATABASE_VERSION = 5.2; diff --git a/packages/core/database/backup.js b/packages/core/database/backup.js index eed63a203..f1ec5acf1 100644 --- a/packages/core/database/backup.js +++ b/packages/core/database/backup.js @@ -20,7 +20,7 @@ export default class Backup { } lastBackupTime() { - return this._db.context.read("lastBackupTime"); + return this._db.storage.read("lastBackupTime"); } /** @@ -35,22 +35,22 @@ export default class Backup { if (!validTypes.some((t) => t === type)) throw new Error("Invalid type. It must be one of 'mobile' or 'web'."); - let keys = await this._db.context.getAllKeys(); + let keys = await this._db.storage.getAllKeys(); let data = filterData( - Object.fromEntries(await this._db.context.readMulti(keys)) + Object.fromEntries(await this._db.storage.readMulti(keys)) ); let hash = {}; if (encrypt) { const key = await this._db.user.getEncryptionKey(); - data = await this._db.context.encrypt(key, JSON.stringify(data)); + data = await this._db.storage.encrypt(key, JSON.stringify(data)); } else { hash = { hash: SparkMD5.hash(JSON.stringify(data)), hash_type: "md5" }; } // save backup time - await this._db.context.write("lastBackupTime", Date.now()); + await this._db.storage.write("lastBackupTime", Date.now()); return JSON.stringify({ version: CURRENT_DATABASE_VERSION, type, @@ -77,7 +77,7 @@ export default class Backup { //check if we have encrypted data if (db.salt && db.iv) { if (!key) key = await this._db.user.getEncryptionKey(); - backup.data = JSON.parse(await this._db.context.decrypt(key, db)); + backup.data = JSON.parse(await this._db.storage.decrypt(key, db)); } else if (!this._verify(backup)) throw new Error("Backup file has been tempered, aborting..."); diff --git a/packages/core/database/fs.js b/packages/core/database/fs.js new file mode 100644 index 000000000..e5dadea83 --- /dev/null +++ b/packages/core/database/fs.js @@ -0,0 +1,65 @@ +import hosts from "../utils/constants"; +import TokenManager from "../api/token-manager"; + +export default class FileStorage { + constructor(fs, storage) { + this.fs = fs; + this.tokenManager = new TokenManager(storage); + } + + async downloadFile(hash) { + const url = `${hosts.API_HOST}/s3?name=${hash}`; + const token = await this.tokenManager.getAccessToken(); + return await this.fs.downloadFile(hash, { + url, + headers: { Authorization: `Bearer ${token}` }, + }); + } + + async uploadFile(hash) { + const token = await this.tokenManager.getAccessToken(); + const url = await this._getPresignedURL(hash, token, "PUT"); + return await this.fs.uploadFile(hash, { url }); + } + + readEncrypted(filename, encryptionKey, cipherData) { + return this.fs.readEncrypted(filename, encryptionKey, cipherData); + } + + writeEncrypted(filename, data, type, encryptionKey) { + return this._db.fs.writeEncrypted(filename, { + data, + type, + key: encryptionKey, + }); + } + + async deleteFile(filename) { + const token = await this.tokenManager.getToken(); + const url = `${hosts.API_HOST}/s3?name=${hash}`; + return await this.fs.deleteFile(filename, { + url, + headers: { Authorization: `Bearer ${token}` }, + }); + } + + /** + * + * @param {string} filename + * @returns {Promise} + */ + exists(filename) { + return this.fs.exists(filename); + } + + async _getPresignedURL(filename, token, verb) { + const response = await fetch(`${hosts.API_HOST}/s3?name=${filename}`, { + method: verb, + headers: { + Authorization: `Bearer ${token}`, + }, + }); + if (response.ok) return await response.text(); + throw new Error("Couldn't get presigned url."); + } +} diff --git a/packages/core/utils/dataurl.js b/packages/core/utils/dataurl.js index ea7152840..5ba312c95 100644 --- a/packages/core/utils/dataurl.js +++ b/packages/core/utils/dataurl.js @@ -8,7 +8,7 @@ function toObject(dataurl) { } function fromObject({ type, data }) { - //const { groups } = REGEX.exec(dataurl); + if (REGEX.test(data)) return data; return `data:${type};base64,${data}`; } diff --git a/packages/core/utils/event-manager.js b/packages/core/utils/event-manager.js index 03dcbb161..5340d985e 100644 --- a/packages/core/utils/event-manager.js +++ b/packages/core/utils/event-manager.js @@ -16,10 +16,11 @@ class EventManager { subscribe(name, handler, once = false) { if (!name || !handler) throw new Error("name and handler are required."); this._registry.set(handler, { name, once }); + return { unsubscribe: () => this.unsubscribe(name, handler) }; } unsubscribe(_name, handler) { - this._registry.delete(handler); + return this._registry.delete(handler); } publish(name, ...args) {