Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc3ba79df0 | ||
|
|
2609d73bbd | ||
|
|
1c753a6f8f | ||
|
|
d2b76399bc | ||
|
|
1c500ff052 |
3
bin/generateEncryptionKey.js
Normal file
3
bin/generateEncryptionKey.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import crypto from 'crypto'
|
||||||
|
|
||||||
|
console.log(crypto.randomBytes(32).toString('hex'))
|
||||||
@@ -24,9 +24,6 @@ mac:
|
|||||||
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||||
notarize: false
|
|
||||||
cscLink: $CSC_LINK
|
|
||||||
cscKeyPassword: $CSC_KEY_PASSWORD
|
|
||||||
dmg:
|
dmg:
|
||||||
artifactName: ${name}-${version}.${ext}
|
artifactName: ${name}-${version}.${ext}
|
||||||
linux:
|
linux:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import fs from "fs/promises";
|
|||||||
import path, { join } from "path";
|
import path, { join } from "path";
|
||||||
import { Index } from "flexsearch";
|
import { Index } from "flexsearch";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
import sodium from "libsodium-wrappers";
|
||||||
import __cjs_mod__ from "node:module";
|
import __cjs_mod__ from "node:module";
|
||||||
const __filename = import.meta.filename;
|
const __filename = import.meta.filename;
|
||||||
const __dirname = import.meta.dirname;
|
const __dirname = import.meta.dirname;
|
||||||
@@ -88,23 +89,73 @@ class PluginConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
class NotesAPI {
|
class NotesAPI {
|
||||||
constructor(adapter) {
|
constructor(adapter, encryptionKey = null) {
|
||||||
if (!adapter) {
|
if (!adapter) {
|
||||||
throw new Error("NotesAPI requires a storage adapter");
|
throw new Error("NotesAPI requires a storage adapter");
|
||||||
}
|
}
|
||||||
this.adapter = adapter;
|
this.adapter = adapter;
|
||||||
this.notesCache = /* @__PURE__ */ new Map();
|
this.notesCache = /* @__PURE__ */ new Map();
|
||||||
|
this.encryptionKey = encryptionKey || process.env.NOTES_ENCRYPTION_KEY;
|
||||||
|
this._sodiumReady = false;
|
||||||
this.index = new Index({
|
this.index = new Index({
|
||||||
tokenize: "tolerant",
|
tokenize: "tolerant",
|
||||||
resolution: 9
|
resolution: 9
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
async _initSodium() {
|
||||||
|
if (!this._sodiumReady) {
|
||||||
|
await sodium.ready;
|
||||||
|
this._sodiumReady = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_encrypt(note) {
|
||||||
|
if (!this.encryptionKey) {
|
||||||
|
throw new Error("Encryption key not set");
|
||||||
|
}
|
||||||
|
const key = Buffer.from(this.encryptionKey, "hex");
|
||||||
|
if (key.length !== 32) {
|
||||||
|
throw new Error("Encryption key must be 64 hex characters (32 bytes)");
|
||||||
|
}
|
||||||
|
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
|
||||||
|
const message = JSON.stringify(note);
|
||||||
|
const ciphertext = sodium.crypto_secretbox_easy(
|
||||||
|
Buffer.from(message),
|
||||||
|
nonce,
|
||||||
|
key
|
||||||
|
);
|
||||||
|
const combined = Buffer.concat([nonce, ciphertext]);
|
||||||
|
return combined.toString("base64");
|
||||||
|
}
|
||||||
|
_decrypt(encryptedData) {
|
||||||
|
if (!this.encryptionKey) {
|
||||||
|
throw new Error("Encryption key not set");
|
||||||
|
}
|
||||||
|
const key = Buffer.from(this.encryptionKey, "hex");
|
||||||
|
if (key.length !== 32) {
|
||||||
|
throw new Error("Encryption key must be 64 hex characters (32 bytes)");
|
||||||
|
}
|
||||||
|
const combined = Buffer.from(encryptedData, "base64");
|
||||||
|
const nonce = combined.slice(0, sodium.crypto_secretbox_NONCEBYTES);
|
||||||
|
const ciphertext = combined.slice(sodium.crypto_secretbox_NONCEBYTES);
|
||||||
|
const decrypted = sodium.crypto_secretbox_open_easy(
|
||||||
|
ciphertext,
|
||||||
|
nonce,
|
||||||
|
key
|
||||||
|
);
|
||||||
|
return JSON.parse(decrypted.toString());
|
||||||
|
}
|
||||||
async init() {
|
async init() {
|
||||||
|
await this._initSodium();
|
||||||
await this.adapter.init();
|
await this.adapter.init();
|
||||||
const notes = await this.adapter.getAll();
|
const encryptedNotes = await this.adapter.getAll();
|
||||||
for (const note of notes) {
|
for (const encryptedNote of encryptedNotes) {
|
||||||
|
try {
|
||||||
|
const note = this._decrypt(encryptedNote.data || encryptedNote);
|
||||||
this.notesCache.set(note.id, note);
|
this.notesCache.set(note.id, note);
|
||||||
this.index.add(note.id, note.title + "\n" + note.content);
|
this.index.add(note.id, note.title + "\n" + note.content);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to decrypt note:", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* -----------------------
|
/* -----------------------
|
||||||
@@ -136,9 +187,13 @@ class NotesAPI {
|
|||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
content
|
content
|
||||||
};
|
};
|
||||||
|
const encryptedNote = {
|
||||||
|
id: note.id,
|
||||||
|
data: this._encrypt(note)
|
||||||
|
};
|
||||||
this.notesCache.set(id, note);
|
this.notesCache.set(id, note);
|
||||||
this.index.add(id, note.title + "\n" + content);
|
this.index.add(id, note.title + "\n" + content);
|
||||||
await this.adapter.create(note);
|
await this.adapter.create(encryptedNote);
|
||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
async deleteNote(id) {
|
async deleteNote(id) {
|
||||||
@@ -151,8 +206,12 @@ class NotesAPI {
|
|||||||
if (!note) throw new Error("Note not found");
|
if (!note) throw new Error("Note not found");
|
||||||
note.content = content;
|
note.content = content;
|
||||||
note.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
note.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
||||||
|
const encryptedNote = {
|
||||||
|
id: note.id,
|
||||||
|
data: this._encrypt(note)
|
||||||
|
};
|
||||||
this.index.update(id, note.title + "\n" + content);
|
this.index.update(id, note.title + "\n" + content);
|
||||||
await this.adapter.update(note);
|
await this.adapter.update(encryptedNote);
|
||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
async updateNoteMetadata(id, updates = {}) {
|
async updateNoteMetadata(id, updates = {}) {
|
||||||
@@ -171,8 +230,12 @@ class NotesAPI {
|
|||||||
note.category = updates.category;
|
note.category = updates.category;
|
||||||
}
|
}
|
||||||
note.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
note.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
||||||
|
const encryptedNote = {
|
||||||
|
id: note.id,
|
||||||
|
data: this._encrypt(note)
|
||||||
|
};
|
||||||
this.index.update(id, note.title + "\n" + note.content);
|
this.index.update(id, note.title + "\n" + note.content);
|
||||||
await this.adapter.update(note);
|
await this.adapter.update(encryptedNote);
|
||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
search(query) {
|
search(query) {
|
||||||
@@ -235,13 +298,13 @@ app.whenReady().then(async () => {
|
|||||||
const registry = new PluginRegistry();
|
const registry = new PluginRegistry();
|
||||||
registry.register(filesystemPlugin);
|
registry.register(filesystemPlugin);
|
||||||
registry.register(supabasePlugin);
|
registry.register(supabasePlugin);
|
||||||
await new PluginConfig(filesystemPlugin).load();
|
const config = await new PluginConfig(filesystemPlugin).load();
|
||||||
const plugin = registry.get(supabasePlugin.id);
|
const plugin = registry.get(config.activeAdapter);
|
||||||
const adapter = plugin.createAdapter({
|
const adapter = plugin.createAdapter(config.adapterConfig);
|
||||||
supabaseKey: process.env.SUPABASE_KEY,
|
const notesAPI = new NotesAPI(
|
||||||
supabaseUrl: process.env.SUPABASE_URL
|
adapter,
|
||||||
});
|
"729a0d21d783654c68f1a0123e2a0e986350de536b5324f1f35876ea12ffeaf5"
|
||||||
const notesAPI = new NotesAPI(adapter);
|
);
|
||||||
await notesAPI.init();
|
await notesAPI.init();
|
||||||
ipcMain.handle("notesAPI:call", (_, method, args) => {
|
ipcMain.handle("notesAPI:call", (_, method, args) => {
|
||||||
if (!notesAPI[method]) {
|
if (!notesAPI[method]) {
|
||||||
|
|||||||
149
package-lock.json
generated
149
package-lock.json
generated
@@ -1,18 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "app.takerofnotes.com",
|
"name": "takerofnotes-app",
|
||||||
"version": "1.0.0",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "app.takerofnotes.com",
|
"name": "takerofnotes-app",
|
||||||
"version": "1.0.0",
|
"version": "0.1.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron-toolkit/preload": "^3.0.2",
|
"@electron-toolkit/preload": "^3.0.2",
|
||||||
"@electron-toolkit/utils": "^4.0.0",
|
"@electron-toolkit/utils": "^4.0.0",
|
||||||
"@fuzzco/font-loader": "^1.0.2",
|
"@fuzzco/font-loader": "^1.0.2",
|
||||||
"@takerofnotes/plugin-filesystem": "^0.1.1",
|
"@takerofnotes/plugin-filesystem": "^0.2.0",
|
||||||
"@takerofnotes/plugin-supabase": "^0.1.0",
|
"@takerofnotes/plugin-supabase": "^0.1.0",
|
||||||
"@tiptap/extension-document": "^3.19.0",
|
"@tiptap/extension-document": "^3.19.0",
|
||||||
"@tiptap/extension-image": "^3.19.0",
|
"@tiptap/extension-image": "^3.19.0",
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
"flexsearch": "^0.8.212",
|
"flexsearch": "^0.8.212",
|
||||||
"gsap": "^3.14.2",
|
"gsap": "^3.14.2",
|
||||||
"lenis": "^1.3.17",
|
"lenis": "^1.3.17",
|
||||||
|
"libsodium-wrappers": "^0.8.2",
|
||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
"sass": "^1.97.3",
|
"sass": "^1.97.3",
|
||||||
"sass-embedded": "^1.97.3",
|
"sass-embedded": "^1.97.3",
|
||||||
@@ -2343,19 +2344,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@takerofnotes/plugin-filesystem": {
|
"node_modules/@takerofnotes/plugin-filesystem": {
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@takerofnotes/plugin-filesystem/-/plugin-filesystem-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@takerofnotes/plugin-filesystem/-/plugin-filesystem-0.2.0.tgz",
|
||||||
"integrity": "sha512-u3L6HLxN/+t7PTtzzRA2uzgaVh/O1vasngU/tQeC6JsTnlIrypWFVbAlTS5zT9Km4y+8w6C16eEq7tcN8+5lPg==",
|
"integrity": "sha512-BP7HBN0SKAqBiv5pDtXpyVmkW9UrOnPXpKeThTwQTIShHyN5aPaD8ZEyv8+vfTSs3qnXWGplNdEPVjbQmc27+Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@takerofnotes/plugin-sdk": "^0.1.0",
|
"@takerofnotes/plugin-sdk": "^0.3.1"
|
||||||
"gray-matter": "^4.0.3"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@takerofnotes/plugin-sdk": {
|
"node_modules/@takerofnotes/plugin-sdk": {
|
||||||
"version": "0.1.0",
|
"version": "0.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@takerofnotes/plugin-sdk/-/plugin-sdk-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@takerofnotes/plugin-sdk/-/plugin-sdk-0.3.1.tgz",
|
||||||
"integrity": "sha512-ofhwwiQ59kNMEg2vvYoNq5JdXHB9/6TkDsbyroM5nsP/VPUvJSQ5g0UWCYBhteSzJ36iFUB+LtUwVt6gOXDClw==",
|
"integrity": "sha512-9GfPKyu1n52N00zYlLK32wdmGdc2uSd0jTj6UEixauW0TXn/7hD6SLpLRGSXcfZOJGGoi3iQk4MfjNsthe2ucw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
@@ -5105,19 +5105,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esprima": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"bin": {
|
|
||||||
"esparse": "bin/esparse.js",
|
|
||||||
"esvalidate": "bin/esvalidate.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/estree-walker": {
|
"node_modules/estree-walker": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
@@ -5137,18 +5124,6 @@
|
|||||||
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
|
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/extend-shallow": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"is-extendable": "^0.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/extract-zip": {
|
"node_modules/extract-zip": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
||||||
@@ -5625,49 +5600,6 @@
|
|||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/gray-matter": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
|
|
||||||
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"js-yaml": "^3.13.1",
|
|
||||||
"kind-of": "^6.0.2",
|
|
||||||
"section-matter": "^1.0.0",
|
|
||||||
"strip-bom-string": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/gray-matter/node_modules/argparse": {
|
|
||||||
"version": "1.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
|
||||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"sprintf-js": "~1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/gray-matter/node_modules/js-yaml": {
|
|
||||||
"version": "3.14.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
|
||||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"argparse": "^1.0.7",
|
|
||||||
"esprima": "^4.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"js-yaml": "bin/js-yaml.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/gray-matter/node_modules/sprintf-js": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
|
||||||
"license": "BSD-3-Clause"
|
|
||||||
},
|
|
||||||
"node_modules/gsap": {
|
"node_modules/gsap": {
|
||||||
"version": "3.14.2",
|
"version": "3.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.14.2.tgz",
|
||||||
@@ -5930,15 +5862,6 @@
|
|||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-extendable": {
|
|
||||||
"version": "0.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
|
||||||
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
@@ -6155,15 +6078,6 @@
|
|||||||
"json-buffer": "3.0.1"
|
"json-buffer": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/kind-of": {
|
|
||||||
"version": "6.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
|
||||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lazy-val": {
|
"node_modules/lazy-val": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
|
||||||
@@ -6196,6 +6110,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/libsodium": {
|
||||||
|
"version": "0.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.8.2.tgz",
|
||||||
|
"integrity": "sha512-TsnGYMoZtpweT+kR+lOv5TVsnJ/9U0FZOsLFzFOMWmxqOAYXjX3fsrPAW+i1LthgDKXJnI9A8dWEanT1tnJKIw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/libsodium-wrappers": {
|
||||||
|
"version": "0.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.8.2.tgz",
|
||||||
|
"integrity": "sha512-VFLmfxkxo+U9q60tjcnSomQBRx2UzlRjKWJqvB4K1pUqsMQg4cu3QXA2nrcsj9A1qRsnJBbi2Ozx1hsiDoCkhw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"libsodium": "^0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/linkify-it": {
|
"node_modules/linkify-it": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||||
@@ -8121,19 +8050,6 @@
|
|||||||
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
|
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/section-matter": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"extend-shallow": "^2.0.1",
|
|
||||||
"kind-of": "^6.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "6.3.1",
|
"version": "6.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
@@ -8416,15 +8332,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/strip-bom-string": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/sumchecker": {
|
"node_modules/sumchecker": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "app.takerofnotes.com",
|
"name": "takerofnotes-app",
|
||||||
"version": "1.0.0",
|
"version": "0.1.0",
|
||||||
"description": "An Electron application with Vue",
|
"description": "An Electron application with Vue",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "example.com",
|
"author": "example.com",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"@electron-toolkit/preload": "^3.0.2",
|
"@electron-toolkit/preload": "^3.0.2",
|
||||||
"@electron-toolkit/utils": "^4.0.0",
|
"@electron-toolkit/utils": "^4.0.0",
|
||||||
"@fuzzco/font-loader": "^1.0.2",
|
"@fuzzco/font-loader": "^1.0.2",
|
||||||
"@takerofnotes/plugin-filesystem": "^0.1.1",
|
"@takerofnotes/plugin-filesystem": "^0.2.0",
|
||||||
"@takerofnotes/plugin-supabase": "^0.1.0",
|
"@takerofnotes/plugin-supabase": "^0.1.0",
|
||||||
"@tiptap/extension-document": "^3.19.0",
|
"@tiptap/extension-document": "^3.19.0",
|
||||||
"@tiptap/extension-image": "^3.19.0",
|
"@tiptap/extension-image": "^3.19.0",
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
"flexsearch": "^0.8.212",
|
"flexsearch": "^0.8.212",
|
||||||
"gsap": "^3.14.2",
|
"gsap": "^3.14.2",
|
||||||
"lenis": "^1.3.17",
|
"lenis": "^1.3.17",
|
||||||
|
"libsodium-wrappers": "^0.8.2",
|
||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
"sass": "^1.97.3",
|
"sass": "^1.97.3",
|
||||||
"sass-embedded": "^1.97.3",
|
"sass-embedded": "^1.97.3",
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import { Index } from 'flexsearch'
|
import { Index } from 'flexsearch'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
|
import sodium from 'libsodium-wrappers'
|
||||||
|
|
||||||
export default class NotesAPI {
|
export default class NotesAPI {
|
||||||
constructor(adapter) {
|
constructor(adapter, encryptionKey = null) {
|
||||||
if (!adapter) {
|
if (!adapter) {
|
||||||
throw new Error('NotesAPI requires a storage adapter')
|
throw new Error('NotesAPI requires a storage adapter')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.adapter = adapter
|
this.adapter = adapter
|
||||||
this.notesCache = new Map()
|
this.notesCache = new Map()
|
||||||
|
this.encryptionKey = encryptionKey || process.env.NOTES_ENCRYPTION_KEY
|
||||||
|
this._sodiumReady = false
|
||||||
|
|
||||||
this.index = new Index({
|
this.index = new Index({
|
||||||
tokenize: 'tolerant',
|
tokenize: 'tolerant',
|
||||||
@@ -16,14 +19,73 @@ export default class NotesAPI {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _initSodium() {
|
||||||
|
if (!this._sodiumReady) {
|
||||||
|
await sodium.ready
|
||||||
|
this._sodiumReady = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_encrypt(note) {
|
||||||
|
if (!this.encryptionKey) {
|
||||||
|
throw new Error('Encryption key not set')
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = Buffer.from(this.encryptionKey, 'hex')
|
||||||
|
if (key.length !== 32) {
|
||||||
|
throw new Error('Encryption key must be 64 hex characters (32 bytes)')
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES)
|
||||||
|
const message = JSON.stringify(note)
|
||||||
|
|
||||||
|
const ciphertext = sodium.crypto_secretbox_easy(
|
||||||
|
Buffer.from(message),
|
||||||
|
nonce,
|
||||||
|
key
|
||||||
|
)
|
||||||
|
|
||||||
|
const combined = Buffer.concat([nonce, ciphertext])
|
||||||
|
return combined.toString('base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
_decrypt(encryptedData) {
|
||||||
|
if (!this.encryptionKey) {
|
||||||
|
throw new Error('Encryption key not set')
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = Buffer.from(this.encryptionKey, 'hex')
|
||||||
|
if (key.length !== 32) {
|
||||||
|
throw new Error('Encryption key must be 64 hex characters (32 bytes)')
|
||||||
|
}
|
||||||
|
|
||||||
|
const combined = Buffer.from(encryptedData, 'base64')
|
||||||
|
const nonce = combined.slice(0, sodium.crypto_secretbox_NONCEBYTES)
|
||||||
|
const ciphertext = combined.slice(sodium.crypto_secretbox_NONCEBYTES)
|
||||||
|
|
||||||
|
const decrypted = sodium.crypto_secretbox_open_easy(
|
||||||
|
ciphertext,
|
||||||
|
nonce,
|
||||||
|
key
|
||||||
|
)
|
||||||
|
|
||||||
|
return JSON.parse(decrypted.toString())
|
||||||
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
await this._initSodium()
|
||||||
await this.adapter.init()
|
await this.adapter.init()
|
||||||
|
|
||||||
const notes = await this.adapter.getAll()
|
const encryptedNotes = await this.adapter.getAll()
|
||||||
|
|
||||||
for (const note of notes) {
|
for (const encryptedNote of encryptedNotes) {
|
||||||
|
try {
|
||||||
|
const note = this._decrypt(encryptedNote.data || encryptedNote)
|
||||||
this.notesCache.set(note.id, note)
|
this.notesCache.set(note.id, note)
|
||||||
this.index.add(note.id, note.title + '\n' + note.content)
|
this.index.add(note.id, note.title + '\n' + note.content)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to decrypt note:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,10 +127,15 @@ export default class NotesAPI {
|
|||||||
content,
|
content,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const encryptedNote = {
|
||||||
|
id: note.id,
|
||||||
|
data: this._encrypt(note),
|
||||||
|
}
|
||||||
|
|
||||||
this.notesCache.set(id, note)
|
this.notesCache.set(id, note)
|
||||||
this.index.add(id, note.title + '\n' + content)
|
this.index.add(id, note.title + '\n' + content)
|
||||||
|
|
||||||
await this.adapter.create(note)
|
await this.adapter.create(encryptedNote)
|
||||||
|
|
||||||
return note
|
return note
|
||||||
}
|
}
|
||||||
@@ -87,9 +154,14 @@ export default class NotesAPI {
|
|||||||
note.content = content
|
note.content = content
|
||||||
note.updatedAt = new Date().toISOString()
|
note.updatedAt = new Date().toISOString()
|
||||||
|
|
||||||
|
const encryptedNote = {
|
||||||
|
id: note.id,
|
||||||
|
data: this._encrypt(note),
|
||||||
|
}
|
||||||
|
|
||||||
this.index.update(id, note.title + '\n' + content)
|
this.index.update(id, note.title + '\n' + content)
|
||||||
|
|
||||||
await this.adapter.update(note)
|
await this.adapter.update(encryptedNote)
|
||||||
|
|
||||||
return note
|
return note
|
||||||
}
|
}
|
||||||
@@ -115,9 +187,14 @@ export default class NotesAPI {
|
|||||||
|
|
||||||
note.updatedAt = new Date().toISOString()
|
note.updatedAt = new Date().toISOString()
|
||||||
|
|
||||||
|
const encryptedNote = {
|
||||||
|
id: note.id,
|
||||||
|
data: this._encrypt(note),
|
||||||
|
}
|
||||||
|
|
||||||
this.index.update(id, note.title + '\n' + note.content)
|
this.index.update(id, note.title + '\n' + note.content)
|
||||||
|
|
||||||
await this.adapter.update(note)
|
await this.adapter.update(encryptedNote)
|
||||||
|
|
||||||
return note
|
return note
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,16 +82,19 @@ app.whenReady().then(async () => {
|
|||||||
const config = await new PluginConfig(filesystemPlugin).load()
|
const config = await new PluginConfig(filesystemPlugin).load()
|
||||||
|
|
||||||
// Create instance of active adapter
|
// Create instance of active adapter
|
||||||
// const plugin = registry.get(config.activeAdapter)
|
const plugin = registry.get(config.activeAdapter)
|
||||||
const plugin = registry.get(supabasePlugin.id)
|
// const plugin = registry.get(supabasePlugin.id)
|
||||||
// const adapter = plugin.createAdapter(config.adapterConfig)
|
const adapter = plugin.createAdapter(config.adapterConfig)
|
||||||
const adapter = plugin.createAdapter({
|
// const adapter = plugin.createAdapter({
|
||||||
supabaseKey: process.env.SUPABASE_KEY,
|
// supabaseKey: process.env.SUPABASE_KEY,
|
||||||
supabaseUrl: process.env.SUPABASE_URL,
|
// supabaseUrl: process.env.SUPABASE_URL,
|
||||||
})
|
// })
|
||||||
|
|
||||||
// Init Notes API
|
// Init Notes API
|
||||||
const notesAPI = new NotesAPI(adapter)
|
const notesAPI = new NotesAPI(
|
||||||
|
adapter,
|
||||||
|
'729a0d21d783654c68f1a0123e2a0e986350de536b5324f1f35876ea12ffeaf5',
|
||||||
|
)
|
||||||
await notesAPI.init()
|
await notesAPI.init()
|
||||||
|
|
||||||
// Handle Notes API
|
// Handle Notes API
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ const updateTitle = _debounce(async (editor) => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const note = await loadNote(id)
|
const note = await loadNote(id)
|
||||||
|
console.log(note)
|
||||||
lastTitle = note.title
|
lastTitle = note.title
|
||||||
|
|
||||||
editor.value = new Editor({
|
editor.value = new Editor({
|
||||||
@@ -90,11 +91,9 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
Markdown.configure({}),
|
|
||||||
Image,
|
Image,
|
||||||
],
|
],
|
||||||
content: note.content,
|
content: note.content,
|
||||||
contentType: 'markdown',
|
|
||||||
onUpdate: updateNote,
|
onUpdate: updateNote,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user