From b37772870563cb6e7fc88def4b911559c325ef2b Mon Sep 17 00:00:00 2001 From: Abdullah Atta Date: Mon, 21 Aug 2023 16:24:05 +0500 Subject: [PATCH] core: fix more typings --- packages/core/__e2e__/sync.test.js | 116 ++--- packages/core/__e2e__/token-manager.test.js | 16 +- packages/core/__e2e__/user-manager.test.js | 2 +- packages/core/src/api/lookup.ts | 3 +- packages/core/src/api/monographs.js | 6 +- .../core/src/api/sync/__tests__/sync.test.js | 449 ------------------ packages/core/src/api/user-manager.ts | 6 +- packages/core/src/collections/notes.ts | 3 +- packages/core/src/database/backup.ts | 2 +- packages/core/src/migrations.ts | 2 +- packages/core/src/types.ts | 8 + packages/core/src/utils/grouping.ts | 21 +- .../core/src/utils/{random.js => random.ts} | 12 +- .../{title-format.js => title-format.ts} | 16 +- 14 files changed, 104 insertions(+), 558 deletions(-) delete mode 100644 packages/core/src/api/sync/__tests__/sync.test.js rename packages/core/src/utils/{random.js => random.ts} (83%) rename packages/core/src/utils/{title-format.js => title-format.ts} (88%) diff --git a/packages/core/__e2e__/sync.test.js b/packages/core/__e2e__/sync.test.js index bb25831ba..83bc744cc 100644 --- a/packages/core/__e2e__/sync.test.js +++ b/packages/core/__e2e__/sync.test.js @@ -27,7 +27,7 @@ import { delay } from "../__tests__/utils"; import { test, expect, vitest } from "vitest"; import { login } from "./utils"; -const TEST_TIMEOUT = 60 * 1000; +const TEST_TIMEOUT = 30 * 1000; test( "case 1: device A & B should only download the changes from device C (no uploading)", @@ -254,18 +254,33 @@ test( await syncAndWait(deviceA, deviceB); + const colorId = await deviceA.colors.add({ + title: "yellow", + colorCode: "#ffff22" + }); for (let noteId of noteIds) { - await deviceA.notes.note(noteId).color("purple"); expect(deviceB.notes.note(noteId)).toBeTruthy(); - expect(deviceB.notes.note(noteId).data.color).toBeUndefined(); + expect( + deviceB.relations + .from({ id: colorId, type: "color" }, "note") + .findIndex((a) => a.to.id === noteId) + ).toBe(-1); + + await deviceA.relations.add( + { id: colorId, type: "color" }, + { id: noteId, type: "note" } + ); } await syncAndWait(deviceA, deviceB); - const purpleColor = deviceB.colors.tag("purple"); - expect(noteIds.every((id) => purpleColor.noteIds.indexOf(id) > -1)).toBe( - true - ); + expect(deviceB.colors.exists(colorId)).toBeTruthy(); + const purpleNotes = deviceB.relations + .from({ id: colorId, type: "color" }, "note") + .resolved(); + expect( + noteIds.every((id) => purpleNotes.findIndex((p) => p.id === id) > -1) + ).toBe(true); console.log("issue colors log out"); await cleanup(deviceA, deviceB); @@ -289,30 +304,25 @@ test( expect(deviceB.notebooks.notebook(id)).toBeDefined(); - await deviceA.notebooks.notebook(id).topics.add("Topic 1"); + await deviceA.notebooks.topics(id).add({ title: "Topic 1" }); // to create a conflict await delay(1500); - await deviceB.notebooks.notebook(id).topics.add("Topic 2"); + await deviceB.notebooks.topics(id).add({ title: "Topic 2" }); - expect(deviceA.notebooks.notebook(id).topics.has("Topic 1")).toBeTruthy(); - expect(deviceB.notebooks.notebook(id).topics.has("Topic 2")).toBeTruthy(); + expect(deviceA.notebooks.topics(id).has("Topic 1")).toBeTruthy(); + expect(deviceB.notebooks.topics(id).has("Topic 2")).toBeTruthy(); - expect( - deviceB.notebooks.notebook(id).topics.topic("Topic 2")._topic.dateEdited - ).toBeGreaterThan( - deviceA.notebooks.notebook(id).topics.topic("Topic 1")._topic.dateEdited - ); - expect(deviceB.notebooks.notebook(id).dateModified).toBeGreaterThan( - deviceA.notebooks.notebook(id).dateModified - ); + await syncAndWait(deviceA, deviceB, false); - await syncAndWait(deviceB, deviceA, false); + // await delay(1000); - expect(deviceA.notebooks.notebook(id).topics.has("Topic 1")).toBeTruthy(); - expect(deviceB.notebooks.notebook(id).topics.has("Topic 1")).toBeTruthy(); + // await syncAndWait(deviceB, deviceB, false); - expect(deviceA.notebooks.notebook(id).topics.has("Topic 2")).toBeTruthy(); - expect(deviceB.notebooks.notebook(id).topics.has("Topic 2")).toBeTruthy(); + expect(deviceA.notebooks.topics(id).has("Topic 1")).toBeTruthy(); + expect(deviceB.notebooks.topics(id).has("Topic 1")).toBeTruthy(); + + expect(deviceA.notebooks.topics(id).has("Topic 2")).toBeTruthy(); + expect(deviceB.notebooks.topics(id).has("Topic 2")).toBeTruthy(); console.log("issue new topic log out"); await cleanup(deviceA, deviceB); @@ -330,10 +340,9 @@ test( const id = await deviceA.notebooks.add({ title: "Notebook 1", - topics: ["Topic 1"] + topics: [{ title: "Topic 1" }] }); - - const topic = deviceA.notebooks.notebook(id).topics.topic("Topic 1"); + const topic = deviceA.notebooks.topics(id).topic("Topic 1"); await syncAndWait(deviceA, deviceB, false); @@ -342,45 +351,24 @@ test( const noteA = await deviceA.notes.add({ title: "Note 1" }); await deviceA.notes.addToNotebook({ id, topic: topic.id }, noteA); - expect( - deviceA.notebooks.notebook(id).topics.topic(topic.id).totalNotes - ).toBe(1); + expect(topic.totalNotes).toBe(1); await delay(2000); const noteB = await deviceB.notes.add({ title: "Note 2" }); await deviceB.notes.addToNotebook({ id, topic: topic.id }, noteB); - expect( - deviceB.notebooks.notebook(id).topics.topic(topic.id).totalNotes - ).toBe(1); + expect(deviceB.notebooks.topics(id).topic(topic.id).totalNotes).toBe(1); - ctx.onTestFailed(() => { - console.log(deviceA.notes.topicReferences.get(topic.id), noteA); - console.log(deviceB.notes.topicReferences.get(topic.id), noteB); - - deviceB.notes.topicReferences.rebuild(); - deviceA.notes.topicReferences.rebuild(); - - console.log(deviceA.notes.topicReferences.get(topic.id), noteA); - console.log(deviceB.notes.topicReferences.get(topic.id), noteB); - }); await syncAndWait(deviceB, deviceA, false); + await syncAndWait(deviceA, deviceB, false); - expect(deviceA.notes.note(noteB)).toBeDefined(); - expect(deviceB.notes.note(noteA)).toBeDefined(); + expect(deviceA.notebooks.topics(id).topic(topic.id).totalNotes).toBe(2); + expect(deviceB.notebooks.topics(id).topic(topic.id).totalNotes).toBe(2); expect(deviceA.notes.note(noteA).data.notebooks).toHaveLength(1); expect(deviceA.notes.note(noteB).data.notebooks).toHaveLength(1); - expect( - deviceA.notebooks.notebook(id).topics.topic(topic.id).totalNotes - ).toBe(2); - expect( - deviceB.notebooks.notebook(id).topics.topic(topic.id).totalNotes - ).toBe(2); - - console.log("issue assigning 2 notes log out"); await cleanup(deviceA, deviceB); }, TEST_TIMEOUT @@ -407,7 +395,12 @@ async function initializeDevice(id, capabilities = []) { }); const device = new Database(); - device.setup(new NodeStorageInterface(), EventSource, FS, Compressor); + device.setup({ + storage: new NodeStorageInterface(), + eventsource: EventSource, + fs: FS, + compressor: Compressor + }); await device.init(); @@ -442,22 +435,12 @@ async function cleanup(...devices) { * @returns */ function syncAndWait(deviceA, deviceB, force = false) { - return new Promise((resolve, reject) => { - const ref2 = deviceB.eventManager.subscribe( - EVENTS.databaseSyncRequested, - (full, force, lastSynced) => { - console.log("sync requested by device A", full, force, lastSynced); - ref2.unsubscribe(); - deviceB.sync(full, force, lastSynced).catch(reject); - } - ); - + return new Promise((resolve) => { const ref = deviceB.eventManager.subscribe(EVENTS.syncCompleted, () => { ref.unsubscribe(); console.log("sync completed."); resolve(); }); - console.log( "waiting for sync...", "Device A:", @@ -465,7 +448,6 @@ function syncAndWait(deviceA, deviceB, force = false) { "Device B:", deviceB.syncer.sync.syncing ); - - deviceA.sync(true, force).catch(reject); + deviceA.sync(true, force); }); } diff --git a/packages/core/__e2e__/token-manager.test.js b/packages/core/__e2e__/token-manager.test.js index 75ebdf650..13f52e483 100644 --- a/packages/core/__e2e__/token-manager.test.js +++ b/packages/core/__e2e__/token-manager.test.js @@ -27,15 +27,15 @@ test( databaseTest().then(async (db) => { await expect(login(db)).resolves.not.toThrow(); - const token = await db.user.tokenManager.getToken(); + const token = await db.tokenManager.getToken(); expect(token).toBeDefined(); expect( await Promise.all([ - db.user.tokenManager._refreshToken(true), - db.user.tokenManager._refreshToken(true), - db.user.tokenManager._refreshToken(true), - db.user.tokenManager._refreshToken(true) + db.tokenManager._refreshToken(true), + db.tokenManager._refreshToken(true), + db.tokenManager._refreshToken(true), + db.tokenManager._refreshToken(true) ]) ).toHaveLength(4); }), @@ -48,11 +48,11 @@ test( databaseTest().then(async (db) => { await expect(login(db)).resolves.not.toThrow(); - const token = await db.user.tokenManager.getToken(); + const token = await db.tokenManager.getToken(); expect(token).toBeDefined(); for (let i = 0; i <= 5; ++i) { - await db.user.tokenManager._refreshToken(true); - await db.user.tokenManager.saveToken(token); + await db.tokenManager._refreshToken(true); + await db.tokenManager.saveToken(token); } }), 30000 diff --git a/packages/core/__e2e__/user-manager.test.js b/packages/core/__e2e__/user-manager.test.js index 311d330ab..361727139 100644 --- a/packages/core/__e2e__/user-manager.test.js +++ b/packages/core/__e2e__/user-manager.test.js @@ -38,7 +38,7 @@ test( databaseTest().then(async (db) => { await expect(login(db)).resolves.not.toThrow(); - await expect(db.user.tokenManager.getToken()).resolves.toBeDefined(); + await expect(db.tokenManager.getToken()).resolves.toBeDefined(); }), 30000 ); diff --git a/packages/core/src/api/lookup.ts b/packages/core/src/api/lookup.ts index 28945d76b..779545292 100644 --- a/packages/core/src/api/lookup.ts +++ b/packages/core/src/api/lookup.ts @@ -21,6 +21,7 @@ import { filter, parse } from "liqe"; import Database from "."; import { Attachment, + GroupedItems, Note, Notebook, Reminder, @@ -66,7 +67,7 @@ export default class Lookup { return this.byTitle(array, query); } - tags(array: Tag[], query: string) { + tags(array: GroupedItems, query: string) { return this.byTitle(array, query); } diff --git a/packages/core/src/api/monographs.js b/packages/core/src/api/monographs.js index ac011a9ed..47fba028f 100644 --- a/packages/core/src/api/monographs.js +++ b/packages/core/src/api/monographs.js @@ -38,7 +38,7 @@ class Monographs { async init() { try { const user = await this._db.user.getUser(); - const token = await this._db.user.tokenManager.getAccessToken(); + const token = await this._db.tokenManager.getAccessToken(); if (!user || !token || !user.isEmailConfirmed) return; let monographs = await this._db.storage().read("monographs", true); monographs = await http.get(`${Constants.API_HOST}/monographs`, token); @@ -82,7 +82,7 @@ class Monographs { let update = !!this.isPublished(noteId); const user = await this._db.user.getUser(); - const token = await this._db.user.tokenManager.getAccessToken(); + const token = await this._db.tokenManager.getAccessToken(); if (!user || !token) throw new Error("Please login to publish a note."); const note = this._db.notes.note(noteId); @@ -136,7 +136,7 @@ class Monographs { if (!this.monographs) await this.init(); const user = await this._db.user.getUser(); - const token = await this._db.user.tokenManager.getAccessToken(); + const token = await this._db.tokenManager.getAccessToken(); if (!user || !token) throw new Error("Please login to publish a note."); // const note = this._db.notes.note(noteId); diff --git a/packages/core/src/api/sync/__tests__/sync.test.js b/packages/core/src/api/sync/__tests__/sync.test.js deleted file mode 100644 index 79f4507df..000000000 --- a/packages/core/src/api/sync/__tests__/sync.test.js +++ /dev/null @@ -1,449 +0,0 @@ -/* -This file is part of the Notesnook project (https://notesnook.com/) - -Copyright (C) 2023 Streetwriters (Private) Limited - -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 . -*/ - -import Database from "../../index"; -import { NodeStorageInterface } from "../../../../__mocks__/node-storage.mock"; -import { FS } from "../../../../__mocks__/fs.mock"; -import Compressor from "../../../../__mocks__/compressor.mock"; -import { CHECK_IDS, EV, EVENTS } from "../../../common"; -import { EventSource } from "event-source-polyfill"; -import { delay } from "../../../../__tests__/utils"; -import { test, expect, vitest } from "vitest"; -import { login } from "../../../../__e2e__/utils"; - -const TEST_TIMEOUT = 30 * 1000; - -test( - "case 1: device A & B should only download the changes from device C (no uploading)", - async () => { - const types = []; - function onSyncProgress({ type }) { - types.push(type); - } - - const [deviceA, deviceB, deviceC] = await Promise.all([ - initializeDevice("deviceA"), - initializeDevice("deviceB"), - initializeDevice("deviceC") - ]); - - deviceA.eventManager.subscribe(EVENTS.syncProgress, onSyncProgress); - deviceB.eventManager.subscribe(EVENTS.syncProgress, onSyncProgress); - - await deviceC.notes.add({ title: "new note 1" }); - await syncAndWait(deviceC, deviceC); - - expect(types.every((t) => t === "download")).toBe(true); - - await cleanup(deviceA, deviceB, deviceC); - }, - TEST_TIMEOUT -); - -test( - "case 3: Device A & B have unsynced changes but server has nothing", - async () => { - const [deviceA, deviceB] = await Promise.all([ - initializeDevice("deviceA"), - initializeDevice("deviceB") - ]); - - const note1Id = await deviceA.notes.add({ - title: "Test note from device A" - }); - const note2Id = await deviceB.notes.add({ - title: "Test note from device B" - }); - - await syncAndWait(deviceA, deviceB); - - expect(deviceA.notes.note(note2Id)).toBeTruthy(); - expect(deviceB.notes.note(note1Id)).toBeTruthy(); - expect(deviceA.notes.note(note1Id)).toBeTruthy(); - expect(deviceB.notes.note(note2Id)).toBeTruthy(); - - await cleanup(deviceA, deviceA); - }, - TEST_TIMEOUT -); - -// test( -// "case 4: Device A's sync is interrupted halfway and Device B makes some changes afterwards and syncs.", -// async () => { -// const deviceA = await initializeDevice("deviceA"); -// const deviceB = await initializeDevice("deviceB"); - -// const unsyncedNoteIds = []; -// for (let i = 0; i < 10; ++i) { -// const id = await deviceA.notes.add({ -// title: `Test note ${i} from device A`, -// }); -// unsyncedNoteIds.push(id); -// } - -// const half = unsyncedNoteIds.length / 2 + 1; -// deviceA.eventManager.subscribe( -// EVENTS.syncProgress, -// async ({ type, current }) => { -// if (type === "upload" && current === half) { -// await deviceA.syncer.stop(); -// } -// } -// ); - -// await expect(deviceA.sync(true)).rejects.toThrow(); - -// let syncedNoteIds = []; -// for (let i = 0; i < unsyncedNoteIds.length; ++i) { -// const expectedNoteId = unsyncedNoteIds[i]; -// if (deviceB.notes.note(expectedNoteId)) -// syncedNoteIds.push(expectedNoteId); -// } -// expect( -// syncedNoteIds.length === half - 1 || syncedNoteIds.length === half -// ).toBe(true); - -// const deviceBNoteId = await deviceB.notes.add({ -// title: "Test note of case 4 from device B", -// }); - -// await deviceB.sync(true); - -// await syncAndWait(deviceA, deviceB); - -// expect(deviceA.notes.note(deviceBNoteId)).toBeTruthy(); -// expect( -// unsyncedNoteIds -// .map((id) => !!deviceB.notes.note(id)) -// .every((res) => res === true) -// ).toBe(true); - -// await cleanup(deviceA, deviceB); -// }, -// -// ); - -// test.only( -// "case 5: Device A's sync is interrupted halfway and Device B makes changes on the same note's content that didn't get synced on Device A due to interruption.", -// async () => { -// const deviceA = await initializeDevice("deviceA"); -// const deviceB = await initializeDevice("deviceB"); - -// const noteIds = []; -// for (let i = 0; i < 10; ++i) { -// const id = await deviceA.notes.add({ -// content: { -// type: "tiptap", -// data: `

deviceA=true

`, -// }, -// }); -// noteIds.push(id); -// } - -// await deviceA.sync(true); -// await deviceB.sync(true); - -// const unsyncedNoteIds = []; -// for (let id of noteIds) { -// const noteId = await deviceA.notes.add({ -// id, -// content: { -// type: "tiptap", -// data: `

deviceA=true+changed=true

`, -// }, -// }); -// unsyncedNoteIds.push(noteId); -// } - -// deviceA.eventManager.subscribe( -// EVENTS.syncProgress, -// async ({ type, total, current }) => { -// const half = total / 2 + 1; -// if (type === "upload" && current === half) { -// await deviceA.syncer.stop(); -// } -// } -// ); - -// await expect(deviceA.sync(true)).rejects.toThrow(); - -// await delay(10 * 1000); - -// for (let id of unsyncedNoteIds) { -// await deviceB.notes.add({ -// id, -// content: { -// type: "tiptap", -// data: "

changes from device B

", -// }, -// }); -// } - -// const error = await withError(async () => { -// await deviceB.sync(true); -// await deviceA.sync(true); -// }); - -// expect(error).not.toBeInstanceOf(NoErrorThrownError); -// expect(error.message.includes("Merge")).toBeTruthy(); - -// await cleanup(deviceA, deviceB); -// }, -// -// ); - -test( - "issue: running force sync from device A makes device B always download everything", - async () => { - const [deviceA, deviceB] = await Promise.all([ - initializeDevice("deviceA"), - initializeDevice("deviceB") - ]); - - await syncAndWait(deviceA, deviceB, true); - - const handler = vitest.fn(); - deviceB.eventManager.subscribe(EVENTS.syncProgress, handler); - - await deviceB.sync(true); - - expect(handler).not.toHaveBeenCalled(); - - await cleanup(deviceB); - }, - TEST_TIMEOUT -); - -test( - "issue: colors are not properly created if multiple notes are synced together", - async () => { - const [deviceA, deviceB] = await Promise.all([ - initializeDevice("deviceA", [CHECK_IDS.noteColor]), - initializeDevice("deviceB", [CHECK_IDS.noteColor]) - ]); - - const noteIds = []; - for (let i = 0; i < 3; ++i) { - const id = await deviceA.notes.add({ - content: { - type: "tiptap", - data: `

deviceA=true

` - } - }); - noteIds.push(id); - } - - await syncAndWait(deviceA, deviceB); - - const colorId = await deviceA.colors.add({ - title: "yellow", - colorCode: "#ffff22" - }); - for (let noteId of noteIds) { - expect(deviceB.notes.note(noteId)).toBeTruthy(); - expect( - deviceB.relations - .from({ id: colorId, type: "color" }, "note") - .findIndex((a) => a.to.id === noteId) - ).toBe(-1); - - await deviceA.relations.add( - { id: colorId, type: "color" }, - { id: noteId, type: "note" } - ); - } - - await syncAndWait(deviceA, deviceB); - - expect(deviceB.colors.exists(colorId)).toBeTruthy(); - const purpleNotes = deviceB.relations - .from({ id: colorId, type: "color" }, "note") - .resolved(); - expect( - noteIds.every((id) => purpleNotes.findIndex((p) => p.id === id) > -1) - ).toBe(true); - - await cleanup(deviceA, deviceB); - }, - TEST_TIMEOUT -); - -test( - "issue: new topic on device A gets replaced by the new topic on device B", - async () => { - const [deviceA, deviceB] = await Promise.all([ - initializeDevice("deviceA"), - initializeDevice("deviceB") - ]); - // const deviceA = await initializeDevice("deviceA"); - // const deviceB = await initializeDevice("deviceB"); - - const id = await deviceA.notebooks.add({ title: "Notebook 1" }); - - await syncAndWait(deviceA, deviceB, false); - - expect(deviceB.notebooks.notebook(id)).toBeDefined(); - - await deviceA.notebooks.topics(id).add({ title: "Topic 1" }); - // to create a conflict - await delay(1500); - await deviceB.notebooks.topics(id).add({ title: "Topic 2" }); - - expect(deviceA.notebooks.topics(id).has("Topic 1")).toBeTruthy(); - expect(deviceB.notebooks.topics(id).has("Topic 2")).toBeTruthy(); - - await syncAndWait(deviceA, deviceB, false); - - // await delay(1000); - - // await syncAndWait(deviceB, deviceB, false); - - expect(deviceA.notebooks.topics(id).has("Topic 1")).toBeTruthy(); - expect(deviceB.notebooks.topics(id).has("Topic 1")).toBeTruthy(); - - expect(deviceA.notebooks.topics(id).has("Topic 2")).toBeTruthy(); - expect(deviceB.notebooks.topics(id).has("Topic 2")).toBeTruthy(); - - await cleanup(deviceA, deviceB); - }, - TEST_TIMEOUT -); - -test( - "issue: remove notebook reference from notes that are removed from topic during merge", - async () => { - const [deviceA, deviceB] = await Promise.all([ - initializeDevice("deviceA"), - initializeDevice("deviceB") - ]); - - const id = await deviceA.notebooks.add({ - title: "Notebook 1", - topics: [{ title: "Topic 1" }] - }); - const topic = deviceA.notebooks.topics(id).topic("Topic 1"); - - await syncAndWait(deviceA, deviceB, false); - - expect(deviceB.notebooks.notebook(id)).toBeDefined(); - - const noteA = await deviceA.notes.add({ title: "Note 1" }); - await deviceA.notes.addToNotebook({ id, topic: topic.id }, noteA); - - expect(topic.totalNotes).toBe(1); - - await delay(2000); - - const noteB = await deviceB.notes.add({ title: "Note 2" }); - await deviceB.notes.addToNotebook({ id, topic: topic.id }, noteB); - - expect(deviceB.notebooks.topics(id).topic(topic.id).totalNotes).toBe(1); - - await syncAndWait(deviceB, deviceA, false); - await syncAndWait(deviceA, deviceB, false); - - expect(deviceA.notebooks.topics(id).topic(topic.id).totalNotes).toBe(2); - expect(deviceB.notebooks.topics(id).topic(topic.id).totalNotes).toBe(2); - - expect(deviceA.notes.note(noteA).data.notebooks).toHaveLength(1); - expect(deviceA.notes.note(noteB).data.notebooks).toHaveLength(1); - - await cleanup(deviceA, deviceB); - }, - TEST_TIMEOUT -); - -/** - * - * @param {string} id - * @returns {Promise} - */ -async function initializeDevice(id, capabilities = []) { - console.time(`Init ${id}`); - EV.subscribe(EVENTS.userCheckStatus, async (type) => { - return { - type, - result: capabilities.indexOf(type) > -1 - }; - }); - EV.subscribe(EVENTS.syncCheckStatus, async (type) => { - return { - type, - result: true - }; - }); - - const device = new Database(); - device.setup({ - storage: new NodeStorageInterface(), - eventsource: EventSource, - fs: FS, - compressor: Compressor - }); - - await device.init(); - - await login(device); - - await device.user.resetUser(false); - - await device.sync(true, false); - - console.timeEnd(`Init ${id}`); - return device; -} - -/** - * - * @param {...Database} devices - */ -async function cleanup(...devices) { - await Promise.all([ - devices.map(async (device) => { - await device.syncer.stop(); - await device.user.logout(); - device.eventManager.unsubscribeAll(); - }) - ]); - EV.unsubscribeAll(); -} - -/** - * - * @param {Database} deviceA - * @param {Database} deviceB - * @returns - */ -function syncAndWait(deviceA, deviceB, force = false) { - return new Promise((resolve) => { - const ref = deviceB.eventManager.subscribe(EVENTS.syncCompleted, () => { - ref.unsubscribe(); - console.log("sync completed."); - resolve(); - }); - console.log( - "waiting for sync...", - "Device A:", - deviceA.syncer.sync.syncing, - "Device B:", - deviceB.syncer.sync.syncing - ); - deviceA.sync(true, force); - }); -} diff --git a/packages/core/src/api/user-manager.ts b/packages/core/src/api/user-manager.ts index 5b5c5a660..366275ce6 100644 --- a/packages/core/src/api/user-manager.ts +++ b/packages/core/src/api/user-manager.ts @@ -26,16 +26,18 @@ import { HealthCheck } from "./healthcheck"; import Database from "."; import { Cipher, SerializedKey } from "@notesnook/crypto"; +export type AuthenticatorType = "app" | "sms" | "email"; export type User = { id: string; email: string; isEmailConfirmed: boolean; salt: string; attachmentsKey?: Cipher; + marketingConsent?: boolean; mfa: { isEnabled: boolean; - primaryMethod: string; - secondaryMethod: string; + primaryMethod: AuthenticatorType; + secondaryMethod?: AuthenticatorType; remainingValidCodes: number; }; subscription: { diff --git a/packages/core/src/collections/notes.ts b/packages/core/src/collections/notes.ts index 63b31dc1b..7df2f8018 100644 --- a/packages/core/src/collections/notes.ts +++ b/packages/core/src/collections/notes.ts @@ -29,7 +29,6 @@ import { CHECK_IDS, checkIsUserPremium } from "../common"; import { buildFromTemplate } from "../utils/templates"; import { Note, - UnencryptedContentItem, TrashOrItem, isTrashItem, MaybeDeletedItem, @@ -43,7 +42,7 @@ import { NoteContent } from "./session-content"; type NotebookReference = { id: string; topic?: string; rebuildCache?: boolean }; type ExportOptions = { format: "html" | "md" | "txt" | "md-frontmatter"; - contentItem?: UnencryptedContentItem; + contentItem?: NoteContent; rawContent?: string; }; diff --git a/packages/core/src/database/backup.ts b/packages/core/src/database/backup.ts index 90fad7dac..b9fcd0e8c 100644 --- a/packages/core/src/database/backup.ts +++ b/packages/core/src/database/backup.ts @@ -148,7 +148,7 @@ export default class Backup { constructor(private readonly db: Database) {} lastBackupTime() { - return this.db.storage().read("lastBackupTime"); + return this.db.storage().read("lastBackupTime"); } async updateBackupTime() { diff --git a/packages/core/src/migrations.ts b/packages/core/src/migrations.ts index dba1739f0..8bc856542 100644 --- a/packages/core/src/migrations.ts +++ b/packages/core/src/migrations.ts @@ -188,7 +188,7 @@ const migrations: Migration[] = [ version: 5.9, items: { tag: async (item, db) => { - const alias = db.settings?.getAlias(item.id); + const alias = db.settings.getAlias(item.id); item.title = alias || item.title; item.id = getId(item.dateCreated); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index f923eb08b..e097489ab 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -26,6 +26,8 @@ export type GroupOptions = { sortDirection: "desc" | "asc"; }; +export type GroupedItems = (T | GroupHeader)[]; + export type GroupingKey = | "home" | "notes" @@ -33,12 +35,14 @@ export type GroupingKey = | "tags" | "topics" | "trash" + | "reminders" | "favorites"; export type ValueOf = T[keyof T]; export type GroupHeader = { type: "header"; + id: string; title: string; }; @@ -342,3 +346,7 @@ export function isTrashItem( ): item is TrashItem { return !isDeleted(item) && item.type === "trash"; } + +export function isGroupHeader(item: GroupHeader | Item): item is GroupHeader { + return item.type === "header"; +} diff --git a/packages/core/src/utils/grouping.ts b/packages/core/src/utils/grouping.ts index 373e120ba..905928ecb 100644 --- a/packages/core/src/utils/grouping.ts +++ b/packages/core/src/utils/grouping.ts @@ -18,13 +18,18 @@ along with this program. If not, see . */ import { isReminderActive } from "../collections/reminders"; -import { GroupHeader, GroupOptions, GroupableItem, Reminder } from "../types"; +import { + GroupedItems, + GroupOptions, + GroupableItem, + Item, + Reminder +} from "../types"; import { getWeekGroupFromTimestamp, MONTHS_FULL } from "./date"; type EvaluateKeyFunction = (item: T) => string; -type GroupedItems = (T | GroupHeader)[]; -const getSortValue = ( +export const getSortValue = ( options: GroupOptions, item: T ) => { @@ -121,11 +126,14 @@ export function groupArray( } const groups: GroupedItems = []; if (conflicted.length > 0) - groups.push({ title: "Conflicted", type: "header" }, ...conflicted); + groups.push( + { title: "Conflicted", type: "header", id: "conflicted" }, + ...conflicted + ); if (pinned.length > 0) - groups.push({ title: "Pinned", type: "header" }, ...pinned); + groups.push({ title: "Pinned", type: "header", id: "pinned" }, ...pinned); if (others.length > 0) - groups.push({ title: "All", type: "header" }, ...others); + groups.push({ title: "All", type: "header", id: "all" }, ...others); return groups; } @@ -181,6 +189,7 @@ function flattenGroups(groups: Map) { if (groupItems.length <= 0) return; items.push({ title: groupTitle, + id: groupTitle.toLowerCase(), type: "header" }); groupItems.forEach((item) => items.push(item)); diff --git a/packages/core/src/utils/random.js b/packages/core/src/utils/random.ts similarity index 83% rename from packages/core/src/utils/random.js rename to packages/core/src/utils/random.ts index 93a777f91..d87b82c05 100644 --- a/packages/core/src/utils/random.js +++ b/packages/core/src/utils/random.ts @@ -17,15 +17,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -/** - * - * @param {number} size - * @returns {Buffer} - */ -export function randomBytes(size) { +export function randomBytes(size: number) { if (!globalThis.crypto || !crypto) throw new Error("Crypto is not supported on this platform."); - if (crypto.randomBytes) return crypto.randomBytes(size); + if ("randomBytes" in crypto && typeof crypto.randomBytes === "function") + return crypto.randomBytes(size); if (!crypto.getRandomValues) throw new Error( @@ -39,7 +35,7 @@ export function randomBytes(size) { export function randomInt() { const randomBuffer = randomBytes(1); - let randomNumber = randomBuffer[0] / 0xff; // / (0xffffffff + 1); + const randomNumber = randomBuffer[0] / 0xff; // / (0xffffffff + 1); return Math.floor(randomNumber * 0xffffff); } diff --git a/packages/core/src/utils/title-format.js b/packages/core/src/utils/title-format.ts similarity index 88% rename from packages/core/src/utils/title-format.js rename to packages/core/src/utils/title-format.ts index 4eaf842be..adab9a23d 100644 --- a/packages/core/src/utils/title-format.js +++ b/packages/core/src/utils/title-format.ts @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { formatDate } from "./date"; +import { TimeFormat, formatDate } from "./date"; export const NEWLINE_STRIP_REGEX = /[\r\n\t\v]+/gm; @@ -29,20 +29,18 @@ const TIMESTAMP_REGEX = /\$timestamp\$/g; const DATE_TIME_STRIP_REGEX = /[\\\-: ]/g; export function formatTitle( - titleFormat, - dateFormat, - timeFormat, - headline, - totalNotes + titleFormat: string, + dateFormat: string, + timeFormat: TimeFormat, + headline = "", + totalNotes = 0 ) { const date = formatDate(Date.now(), { dateFormat, - timeFormat, type: "date" }); const time = formatDate(Date.now(), { - dateFormat, timeFormat, type: "time" }); @@ -54,5 +52,5 @@ export function formatTitle( .replace(TIME_REGEX, time) .replace(HEADLINE_REGEX, headline || "") .replace(TIMESTAMP_REGEX, timestamp) - .replace(COUNT_REGEX, totalNotes + 1); + .replace(COUNT_REGEX, `${totalNotes + 1}`); }