2022-08-31 06:33:37 +05:00
|
|
|
/*
|
|
|
|
|
This file is part of the Notesnook project (https://notesnook.com/)
|
|
|
|
|
|
2023-01-16 13:44:52 +05:00
|
|
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
2022-08-31 06:33:37 +05:00
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
2022-08-30 16:13:11 +05:00
|
|
|
|
2020-02-03 12:03:07 +05:00
|
|
|
import Notes from "../collections/notes";
|
2020-04-13 12:46:29 +05:00
|
|
|
import Storage from "../database/storage";
|
2021-09-26 11:47:13 +05:00
|
|
|
import FileStorage from "../database/fs";
|
2020-02-04 18:27:32 +05:00
|
|
|
import Notebooks from "../collections/notebooks";
|
2020-02-06 16:46:23 +05:00
|
|
|
import Trash from "../collections/trash";
|
2020-02-21 21:39:27 +05:00
|
|
|
import Tags from "../collections/tags";
|
2020-02-11 16:28:28 +05:00
|
|
|
import Sync from "./sync";
|
2020-03-07 12:29:55 +05:00
|
|
|
import Vault from "./vault";
|
2020-03-09 12:39:49 +05:00
|
|
|
import Lookup from "./lookup";
|
2020-03-19 11:30:05 +05:00
|
|
|
import Content from "../collections/content";
|
2020-09-13 13:24:24 +05:00
|
|
|
import Backup from "../database/backup";
|
2020-05-14 13:51:48 +05:00
|
|
|
import Session from "./session";
|
2020-09-19 11:46:36 +05:00
|
|
|
import Constants from "../utils/constants";
|
2022-03-30 15:52:48 +05:00
|
|
|
import { EV, EVENTS } from "../common";
|
2020-11-25 15:18:57 +05:00
|
|
|
import Settings from "./settings";
|
2020-12-05 15:26:54 +05:00
|
|
|
import Migrations from "./migrations";
|
2020-12-16 12:06:25 +05:00
|
|
|
import UserManager from "./user-manager";
|
2021-01-05 14:07:02 +05:00
|
|
|
import http from "../utils/http";
|
2021-06-15 11:57:00 +05:00
|
|
|
import Monographs from "./monographs";
|
2021-07-24 11:13:41 +05:00
|
|
|
import Offers from "./offers";
|
2021-09-15 02:16:55 +05:00
|
|
|
import Attachments from "../collections/attachments";
|
2021-09-15 11:47:10 +05:00
|
|
|
import Debug from "./debug";
|
2021-10-26 23:06:52 +05:00
|
|
|
import { Mutex } from "async-mutex";
|
2021-12-21 13:41:08 +05:00
|
|
|
import NoteHistory from "../collections/note-history";
|
2022-03-11 22:49:24 +05:00
|
|
|
import MFAManager from "./mfa-manager";
|
2022-03-30 15:52:48 +05:00
|
|
|
import EventManager from "../utils/event-manager";
|
2022-04-03 03:30:09 +05:00
|
|
|
import Pricing from "./pricing";
|
2022-07-20 07:34:12 +05:00
|
|
|
import { logger } from "../logger";
|
2022-09-07 12:47:02 +05:00
|
|
|
import Shortcuts from "../collections/shortcuts";
|
2022-11-17 13:57:58 +05:00
|
|
|
import Reminders from "../collections/reminders";
|
2022-11-17 14:11:22 +05:00
|
|
|
import Relations from "../collections/relations";
|
2023-03-20 12:17:13 +05:00
|
|
|
import Subscriptions from "./subscriptions";
|
2020-02-03 12:03:07 +05:00
|
|
|
|
2020-09-17 10:10:38 +05:00
|
|
|
/**
|
|
|
|
|
* @type {EventSource}
|
|
|
|
|
*/
|
|
|
|
|
var NNEventSource;
|
2022-07-07 18:40:12 +05:00
|
|
|
// const DIFFERENCE_THRESHOLD = 20 * 1000;
|
|
|
|
|
// const MAX_TIME_ERROR_FAILURES = 5;
|
2020-02-11 13:12:47 +05:00
|
|
|
class Database {
|
2023-04-15 23:23:04 +05:00
|
|
|
isInitialized = false;
|
2020-09-17 10:10:38 +05:00
|
|
|
/**
|
|
|
|
|
*
|
2021-09-26 11:47:13 +05:00
|
|
|
* @param {any} storage
|
2020-09-17 10:10:38 +05:00
|
|
|
* @param {EventSource} eventsource
|
|
|
|
|
*/
|
2023-06-09 18:19:44 +05:00
|
|
|
constructor() {
|
2021-10-26 23:06:52 +05:00
|
|
|
/**
|
|
|
|
|
* @type {EventSource}
|
|
|
|
|
*/
|
|
|
|
|
this.evtSource = null;
|
2021-10-27 10:53:36 +05:00
|
|
|
this.sseMutex = new Mutex();
|
2022-01-07 12:00:41 +05:00
|
|
|
this.lastHeartbeat = undefined; // { local: 0, server: 0 };
|
2022-01-17 10:25:59 +05:00
|
|
|
this.timeErrorFailures = 0;
|
2022-03-30 15:52:48 +05:00
|
|
|
this.eventManager = new EventManager();
|
2023-06-09 18:19:44 +05:00
|
|
|
}
|
2021-10-27 10:53:36 +05:00
|
|
|
|
2023-06-09 18:19:44 +05:00
|
|
|
setup(storage, eventsource, fs, compressor) {
|
|
|
|
|
this.compressor = compressor;
|
|
|
|
|
this.storage = storage ? new Storage(storage) : null;
|
|
|
|
|
this.fs = fs && storage ? new FileStorage(fs, storage) : null;
|
2020-09-17 10:10:38 +05:00
|
|
|
NNEventSource = eventsource;
|
2023-07-03 06:45:12 +05:00
|
|
|
|
|
|
|
|
this.session = new Session(this.storage);
|
|
|
|
|
this.user = new UserManager(this.storage, this);
|
|
|
|
|
this.mfa = new MFAManager(this.storage, this);
|
|
|
|
|
this.syncer = new Sync(this);
|
|
|
|
|
this.vault = new Vault(this);
|
|
|
|
|
this.lookup = new Lookup(this);
|
|
|
|
|
this.backup = new Backup(this);
|
|
|
|
|
this.settings = new Settings(this);
|
|
|
|
|
this.migrations = new Migrations(this);
|
|
|
|
|
this.monographs = new Monographs(this);
|
|
|
|
|
this.offers = new Offers();
|
|
|
|
|
this.debug = new Debug();
|
|
|
|
|
this.pricing = new Pricing();
|
|
|
|
|
this.subscriptions = new Subscriptions(this.user.tokenManager);
|
|
|
|
|
this.trash = new Trash(this);
|
2020-02-03 12:03:07 +05:00
|
|
|
}
|
2020-04-15 23:25:53 +05:00
|
|
|
|
2020-05-14 13:56:39 +05:00
|
|
|
async _validate() {
|
2020-05-14 13:51:48 +05:00
|
|
|
if (!(await this.session.valid())) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
"Your system clock is not setup correctly. Please adjust your date and time and then retry."
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-05-14 13:56:39 +05:00
|
|
|
await this.session.set();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async init() {
|
2020-11-02 09:50:27 +05:00
|
|
|
EV.subscribeMulti(
|
2021-08-31 12:11:46 +05:00
|
|
|
[EVENTS.userLoggedIn, EVENTS.userFetched, EVENTS.tokenRefreshed],
|
2021-09-15 12:47:22 +05:00
|
|
|
this.connectSSE,
|
|
|
|
|
this
|
2020-08-24 11:25:09 +05:00
|
|
|
);
|
2021-09-29 09:53:50 +05:00
|
|
|
EV.subscribe(EVENTS.attachmentDeleted, async (attachment) => {
|
2024-03-21 09:37:18 +05:00
|
|
|
await this.fs.cancel(attachment.metadata?.hash);
|
2021-09-29 09:53:50 +05:00
|
|
|
});
|
2021-06-16 11:53:05 +05:00
|
|
|
EV.subscribe(EVENTS.userLoggedOut, async () => {
|
|
|
|
|
await this.monographs.deinit();
|
2021-11-02 14:31:30 +05:00
|
|
|
await this.fs.clear();
|
2021-10-30 13:51:40 +05:00
|
|
|
this.disconnectSSE();
|
2021-01-23 12:19:51 +05:00
|
|
|
});
|
2020-08-24 11:25:09 +05:00
|
|
|
|
2020-11-09 09:15:11 +05:00
|
|
|
await this._validate();
|
2020-05-14 13:51:48 +05:00
|
|
|
|
2023-04-15 23:23:04 +05:00
|
|
|
await this.initCollections();
|
|
|
|
|
|
|
|
|
|
await this.migrations.init();
|
|
|
|
|
this.isInitialized = true;
|
|
|
|
|
if (this.migrations.required()) {
|
|
|
|
|
logger.warn("Database migration is required.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async initCollections() {
|
2023-07-03 06:45:12 +05:00
|
|
|
await this.settings.init();
|
2020-04-16 02:14:53 +05:00
|
|
|
// collections
|
|
|
|
|
/** @type {Notebooks} */
|
2022-03-30 15:52:48 +05:00
|
|
|
this.notebooks = await Notebooks.new(this, "notebooks");
|
2020-04-16 02:14:53 +05:00
|
|
|
/** @type {Tags} */
|
2022-03-30 15:52:48 +05:00
|
|
|
this.tags = await Tags.new(this, "tags");
|
2020-04-16 02:14:53 +05:00
|
|
|
/** @type {Tags} */
|
2022-03-30 15:52:48 +05:00
|
|
|
this.colors = await Tags.new(this, "colors");
|
2020-04-16 02:14:53 +05:00
|
|
|
/** @type {Content} */
|
2022-03-30 15:52:48 +05:00
|
|
|
this.content = await Content.new(this, "content", false);
|
2021-09-15 02:16:55 +05:00
|
|
|
/** @type {Attachments} */
|
2022-03-30 15:52:48 +05:00
|
|
|
this.attachments = await Attachments.new(this, "attachments");
|
2021-12-21 13:41:08 +05:00
|
|
|
/**@type {NoteHistory} */
|
2022-03-30 15:52:48 +05:00
|
|
|
this.noteHistory = await NoteHistory.new(this, "notehistory", false);
|
2022-09-07 12:47:02 +05:00
|
|
|
/**@type {Shortcuts} */
|
2022-09-07 14:13:26 +05:00
|
|
|
this.shortcuts = await Shortcuts.new(this, "shortcuts");
|
2022-11-17 13:57:58 +05:00
|
|
|
/**@type {Reminders} */
|
|
|
|
|
this.reminders = await Reminders.new(this, "reminders");
|
2022-11-17 14:11:22 +05:00
|
|
|
/**@type {Relations} */
|
|
|
|
|
this.relations = await Relations.new(this, "relations");
|
2023-07-03 06:45:12 +05:00
|
|
|
/** @type {Notes} */
|
|
|
|
|
this.notes = await Notes.new(this, "notes");
|
2021-02-16 16:56:06 +05:00
|
|
|
|
2023-07-03 06:45:12 +05:00
|
|
|
await this.trash.init();
|
|
|
|
|
|
|
|
|
|
this.monographs.init().catch(console.error);
|
2020-08-24 11:14:16 +05:00
|
|
|
}
|
|
|
|
|
|
2021-10-26 23:06:52 +05:00
|
|
|
disconnectSSE() {
|
|
|
|
|
if (!this.evtSource) return;
|
|
|
|
|
this.evtSource.onopen = null;
|
|
|
|
|
this.evtSource.onmessage = null;
|
|
|
|
|
this.evtSource.onerror = null;
|
|
|
|
|
this.evtSource.close();
|
|
|
|
|
this.evtSource = null;
|
|
|
|
|
}
|
2021-06-16 11:51:39 +05:00
|
|
|
|
2022-03-08 13:17:14 +05:00
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param {{force: boolean, error: any}} args
|
|
|
|
|
*/
|
2021-10-26 23:06:52 +05:00
|
|
|
async connectSSE(args) {
|
2022-07-25 16:26:44 +05:00
|
|
|
if (args && !!args.error) return;
|
2021-10-27 10:53:36 +05:00
|
|
|
await this.sseMutex.runExclusive(async () => {
|
2022-04-14 01:10:25 +05:00
|
|
|
this.eventManager.publish(EVENTS.databaseSyncRequested, true, false);
|
2021-10-26 23:06:52 +05:00
|
|
|
|
2022-03-08 13:17:14 +05:00
|
|
|
const forceReconnect = args && args.force;
|
|
|
|
|
if (
|
|
|
|
|
!NNEventSource ||
|
|
|
|
|
(!forceReconnect &&
|
|
|
|
|
this.evtSource &&
|
|
|
|
|
this.evtSource.readyState === this.evtSource.OPEN)
|
|
|
|
|
)
|
|
|
|
|
return;
|
2021-10-26 23:06:52 +05:00
|
|
|
this.disconnectSSE();
|
|
|
|
|
|
2023-01-18 10:39:34 +05:00
|
|
|
const token = await this.user.tokenManager.getAccessToken();
|
|
|
|
|
if (!token) return;
|
|
|
|
|
|
2021-10-26 23:06:52 +05:00
|
|
|
this.evtSource = new NNEventSource(`${Constants.SSE_HOST}/sse`, {
|
2022-08-31 06:33:37 +05:00
|
|
|
headers: { Authorization: `Bearer ${token}` }
|
2021-10-26 23:06:52 +05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.evtSource.onopen = async () => {
|
|
|
|
|
console.log("SSE: opened channel successfully!");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.evtSource.onerror = function (error) {
|
|
|
|
|
console.log("SSE: error:", error);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.evtSource.onmessage = async (event) => {
|
|
|
|
|
try {
|
|
|
|
|
var { type, data } = JSON.parse(event.data);
|
|
|
|
|
data = JSON.parse(data);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log("SSE: Unsupported message. Message = ", event.data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
2022-07-07 13:36:18 +05:00
|
|
|
// TODO: increase reliablity for this.
|
|
|
|
|
// case "heartbeat": {
|
|
|
|
|
// const { t: serverTime } = data;
|
|
|
|
|
// const localTime = Date.now();
|
|
|
|
|
|
|
|
|
|
// if (!this.lastHeartbeat) {
|
|
|
|
|
// this.lastHeartbeat = { local: localTime, server: serverTime };
|
|
|
|
|
// break;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// const timeElapsed = {
|
|
|
|
|
// local: localTime - this.lastHeartbeat.local,
|
|
|
|
|
// server: serverTime - this.lastHeartbeat.server,
|
|
|
|
|
// };
|
|
|
|
|
// const travelTime = timeElapsed.local - timeElapsed.server;
|
|
|
|
|
// const actualTime = localTime - travelTime;
|
|
|
|
|
|
|
|
|
|
// const diff = actualTime - serverTime;
|
|
|
|
|
|
|
|
|
|
// // Fail several times consecutively before raising an error. This is done to root out
|
|
|
|
|
// // false positives.
|
|
|
|
|
// if (Math.abs(diff) > DIFFERENCE_THRESHOLD) {
|
|
|
|
|
// if (this.timeErrorFailures >= MAX_TIME_ERROR_FAILURES) {
|
|
|
|
|
// EV.publish(EVENTS.systemTimeInvalid, { serverTime, localTime });
|
|
|
|
|
// } else this.timeErrorFailures++;
|
|
|
|
|
// } else this.timeErrorFailures = 0;
|
|
|
|
|
|
|
|
|
|
// this.lastHeartbeat.local = localTime;
|
|
|
|
|
// this.lastHeartbeat.server = serverTime;
|
|
|
|
|
// break;
|
|
|
|
|
// }
|
2022-03-30 15:52:48 +05:00
|
|
|
case "upgrade": {
|
2021-10-26 23:06:52 +05:00
|
|
|
const user = await this.user.getUser();
|
|
|
|
|
user.subscription = data;
|
|
|
|
|
await this.user.setUser(user);
|
|
|
|
|
EV.publish(EVENTS.userSubscriptionUpdated, data);
|
|
|
|
|
break;
|
2022-03-30 15:52:48 +05:00
|
|
|
}
|
2023-10-28 10:43:53 +05:00
|
|
|
case "logout": {
|
|
|
|
|
await this.user.logout(true, data.reason || "Unknown.");
|
2021-10-26 23:06:52 +05:00
|
|
|
break;
|
2022-03-30 15:52:48 +05:00
|
|
|
}
|
|
|
|
|
case "emailConfirmed": {
|
2021-10-26 23:06:52 +05:00
|
|
|
const token = await this.storage.read("token");
|
|
|
|
|
await this.user.tokenManager._refreshToken(token);
|
|
|
|
|
await this.user.fetchUser(true);
|
|
|
|
|
EV.publish(EVENTS.userEmailConfirmed);
|
|
|
|
|
break;
|
2022-03-30 15:52:48 +05:00
|
|
|
}
|
2021-10-26 23:06:52 +05:00
|
|
|
}
|
|
|
|
|
};
|
2020-08-24 11:14:16 +05:00
|
|
|
});
|
2020-02-11 16:28:28 +05:00
|
|
|
}
|
|
|
|
|
|
2020-12-16 14:43:56 +05:00
|
|
|
async lastSynced() {
|
2022-03-31 11:37:13 +05:00
|
|
|
return (await this.storage.read("lastSynced")) || 0;
|
2020-12-16 14:43:56 +05:00
|
|
|
}
|
|
|
|
|
|
2023-11-02 12:21:01 +05:00
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param {{
|
|
|
|
|
* type: "full" | "fetch" | "send";
|
|
|
|
|
* force?: boolean;
|
|
|
|
|
* serverLastSynced?: number;
|
|
|
|
|
* }} options
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
sync(options) {
|
|
|
|
|
return this.syncer.start(options);
|
2020-02-03 12:03:07 +05:00
|
|
|
}
|
2020-09-19 11:36:37 +05:00
|
|
|
|
2020-12-16 12:35:17 +05:00
|
|
|
/**
|
|
|
|
|
*
|
2021-11-24 11:56:18 +05:00
|
|
|
* @param {{AUTH_HOST: string, API_HOST: string, SSE_HOST: string, SUBSCRIPTIONS_HOST: string, ISSUES_HOST: string}} hosts
|
2020-12-16 12:35:17 +05:00
|
|
|
*/
|
|
|
|
|
host(hosts) {
|
2020-09-19 11:36:37 +05:00
|
|
|
if (process.env.NODE_ENV !== "production") {
|
2020-12-16 12:35:17 +05:00
|
|
|
Constants.AUTH_HOST = hosts.AUTH_HOST || Constants.AUTH_HOST;
|
|
|
|
|
Constants.API_HOST = hosts.API_HOST || Constants.API_HOST;
|
|
|
|
|
Constants.SSE_HOST = hosts.SSE_HOST || Constants.SSE_HOST;
|
2021-07-24 12:30:02 +05:00
|
|
|
Constants.SUBSCRIPTIONS_HOST =
|
|
|
|
|
hosts.SUBSCRIPTIONS_HOST || Constants.SUBSCRIPTIONS_HOST;
|
2021-11-24 11:56:18 +05:00
|
|
|
Constants.ISSUES_HOST = hosts.ISSUES_HOST || Constants.ISSUES_HOST;
|
2020-09-19 11:36:37 +05:00
|
|
|
}
|
|
|
|
|
}
|
2021-01-05 14:07:02 +05:00
|
|
|
|
|
|
|
|
version() {
|
|
|
|
|
return http.get(`${Constants.API_HOST}/version`);
|
|
|
|
|
}
|
2021-04-10 10:48:46 +05:00
|
|
|
|
2021-06-28 10:05:39 +05:00
|
|
|
async announcements() {
|
|
|
|
|
let url = `${Constants.API_HOST}/announcements/active`;
|
|
|
|
|
const user = await this.user.getUser();
|
|
|
|
|
if (user) url += `?userId=${user.id}`;
|
|
|
|
|
return http.get(url);
|
2021-04-10 10:48:46 +05:00
|
|
|
}
|
2020-02-03 12:03:07 +05:00
|
|
|
}
|
|
|
|
|
|
2020-02-11 13:12:47 +05:00
|
|
|
export default Database;
|