diff --git a/apps/desktop/package-lock.json b/apps/desktop/package-lock.json index cb94b613a..eced7e797 100644 --- a/apps/desktop/package-lock.json +++ b/apps/desktop/package-lock.json @@ -8,19 +8,20 @@ "name": "@notesnook/desktop", "version": "2.5.2", "dependencies": { - "@trpc/client": "^10.18.0", - "@trpc/server": "^10.18.0", + "@trpc/client": "^10.29.1", + "@trpc/server": "^10.29.1", "diary": "^0.3.1", - "electron-trpc": "^0.4.2", + "electron-trpc": "^0.5.0", "electron-updater": "^5.3.0", "icojs": "^0.17.1", + "typed-emitter": "^2.1.0", "yargs": "^17.6.2", "zod": "^3.21.4" }, "devDependencies": { - "@electron/rebuild": "^3.2.10", + "@electron/rebuild": "^3.2.13", "@types/node": "^18.15.0", - "electron": "^25.1.0", + "electron": "^24.4.0", "electron-builder": "^23.6.0", "electron-builder-notarize": "^1.5.1", "electron-reloader": "^1.2.3", @@ -173,6 +174,7 @@ }, "node_modules/@electron/get": { "version": "2.0.2", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.1", @@ -191,9 +193,10 @@ } }, "node_modules/@electron/rebuild": { - "version": "3.2.10", + "version": "3.2.13", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.2.13.tgz", + "integrity": "sha512-DH9Ol4JCnHDYVOD0fKWq+Qqbn/0WU1O6QR0mIpMXEVU4YFM4PlaqNC9K36mGShNBxxGFotZCMDrB1wl/iHM12g==", "dev": true, - "license": "MIT", "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "chalk": "^4.0.0", @@ -201,7 +204,6 @@ "detect-libc": "^2.0.1", "fs-extra": "^10.0.0", "got": "^11.7.0", - "lzma-native": "^8.0.5", "node-abi": "^3.0.0", "node-api-version": "^0.1.4", "node-gyp": "^9.0.0", @@ -211,7 +213,7 @@ "yargs": "^17.0.1" }, "bin": { - "electron-rebuild": "lib/src/cli.js" + "electron-rebuild": "lib/cli.js" }, "engines": { "node": ">=12.13.0" @@ -799,6 +801,7 @@ }, "node_modules/@sindresorhus/is": { "version": "4.6.0", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -809,6 +812,7 @@ }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", + "dev": true, "license": "MIT", "dependencies": { "defer-to-connect": "^2.0.0" @@ -830,18 +834,27 @@ } }, "node_modules/@trpc/client": { - "version": "10.18.0", - "license": "MIT", + "version": "10.29.1", + "resolved": "https://registry.npmjs.org/@trpc/client/-/client-10.29.1.tgz", + "integrity": "sha512-+9Tifg6dtKsYLsqOW0wizqc3iILAkXxn16pyYAeMDPlulPEqNvnI85GDJ0zJOJLIkQnQefkRbtCmtDxLNtV9Eg==", + "funding": [ + "https://trpc.io/sponsor" + ], "peerDependencies": { - "@trpc/server": "10.18.0" + "@trpc/server": "10.29.1" } }, "node_modules/@trpc/server": { - "version": "10.18.0", - "license": "MIT" + "version": "10.29.1", + "resolved": "https://registry.npmjs.org/@trpc/server/-/server-10.29.1.tgz", + "integrity": "sha512-kNXgMh5ya+awuz2tB4eIyVrRs7nVtqGXwSGabzH3l5ZLWz7rbKJquOJ7h6bjvIfWUpaFG62HJNWxxGUtXCRgRw==", + "funding": [ + "https://trpc.io/sponsor" + ] }, "node_modules/@types/cacheable-request": { "version": "6.0.3", + "dev": true, "license": "MIT", "dependencies": { "@types/http-cache-semantics": "*", @@ -878,10 +891,12 @@ }, "node_modules/@types/http-cache-semantics": { "version": "4.0.1", + "dev": true, "license": "MIT" }, "node_modules/@types/keyv": { "version": "3.1.4", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -900,6 +915,7 @@ }, "node_modules/@types/node": { "version": "18.15.11", + "dev": true, "license": "MIT" }, "node_modules/@types/normalize-package-data": { @@ -909,6 +925,7 @@ }, "node_modules/@types/responselike": { "version": "1.0.0", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -933,6 +950,7 @@ }, "node_modules/@types/yauzl": { "version": "2.10.0", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1273,6 +1291,7 @@ }, "node_modules/boolean": { "version": "3.2.0", + "dev": true, "license": "MIT", "optional": true }, @@ -1335,6 +1354,7 @@ }, "node_modules/buffer-crc32": { "version": "0.2.13", + "dev": true, "license": "MIT", "engines": { "node": "*" @@ -1512,6 +1532,7 @@ }, "node_modules/cacheable-lookup": { "version": "5.0.4", + "dev": true, "license": "MIT", "engines": { "node": ">=10.6.0" @@ -1519,6 +1540,7 @@ }, "node_modules/cacheable-request": { "version": "7.0.2", + "dev": true, "license": "MIT", "dependencies": { "clone-response": "^1.0.2", @@ -1638,6 +1660,7 @@ }, "node_modules/clone-response": { "version": "1.0.3", + "dev": true, "license": "MIT", "dependencies": { "mimic-response": "^1.0.0" @@ -1777,6 +1800,7 @@ }, "node_modules/decompress-response": { "version": "6.0.0", + "dev": true, "license": "MIT", "dependencies": { "mimic-response": "^3.1.0" @@ -1790,6 +1814,7 @@ }, "node_modules/decompress-response/node_modules/mimic-response": { "version": "3.1.0", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -1811,6 +1836,7 @@ }, "node_modules/defer-to-connect": { "version": "2.0.1", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -1818,6 +1844,7 @@ }, "node_modules/define-properties": { "version": "1.2.0", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1862,6 +1889,7 @@ }, "node_modules/detect-node": { "version": "2.1.0", + "dev": true, "license": "MIT", "optional": true }, @@ -1981,9 +2009,10 @@ } }, "node_modules/electron": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-25.1.0.tgz", - "integrity": "sha512-VKk4G/0euO7ysMKQKHXmI4d3/qR4uHsAtVFXK2WfQUVxBmc160OAm2R6PN9/EXmgXEioKQBtbc2/lvWyYpDbuA==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-24.5.0.tgz", + "integrity": "sha512-9Xo2EFZHWeuw1otm9mcJYKCNC64fPRpgp+ZJWMJ9RtvsnSgcuitkM4esZv4gIsqhWk5yiKApYHqinIUyu82O0Q==", + "dev": true, "hasInstallScript": true, "dependencies": { "@electron/get": "^2.0.0", @@ -2253,8 +2282,12 @@ } }, "node_modules/electron-trpc": { - "version": "0.4.2", - "license": "MIT", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/electron-trpc/-/electron-trpc-0.5.0.tgz", + "integrity": "sha512-xvnvOpuI0IiecMGS9VOS5/3kLj6IUpjngKhv5VqXU9vP2LzE140G8gqaziX0Tl7MaiYmJZbad/imis9i49n/4A==", + "dependencies": { + "debug": "^4.3.4" + }, "peerDependencies": { "@trpc/client": ">10.0.0", "@trpc/server": ">10.0.0", @@ -2333,6 +2366,7 @@ }, "node_modules/end-of-stream": { "version": "1.4.4", + "dev": true, "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -2340,6 +2374,7 @@ }, "node_modules/env-paths": { "version": "2.2.1", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2360,6 +2395,7 @@ }, "node_modules/es6-error": { "version": "4.1.1", + "dev": true, "license": "MIT", "optional": true }, @@ -2409,6 +2445,7 @@ }, "node_modules/escape-string-regexp": { "version": "4.0.0", + "dev": true, "license": "MIT", "optional": true, "engines": { @@ -2432,6 +2469,7 @@ }, "node_modules/extract-zip": { "version": "2.0.1", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "debug": "^4.1.1", @@ -2460,6 +2498,7 @@ }, "node_modules/fd-slicer": { "version": "1.1.0", + "dev": true, "license": "MIT", "dependencies": { "pend": "~1.2.0" @@ -2543,6 +2582,7 @@ }, "node_modules/fs-extra": { "version": "8.1.0", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -2571,7 +2611,7 @@ }, "node_modules/function-bind": { "version": "1.1.1", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/gauge": { @@ -2601,6 +2641,7 @@ }, "node_modules/get-intrinsic": { "version": "1.2.0", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2614,6 +2655,7 @@ }, "node_modules/get-stream": { "version": "5.2.0", + "dev": true, "license": "MIT", "dependencies": { "pump": "^3.0.0" @@ -2657,6 +2699,7 @@ }, "node_modules/global-agent": { "version": "3.0.0", + "dev": true, "license": "BSD-3-Clause", "optional": true, "dependencies": { @@ -2673,6 +2716,7 @@ }, "node_modules/global-agent/node_modules/semver": { "version": "7.3.8", + "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -2687,6 +2731,7 @@ }, "node_modules/globalthis": { "version": "1.0.3", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2701,6 +2746,7 @@ }, "node_modules/got": { "version": "11.8.6", + "dev": true, "license": "MIT", "dependencies": { "@sindresorhus/is": "^4.0.0", @@ -2733,7 +2779,7 @@ }, "node_modules/has": { "version": "1.0.3", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.1" @@ -2752,6 +2798,7 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.0", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2763,6 +2810,7 @@ }, "node_modules/has-symbols": { "version": "1.0.3", + "dev": true, "license": "MIT", "optional": true, "engines": { @@ -2790,6 +2838,7 @@ }, "node_modules/http-cache-semantics": { "version": "4.1.0", + "dev": true, "license": "BSD-2-Clause" }, "node_modules/http-proxy-agent": { @@ -2807,6 +2856,7 @@ }, "node_modules/http2-wrapper": { "version": "1.0.3", + "dev": true, "license": "MIT", "dependencies": { "quick-lru": "^5.1.1", @@ -3094,6 +3144,7 @@ }, "node_modules/json-buffer": { "version": "3.0.1", + "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -3108,6 +3159,7 @@ }, "node_modules/json-stringify-safe": { "version": "5.0.1", + "dev": true, "license": "ISC", "optional": true }, @@ -3124,6 +3176,7 @@ }, "node_modules/jsonfile": { "version": "4.0.0", + "dev": true, "license": "MIT", "optionalDependencies": { "graceful-fs": "^4.1.6" @@ -3131,6 +3184,7 @@ }, "node_modules/keyv": { "version": "4.5.2", + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -3197,6 +3251,7 @@ }, "node_modules/lowercase-keys": { "version": "2.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3212,23 +3267,6 @@ "node": ">=10" } }, - "node_modules/lzma-native": { - "version": "8.0.6", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-addon-api": "^3.1.0", - "node-gyp-build": "^4.2.1", - "readable-stream": "^3.6.0" - }, - "bin": { - "lzmajs": "bin/lzmajs" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/make-fetch-happen": { "version": "10.2.1", "dev": true, @@ -3265,6 +3303,7 @@ }, "node_modules/matcher": { "version": "3.0.0", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -3326,6 +3365,7 @@ }, "node_modules/mimic-response": { "version": "1.0.1", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -3462,9 +3502,10 @@ "license": "MIT" }, "node_modules/node-abi": { - "version": "3.33.0", + "version": "3.44.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.44.0.tgz", + "integrity": "sha512-MYjZTiAETGG28/7fBH1RjuY7vzDwYC5q5U4whCgM4jNEQcC0gAvN339LxXukmL2T2tGpzYTfp+LZ5RN7E5DwEg==", "dev": true, - "license": "MIT", "dependencies": { "semver": "^7.3.5" }, @@ -3486,11 +3527,6 @@ "node": ">=10" } }, - "node_modules/node-addon-api": { - "version": "3.2.1", - "dev": true, - "license": "MIT" - }, "node_modules/node-api-version": { "version": "0.1.4", "dev": true, @@ -3536,16 +3572,6 @@ "node": "^12.13 || ^14.13 || >=16" } }, - "node_modules/node-gyp-build": { - "version": "4.6.0", - "dev": true, - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/node-gyp/node_modules/semver": { "version": "7.3.8", "dev": true, @@ -3608,6 +3634,7 @@ }, "node_modules/normalize-url": { "version": "6.1.0", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -3632,6 +3659,7 @@ }, "node_modules/object-keys": { "version": "1.1.1", + "dev": true, "license": "MIT", "optional": true, "engines": { @@ -3640,6 +3668,7 @@ }, "node_modules/once": { "version": "1.4.0", + "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -3706,6 +3735,7 @@ }, "node_modules/p-cancelable": { "version": "2.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3980,6 +4010,7 @@ }, "node_modules/pend": { "version": "1.2.0", + "dev": true, "license": "MIT" }, "node_modules/picomatch": { @@ -4014,6 +4045,7 @@ }, "node_modules/progress": { "version": "2.0.3", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -4038,6 +4070,7 @@ }, "node_modules/pump": { "version": "3.0.0", + "dev": true, "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -4054,6 +4087,7 @@ }, "node_modules/quick-lru": { "version": "5.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -4229,10 +4263,12 @@ }, "node_modules/resolve-alpn": { "version": "1.2.1", + "dev": true, "license": "MIT" }, "node_modules/responselike": { "version": "2.0.1", + "dev": true, "license": "MIT", "dependencies": { "lowercase-keys": "^2.0.0" @@ -4277,6 +4313,7 @@ }, "node_modules/roarr": { "version": "2.15.4", + "dev": true, "license": "BSD-3-Clause", "optional": true, "dependencies": { @@ -4336,6 +4373,7 @@ }, "node_modules/semver": { "version": "6.3.0", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4343,11 +4381,13 @@ }, "node_modules/semver-compare": { "version": "1.0.0", + "dev": true, "license": "MIT", "optional": true }, "node_modules/serialize-error": { "version": "7.0.1", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -4362,6 +4402,7 @@ }, "node_modules/serialize-error/node_modules/type-fest": { "version": "0.13.1", + "dev": true, "license": "(MIT OR CC0-1.0)", "optional": true, "engines": { @@ -4509,6 +4550,7 @@ }, "node_modules/sprintf-js": { "version": "1.1.2", + "dev": true, "license": "BSD-3-Clause", "optional": true }, @@ -4586,6 +4628,7 @@ }, "node_modules/sumchecker": { "version": "3.0.1", + "dev": true, "license": "Apache-2.0", "dependencies": { "debug": "^4.1.0" @@ -4783,6 +4826,7 @@ }, "node_modules/universalify": { "version": "0.1.2", + "dev": true, "license": "MIT", "engines": { "node": ">= 4.0.0" @@ -4861,6 +4905,7 @@ }, "node_modules/wrappy": { "version": "1.0.2", + "dev": true, "license": "ISC" }, "node_modules/xmlbuilder": { @@ -4915,6 +4960,7 @@ }, "node_modules/yauzl": { "version": "2.10.0", + "dev": true, "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 3919b6acf..d438c3aa0 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -11,19 +11,20 @@ "repository": "https://github.com/streetwriters/notesnook", "dependencies": { "@notesnook/crypto": "*", - "@trpc/client": "^10.18.0", - "@trpc/server": "^10.18.0", + "@trpc/client": "^10.29.1", + "@trpc/server": "^10.29.1", "diary": "^0.3.1", - "electron-trpc": "^0.4.2", + "electron-trpc": "^0.5.0", "electron-updater": "^5.3.0", "icojs": "^0.17.1", "yargs": "^17.6.2", - "zod": "^3.21.4" + "zod": "^3.21.4", + "typed-emitter": "^2.1.0" }, "devDependencies": { - "@electron/rebuild": "^3.2.10", + "@electron/rebuild": "^3.2.13", "@types/node": "^18.15.0", - "electron": "^25.1.0", + "electron": "^24.4.0", "electron-builder": "^23.6.0", "electron-builder-notarize": "^1.5.1", "electron-reloader": "^1.2.3", @@ -32,12 +33,12 @@ "undici": "^5.22.1" }, "scripts": { - "start": "npm run build:electron && electron build/electron.js", + "start": "turbowatch scripts/dev.ts", + "staging": "zx scripts/staging.mjs", "build": "tsc", "build:electron": "esbuild electron=./src/main.ts ./src/preload.ts --external:electron --external:fsevents --minify --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=false --define:RELEASE=true", "build:mas": "esbuild ./electron.js ./preload.js --minify --external:electron --external:fsevents --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=true --define:RELEASE=true", - "pack": "rm -rf ./build && cp -r ../build ./ && npm run build && yarn electron-builder --linux AppImage:x64 AppImage:arm64", - "postinstall": "patch-package" + "postinstall": "electron-builder install-app-deps && patch-package" }, "author": { "name": "Streetwriters (Private) Limited", @@ -53,8 +54,8 @@ "!*.chunk.js.map", "!*.chunk.js.LICENSE.txt", "build/", - "!build/screenshots", - "!node_modules" + "!build/screenshots${/*}", + "!node_modules${/*}" ], "afterSign": "electron-builder-notarize", "afterPack": "./scripts/removeLocales.js", @@ -185,7 +186,7 @@ ], "directories": { "buildResources": "assets", - "output": "./dist/" + "output": "./output/" }, "publish": [ { diff --git a/apps/desktop/scripts/dev.ts b/apps/desktop/scripts/dev.ts new file mode 100644 index 000000000..d80d0a55a --- /dev/null +++ b/apps/desktop/scripts/dev.ts @@ -0,0 +1,117 @@ +/* +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 path from "path"; +import fs from "fs/promises"; +import { watch } from "turbowatch"; +import { $, within } from "zx"; + +const sodiumNativePrebuildPath = path.join( + `node_modules`, + `@notesnook`, + `crypto`, + `node_modules`, + `@notesnook`, + `sodium`, + `node_modules`, + `sodium-native`, + `prebuilds` +); +let isServerRunning = false; + +async function main() { + const { shutdown } = await watch({ + project: path.join(__dirname, ".."), + cwd: path.join(__dirname, ".."), + triggers: [ + { + expression: [ + "allof", + ["not", ["dirname", "node_modules"]], + ["match", "*.ts", "basename"] + ], + name: "dev", + // retry: { retries: 0 }, + onTeardown: async () => { + await fs.rm("./build/", { force: true, recursive: true }); + }, + onChange: async ({ spawn: $, first, log }) => { + if (first) { + await fs.rm("./build/", { force: true, recursive: true }); + } + + await $`npm run build:electron`; + await $`tsc`; + + if (first) { + await fs.cp(sodiumNativePrebuildPath, "build/prebuilds", { + recursive: true, + force: true + }); + } + + if (!isServerRunning) { + await spawnAndWaitUntil( + path.join(__dirname, "..", "..", "web"), + "npm run start:desktop", + (data) => data.includes("Compiled successfully!") + ); + isServerRunning = true; + } + + log("Starting desktop app!"); + + await $`npx electron ${path.join("build", "electron.js")}`; + } + } + ] + }); + + // SIGINT is the signal sent when we press Ctrl+C + process.once("SIGINT", () => { + void shutdown(); + }); +} + +main(); + +function spawnAndWaitUntil( + cwd: string, + cmd: string, + predicate: (data: string) => boolean +) { + return new Promise((resolve) => { + within(async () => { + $.env = process.env; + $.quote = (c) => c; + + try { + const s = $`cd ${cwd} && ${cmd}`; + s.stdout.on("data", (data) => { + if (predicate(data)) resolve(undefined); // + }); + await s; + } catch (e) { + //ignore + } finally { + isServerRunning = false; + } + }); + }); +} diff --git a/apps/desktop/scripts/staging.mjs b/apps/desktop/scripts/staging.mjs new file mode 100644 index 000000000..e10129032 --- /dev/null +++ b/apps/desktop/scripts/staging.mjs @@ -0,0 +1,65 @@ +/* +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 path from "path"; +import fs from "fs/promises"; +import { $, argv, os } from "zx"; + +$.env = process.env; + +const sodiumNativePrebuildPath = path.join( + `node_modules`, + `@notesnook`, + `crypto`, + `node_modules`, + `@notesnook`, + `sodium`, + `node_modules`, + `sodium-native`, + `prebuilds`, + `${os.platform()}-x64` +); + +await fs.rm("./build/", { force: true, recursive: true }); + +if (argv.rebuild) await $`cd ../web/ && npm run build:desktop`; + +await fs.cp(path.join("..", "web", "build"), "build", { + recursive: true, + force: true +}); + +await $`npm run build:electron`; +await $`tsc`; +await $`npm rebuild`; + +await fs.cp( + sodiumNativePrebuildPath, + path.join("build", "prebuilds", `${process.platform}-x64`), + { + recursive: true, + force: true + } +); + +if (argv.run) { + await $`yarn electron-builder -c.extraMetadata.main=./build/electron.js --linux AppImage:x64`; + + await $`./output/notesnook_linux_x86_64.AppImage`; +} diff --git a/apps/desktop/src/api/bridge.ts b/apps/desktop/src/api/bridge.ts new file mode 100644 index 000000000..cdb6e12c0 --- /dev/null +++ b/apps/desktop/src/api/bridge.ts @@ -0,0 +1,58 @@ +/* +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 { initTRPC } from "@trpc/server"; +import { observable } from "@trpc/server/observable"; +import { EventEmitter } from "events"; +import TypedEventEmitter from "typed-emitter"; + +export type AppEvents = { + onCreateItem(name: "note" | "notebook" | "reminder"): void; +}; + +const emitter = new EventEmitter(); +const typedEmitter = emitter as TypedEventEmitter; +const t = initTRPC.create(); + +export const bridgeRouter = t.router({ + onCreateItem: createSubscription("onCreateItem") +}); + +export const bridge: AppEvents = new Proxy({} as AppEvents, { + get(_t, name) { + if (typeof name === "symbol") return; + return (...args: unknown[]) => { + emitter.emit(name, ...args); + }; + } +}); + +function createSubscription(eventName: TName) { + return t.procedure.subscription(() => { + return observable[0]>((emit) => { + const listener: AppEvents[TName] = (...args: any[]) => { + emit.next(args[0]); + }; + typedEmitter.addListener(eventName, listener); + return () => { + typedEmitter.removeListener(eventName, listener); + }; + }); + }); +} diff --git a/apps/desktop/src/api/index.ts b/apps/desktop/src/api/index.ts index 5350d7ade..9c20fdb86 100644 --- a/apps/desktop/src/api/index.ts +++ b/apps/desktop/src/api/index.ts @@ -22,6 +22,7 @@ import { compressionRouter } from "./compression"; import { osIntegrationRouter } from "./os-integration"; import { spellCheckerRouter } from "./spell-checker"; import { updaterRouter } from "./updater"; +import { bridgeRouter } from "./bridge"; const t = initTRPC.create(); @@ -29,7 +30,8 @@ export const router = t.router({ compress: compressionRouter, integration: osIntegrationRouter, spellChecker: spellCheckerRouter, - updater: updaterRouter + updater: updaterRouter, + bridge: bridgeRouter }); export const api = router.createCaller({}); diff --git a/apps/desktop/src/api/os-integration.ts b/apps/desktop/src/api/os-integration.ts index ab21d7318..73d5b5e03 100644 --- a/apps/desktop/src/api/os-integration.ts +++ b/apps/desktop/src/api/os-integration.ts @@ -19,16 +19,16 @@ along with this program. If not, see . import { initTRPC } from "@trpc/server"; import { z } from "zod"; -import { dialog, Notification, shell } from "electron"; +import { dialog, nativeTheme, Notification, shell } from "electron"; import { AutoLaunch } from "../utils/autolaunch"; import { config, DesktopIntegration } from "../utils/config"; import { bringToFront } from "../utils/bring-to-front"; -import { setTheme, Theme } from "../utils/theme"; +import { getTheme, setTheme, Theme } from "../utils/theme"; import { mkdirSync, writeFileSync } from "fs"; import { dirname, join } from "path"; import { platform } from "os"; import { resolvePath } from "../utils/resolve-path"; -import { client } from "../rpc/electron"; +import { observable } from "@trpc/server/observable"; const t = initTRPC.create(); @@ -132,7 +132,10 @@ export const osIntegrationRouter = t.router({ shell.beep(); } - client.onNotificationClicked(input.tag); + return new Promise((resolve) => { + notification.once("close", () => resolve(undefined)); + notification.once("click", () => resolve(input.tag)); + }); }), openPath: t.procedure .input(z.object({ type: z.literal("path"), link: z.string() })) @@ -141,5 +144,17 @@ export const osIntegrationRouter = t.router({ if (type === "path") return shell.openPath(resolvePath(link)); }), bringToFront: t.procedure.query(() => bringToFront()), - changeTheme: t.procedure.input(Theme).mutation(({ input }) => setTheme(input)) + changeTheme: t.procedure + .input(Theme) + .mutation(({ input }) => setTheme(input)), + + onThemeChanged: t.procedure.subscription(() => + observable<"dark" | "light">((emit) => { + nativeTheme.on("updated", () => { + if (getTheme() === "system") { + emit.next(nativeTheme.shouldUseDarkColors ? "dark" : "light"); + } + }); + }) + ) }); diff --git a/apps/desktop/src/api/updater.ts b/apps/desktop/src/api/updater.ts index 381f8d5ef..50094e334 100644 --- a/apps/desktop/src/api/updater.ts +++ b/apps/desktop/src/api/updater.ts @@ -18,7 +18,12 @@ along with this program. If not, see . */ import { initTRPC } from "@trpc/server"; +import { observable } from "@trpc/server/observable"; import { CancellationToken, autoUpdater } from "electron-updater"; +import type { AppUpdaterEvents } from "electron-updater/out/AppUpdater"; + +type UpdateInfo = { version: string }; +type Progress = { percent: number }; const t = initTRPC.create(); @@ -30,5 +35,37 @@ export const updaterRouter = t.router({ }), check: t.procedure.query(async () => { await autoUpdater.checkForUpdates(); - }) + }), + + onChecking: createSubscription("checking-for-update"), + onDownloaded: createSubscription<"update-downloaded", UpdateInfo>( + "update-downloaded" + ), + onDownloadProgress: createSubscription<"download-progress", Progress>( + "download-progress" + ), + onNotAvailable: createSubscription<"update-not-available", UpdateInfo>( + "update-not-available" + ), + onAvailable: createSubscription<"update-available", UpdateInfo>( + "update-available" + ), + onError: createSubscription("error") }); + +function createSubscription< + TName extends keyof AppUpdaterEvents, + TReturnType = Parameters[0] +>(eventName: TName) { + return t.procedure.subscription(() => { + return observable((emit) => { + const listener: AppUpdaterEvents[TName] = (...args: any[]) => { + emit.next(args[0]); + }; + autoUpdater.addListener(eventName, listener); + return () => { + autoUpdater.removeListener(eventName, listener); + }; + }); + }); +} diff --git a/apps/desktop/src/index.ts b/apps/desktop/src/index.ts index fc157e147..30dcf6689 100644 --- a/apps/desktop/src/index.ts +++ b/apps/desktop/src/index.ts @@ -19,4 +19,4 @@ along with this program. If not, see . export * from "./constants"; export type { AppRouter } from "./api"; -export * from "./rpc"; +export { type UpdateInfo } from "builder-util-runtime"; diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 18f3ffd73..329968966 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -18,7 +18,6 @@ along with this program. If not, see . */ /* global MAC_APP_STORE, RELEASE */ -import { client } from "./rpc/electron"; import { app, BrowserWindow, nativeTheme, shell } from "electron"; import { isDevelopment } from "./utils"; import { registerProtocol, PROTOCOL_URL } from "./utils/protocol"; @@ -118,10 +117,6 @@ async function createWindow() { nativeTheme.on("updated", () => { setupTray(); setupJumplist(); - - if (getTheme() === "system") { - client.onThemeChanged(nativeTheme.shouldUseDarkColors ? "dark" : "light"); - } }); } @@ -129,7 +124,7 @@ app.commandLine.appendSwitch("lang", "en-US"); app.on("ready", async () => { console.info("App ready. Opening window."); - registerProtocol(); + if (!isDevelopment()) registerProtocol(); await createWindow(); configureAutoUpdater(); }); diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts index 507708662..620fd6c1e 100644 --- a/apps/desktop/src/preload.ts +++ b/apps/desktop/src/preload.ts @@ -22,16 +22,15 @@ import { ELECTRON_TRPC_CHANNEL } from "electron-trpc/main"; import { type RendererGlobalElectronTRPC } from "electron-trpc/src/types"; import { NNCrypto } from "@notesnook/crypto"; import { ipcRenderer } from "electron"; -import { CHANNEL, ITransport } from "./rpc"; declare global { var os: string; var electronTRPC: RendererGlobalElectronTRPC; - var RPCTransport: ITransport; var NativeNNCrypto: new () => NNCrypto; } - +console.log("HELLO", process); process.once("loaded", async () => { + console.log("HELLO!"); const electronTRPC: RendererGlobalElectronTRPC = { sendMessage: (operation) => ipcRenderer.send(ELECTRON_TRPC_CHANNEL, operation), @@ -39,20 +38,6 @@ process.once("loaded", async () => { ipcRenderer.on(ELECTRON_TRPC_CHANNEL, (_event, args) => callback(args)) }; globalThis.electronTRPC = electronTRPC; - - globalThis.RPCTransport = { - send(message) { - console.log("[browser] sending message", message); - ipcRenderer.send(CHANNEL, message); - }, - receive(callback) { - ipcRenderer.removeAllListeners(CHANNEL); - ipcRenderer.addListener(CHANNEL, (_event, args) => { - console.log("[browser] recevied message", args); - callback(args); - }); - } - }; }); globalThis.NativeNNCrypto = NNCrypto; diff --git a/apps/desktop/src/rpc/electron.ts b/apps/desktop/src/rpc/electron.ts deleted file mode 100644 index 5c38c2e19..000000000 --- a/apps/desktop/src/rpc/electron.ts +++ /dev/null @@ -1,37 +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 { ipcMain } from "electron"; -import { CHANNEL, createRPCClient } from "."; -import { ClientPrototype, ITransport } from "./types"; - -const transport: ITransport = { - send(message) { - console.log("[electron] sending message", message); - globalThis.window?.webContents.send(CHANNEL, message); - }, - receive(callback) { - ipcMain.on(CHANNEL, (_event, message) => { - console.log("[electron] received message", message); - callback(message); - }); - } -}; - -export const client = createRPCClient(transport, ClientPrototype); diff --git a/apps/desktop/src/rpc/index.ts b/apps/desktop/src/rpc/index.ts deleted file mode 100644 index bbf28edf8..000000000 --- a/apps/desktop/src/rpc/index.ts +++ /dev/null @@ -1,77 +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 { ITransport } from "./types"; - -export const CHANNEL = "RPC_COM_CHANNEL"; -type RPCApi = Record any>; -type WrappedAPI = { - [P in keyof TApi]: ( - ...args: Parameters - ) => ReturnType extends void - ? void - : ReturnType extends Promise - ? Promise - : Promise>; -}; - -export function createRPCClient( - transport: ITransport, - api: TApi -): WrappedAPI { - const wrappedAPI = >{}; - for (const method in api) { - if (Object.hasOwn(api, method)) { - wrappedAPI[method] = (...args: any[]) => { - return new Promise((resolve) => { - transport.receive((message) => { - if (message.type === "response" && message.id === method) { - resolve(message.result); - } - }); - - transport.send({ - type: "message", - id: method, - args - }); - }); - }; - } - } - return wrappedAPI; -} - -export function createRPCServer( - transport: ITransport, - api: TApi -) { - transport.receive(async (message) => { - if ( - message.type === "message" && - message.id && - Object.hasOwn(api, message.id) - ) { - const result = await api[message.id](...message.args); - transport.send({ type: "response", id: message.id, result }); - } - }); -} - -export * from "./types"; diff --git a/apps/desktop/src/rpc/types.ts b/apps/desktop/src/rpc/types.ts deleted file mode 100644 index 30abae563..000000000 --- a/apps/desktop/src/rpc/types.ts +++ /dev/null @@ -1,55 +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 type { - UpdateInfo, - ProgressInfo, - UpdateDownloadedEvent -} from "electron-updater"; -import type { Theme } from "../utils/theme"; - -export const ClientPrototype = { - onCheckingForUpdate() {}, - onUpdateAvailable(info: UpdateInfo) {}, - onUpdateDownloadProgress(progress: ProgressInfo) {}, - onUpdateDownloadCompleted(info: UpdateDownloadedEvent) {}, - onUpdateNotAvailable(info: UpdateInfo) {}, - onThemeChanged(theme: Theme) {}, - onNotificationClicked(tag: string) {}, - onCreateItem(type: "note" | "notebook" | "reminder") { - return type; - } -}; - -export type IClient = typeof ClientPrototype; -export type IClientMethod = keyof IClient; - -export type IMessage = { - type: "message"; - id: string; - args: unknown[]; -}; -export type IResponse = { - type: "response"; - id: string; - result: unknown; -}; -export type ITransport = { - send(message: IMessage | IResponse): void; - receive(callback: (message: IMessage | IResponse) => void): void; -}; diff --git a/apps/desktop/src/utils/autoupdater.ts b/apps/desktop/src/utils/autoupdater.ts index db0636e84..357834626 100644 --- a/apps/desktop/src/utils/autoupdater.ts +++ b/apps/desktop/src/utils/autoupdater.ts @@ -18,7 +18,6 @@ along with this program. If not, see . */ import { autoUpdater } from "electron-updater"; -import { client } from "../rpc/electron"; async function configureAutoUpdater() { autoUpdater.setFeedURL({ @@ -31,14 +30,6 @@ async function configureAutoUpdater() { autoUpdater.allowDowngrade = false; autoUpdater.allowPrerelease = false; autoUpdater.autoInstallOnAppQuit = true; - autoUpdater.addListener("checking-for-update", client.onCheckingForUpdate); - autoUpdater.addListener("update-available", client.onUpdateAvailable); - autoUpdater.addListener("download-progress", client.onUpdateDownloadProgress); - autoUpdater.addListener( - "update-downloaded", - client.onUpdateDownloadCompleted - ); - autoUpdater.addListener("update-not-available", client.onUpdateNotAvailable); } export { configureAutoUpdater }; diff --git a/apps/desktop/src/utils/jumplist.ts b/apps/desktop/src/utils/jumplist.ts index 8efea0c77..1fdbeae41 100644 --- a/apps/desktop/src/utils/jumplist.ts +++ b/apps/desktop/src/utils/jumplist.ts @@ -18,9 +18,9 @@ along with this program. If not, see . */ import { app, Menu } from "electron"; -import { client } from "../rpc/electron"; import { AssetManager } from "./asset-manager"; import { bringToFront } from "./bring-to-front"; +import { bridge } from "../api/bridge"; export function setupJumplist() { if (process.platform === "win32") { @@ -75,7 +75,7 @@ function setDockMenuOnMacOs() { type: "normal", click: () => { bringToFront(); - client.onCreateItem("note"); + bridge.onCreateItem("note"); } }, { @@ -83,7 +83,7 @@ function setDockMenuOnMacOs() { type: "normal", click: () => { bringToFront(); - client.onCreateItem("notebook"); + bridge.onCreateItem("notebook"); } }, { @@ -91,7 +91,7 @@ function setDockMenuOnMacOs() { type: "normal", click: () => { bringToFront(); - client.onCreateItem("reminder"); + bridge.onCreateItem("reminder"); } } ]); diff --git a/apps/desktop/src/utils/protocol.ts b/apps/desktop/src/utils/protocol.ts index 7c6504cc2..4360559fb 100644 --- a/apps/desktop/src/utils/protocol.ts +++ b/apps/desktop/src/utils/protocol.ts @@ -17,14 +17,16 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { protocol, net } from "electron"; +import { protocol, ProtocolRequest } from "electron"; import { isDevelopment } from "./index"; import { createReadStream, statSync } from "fs"; import { extname, join, normalize } from "path"; import { URL } from "url"; +import { Readable } from "stream"; const BASE_PATH = isDevelopment() ? "../public" : ""; const HOSTNAME = `app.notesnook.com`; +const FILE_NOT_FOUND = -6; const SCHEME = "https"; const extensionToMimeType: Record = { html: "text/html", @@ -36,36 +38,98 @@ const extensionToMimeType: Record = { }; function registerProtocol() { - protocol.handle(SCHEME, (request) => { - const url = new URL(request.url); - if (shouldInterceptRequest(url)) { - console.info("Intercepting request:", request); + const protocolInterceptionResult = protocol.interceptStreamProtocol( + SCHEME, + async (request, callback) => { + const url = new URL(request.url); + if (shouldInterceptRequest(url)) { + console.info("Intercepting request:", request.url); - const loadIndex = !extname(url.pathname); - const absoluteFilePath = normalize( - `${__dirname}${ - loadIndex ? `${BASE_PATH}/index.html` : `${BASE_PATH}/${url.pathname}` - }` - ); - const filePath = getPath(absoluteFilePath); - if (!filePath) { - console.error("Local asset file not found at", filePath); - return new Response(undefined, { - status: 404, - statusText: "FILE_NOT_FOUND" + const loadIndex = !extname(url.pathname); + const absoluteFilePath = normalize( + `${__dirname}${ + loadIndex + ? `${BASE_PATH}/index.html` + : `${BASE_PATH}/${url.pathname}` + }` + ); + const filePath = getPath(absoluteFilePath); + if (!filePath) { + console.error("Local asset file not found at", filePath); + callback({ error: FILE_NOT_FOUND }); + return; + } + const fileExtension = extname(filePath).replace(".", ""); + + const data = createReadStream(filePath); + callback({ + data, + mimeType: extensionToMimeType[fileExtension] + }); + } else { + let response: Response; + try { + const body = await getBody(request); + response = await fetch(request.url, { + ...request, + body, + headers: { + ...request.headers + // origin: `${PROTOCOL}://${HOSTNAME}/` + }, + referrer: request.referrer, + redirect: "manual" + }); + } catch (e) { + console.error(e); + console.error(`Error sending request to `, request.url, "Error: ", e); + callback({ statusCode: 400 }); + return; + } + callback({ + statusCode: response.status, + data: response.body ? Readable.fromWeb(response.body) : undefined, + headers: Object.fromEntries(response.headers.entries()), + mimeType: response.headers.get("Content-Type") || undefined }); } - const fileExtension = extname(filePath).replace(".", ""); - const data = createReadStream(filePath); - const headers = new Headers(); - headers.set("Content-Type", extensionToMimeType[fileExtension]); - return new Response(data, { headers }); - } else { - return net.fetch(request); } - }); + ); - console.info(`${SCHEME} protocol inteception "successful"`); + console.info( + `${SCHEME} protocol inteception ${ + protocolInterceptionResult ? "successful" : "failed" + }.` + ); + + // protocol.handle(SCHEME, (request) => { + // const url = new URL(request.url); + // if (shouldInterceptRequest(url)) { + // console.info("Intercepting request:", request.url); + // const loadIndex = !extname(url.pathname); + // const absoluteFilePath = normalize( + // `${__dirname}${ + // loadIndex ? `${BASE_PATH}/index.html` : `${BASE_PATH}/${url.pathname}` + // }` + // ); + // const filePath = getPath(absoluteFilePath); + // if (!filePath) { + // console.error("Local asset file not found at", filePath); + // return new Response(undefined, { + // status: 404, + // statusText: "FILE_NOT_FOUND" + // }); + // } + // const fileExtension = extname(filePath).replace(".", ""); + // const data = createReadStream(filePath); + // return new Response(data, { + // headers: { "Content-Type": extensionToMimeType[fileExtension] } + // }); + // } else { + // return net.fetch(request); + // } + // }); + // console.info(`${SCHEME} protocol inteception "successful"`); } const bypassedRoutes: string[] = []; @@ -77,6 +141,24 @@ function shouldInterceptRequest(url: URL) { const PROTOCOL_URL = `${SCHEME}://${HOSTNAME}/`; export { registerProtocol, PROTOCOL_URL }; +async function getBody(request: ProtocolRequest) { + const session = globalThis?.window?.webContents?.session; + + const blobParts = []; + if (!request.uploadData || !request.uploadData.length) return null; + for (const data of request.uploadData) { + if (data.bytes) { + blobParts.push(new Uint8Array(data.bytes)); + } else if (session && data.blobUUID) { + const buffer = await session.getBlobData(data.blobUUID); + if (!buffer) continue; + blobParts.push(new Uint8Array(buffer)); + } + } + const blob = new Blob(blobParts); + return await blob.arrayBuffer(); +} + function getPath(filePath: string): string | undefined { try { const result = statSync(filePath); diff --git a/apps/desktop/src/utils/tray.ts b/apps/desktop/src/utils/tray.ts index 931b16541..022d85228 100644 --- a/apps/desktop/src/utils/tray.ts +++ b/apps/desktop/src/utils/tray.ts @@ -21,7 +21,7 @@ import { app, Menu, Tray } from "electron"; import { AssetManager } from "./asset-manager"; import { isFlatpak } from "./index"; import { bringToFront } from "./bring-to-front"; -import { client } from "../rpc/electron"; +import { bridge } from "../api/bridge"; let tray: Tray | undefined = undefined; export function destroyTray() { @@ -58,7 +58,7 @@ export function setupTray() { : AssetManager.icon("note-add", { size: trayIconSize }), click: () => { bringToFront(); - client.onCreateItem("note"); + bridge.onCreateItem("note"); } }, { @@ -69,7 +69,7 @@ export function setupTray() { : AssetManager.icon("notebook-add", { size: trayIconSize }), click: () => { bringToFront(); - client.onCreateItem("notebook"); + bridge.onCreateItem("notebook"); } }, { type: "separator" }, diff --git a/apps/web/package-lock.json b/apps/web/package-lock.json index ea116130f..aeb5c56f7 100644 --- a/apps/web/package-lock.json +++ b/apps/web/package-lock.json @@ -22,11 +22,10 @@ "@react-pdf-viewer/core": "^3.12.0", "@react-pdf-viewer/toolbar": "^3.12.0", "@tanstack/react-query": "^4.28.0", - "@tanstack/react-virtual": "^3.0.0-beta.18", "@theme-ui/components": "^0.14.7", "@theme-ui/core": "^0.14.7", - "@trpc/client": "^10.18.0", - "@trpc/react-query": "^10.18.0", + "@trpc/client": "^10.29.1", + "@trpc/react-query": "^10.29.1", "allotment": "^1.12.1", "async-mutex": "^0.3.2", "axios": "^1.3.4", @@ -34,7 +33,7 @@ "comlink": "^4.3.1", "cronosjs": "^1.7.1", "dayjs": "^1.10.4", - "electron-trpc": "^0.4.2", + "electron-trpc": "^0.5.0", "event-source-polyfill": "^1.0.25", "fflate": "^0.7.4", "file-saver": "^2.0.5", @@ -68,6 +67,7 @@ }, "devDependencies": { "@playwright/test": "^1.26.0", + "@trpc/server": "^10.29.1", "@types/file-saver": "^2.0.5", "@types/hookrouter": "^2.2.5", "@types/marked": "^4.0.7", @@ -81,6 +81,7 @@ "buffer": "^6.0.3", "chalk": "^4.1.0", "dotenv": "^10.0.0", + "electron-updater": "^5.3.0", "env-cmd": "^10.1.0", "file-loader": "^6.2.0", "find-process": "^1.4.4", @@ -4699,28 +4700,6 @@ } } }, - "node_modules/@tanstack/react-virtual": { - "version": "3.0.0-beta.48", - "license": "MIT", - "dependencies": { - "@tanstack/virtual-core": "3.0.0-beta.48" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@tanstack/virtual-core": { - "version": "3.0.0-beta.48", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, "node_modules/@theme-ui/components": { "version": "0.14.7", "license": "MIT", @@ -4778,23 +4757,40 @@ } }, "node_modules/@trpc/client": { - "version": "10.18.0", - "license": "MIT", + "version": "10.29.1", + "resolved": "https://registry.npmjs.org/@trpc/client/-/client-10.29.1.tgz", + "integrity": "sha512-+9Tifg6dtKsYLsqOW0wizqc3iILAkXxn16pyYAeMDPlulPEqNvnI85GDJ0zJOJLIkQnQefkRbtCmtDxLNtV9Eg==", + "funding": [ + "https://trpc.io/sponsor" + ], "peerDependencies": { - "@trpc/server": "10.18.0" + "@trpc/server": "10.29.1" } }, "node_modules/@trpc/react-query": { - "version": "10.18.0", - "license": "MIT", + "version": "10.29.1", + "resolved": "https://registry.npmjs.org/@trpc/react-query/-/react-query-10.29.1.tgz", + "integrity": "sha512-yWsce8euPSVtn3SeBKXxLmq607/sqyIez7pgMOhMBKehRNdZzrGp3MhjmRwim+IUKLrw71kUgsw7w6uT5FPB0g==", + "funding": [ + "https://trpc.io/sponsor" + ], "peerDependencies": { "@tanstack/react-query": "^4.18.0", - "@trpc/client": "10.18.0", - "@trpc/server": "10.18.0", + "@trpc/client": "10.29.1", + "@trpc/server": "10.29.1", "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, + "node_modules/@trpc/server": { + "version": "10.29.1", + "resolved": "https://registry.npmjs.org/@trpc/server/-/server-10.29.1.tgz", + "integrity": "sha512-kNXgMh5ya+awuz2tB4eIyVrRs7nVtqGXwSGabzH3l5ZLWz7rbKJquOJ7h6bjvIfWUpaFG62HJNWxxGUtXCRgRw==", + "dev": true, + "funding": [ + "https://trpc.io/sponsor" + ] + }, "node_modules/@trysound/sax": { "version": "0.2.0", "license": "ISC", @@ -6517,6 +6513,19 @@ "version": "1.1.2", "license": "MIT" }, + "node_modules/builder-util-runtime": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.1.1.tgz", + "integrity": "sha512-azRhYLEoDvRDR8Dhis4JatELC/jUvYjm4cVSj7n9dauGTOM2eeNn9KS0z6YA6oDsjI1xphjNbY6PZZeHPzzqaw==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/builtin-modules": { "version": "3.3.0", "license": "MIT", @@ -7985,14 +7994,67 @@ "license": "ISC" }, "node_modules/electron-trpc": { - "version": "0.4.2", - "license": "MIT", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/electron-trpc/-/electron-trpc-0.5.0.tgz", + "integrity": "sha512-xvnvOpuI0IiecMGS9VOS5/3kLj6IUpjngKhv5VqXU9vP2LzE140G8gqaziX0Tl7MaiYmJZbad/imis9i49n/4A==", + "dependencies": { + "debug": "^4.3.4" + }, "peerDependencies": { "@trpc/client": ">10.0.0", "@trpc/server": ">10.0.0", "electron": ">19.0.0" } }, + "node_modules/electron-updater": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-5.3.0.tgz", + "integrity": "sha512-iKEr7yQBcvnQUPnSDYGSWC9t0eF2YbZWeYYYZzYxdl+HiRejXFENjYMnYjoOm2zxyD6Cr2JTHZhp9pqxiXuCOw==", + "dev": true, + "dependencies": { + "@types/semver": "^7.3.6", + "builder-util-runtime": "9.1.1", + "fs-extra": "^10.0.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "lodash.escaperegexp": "^4.1.2", + "lodash.isequal": "^4.5.0", + "semver": "^7.3.5", + "typed-emitter": "^2.1.0" + } + }, + "node_modules/electron-updater/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/electron-updater/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-updater/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/emittery": { "version": "0.8.1", "license": "MIT", @@ -11920,6 +11982,12 @@ "language-subtag-registry": "~0.3.2" } }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true + }, "node_modules/leven": { "version": "3.1.0", "license": "MIT", @@ -12042,6 +12110,12 @@ "version": "4.0.8", "license": "MIT" }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "dev": true + }, "node_modules/lodash.includes": { "version": "4.3.0", "license": "MIT" @@ -15534,6 +15608,23 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true, + "optional": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "funding": [ @@ -17040,6 +17131,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", + "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", + "dev": true, + "optionalDependencies": { + "rxjs": "*" + } + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "license": "MIT", diff --git a/apps/web/package.json b/apps/web/package.json index ee7035820..b2cc3e6b7 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -30,11 +30,10 @@ "@react-pdf-viewer/core": "^3.12.0", "@react-pdf-viewer/toolbar": "^3.12.0", "@tanstack/react-query": "^4.28.0", - "@tanstack/react-virtual": "^3.0.0-beta.18", "@theme-ui/components": "^0.14.7", "@theme-ui/core": "^0.14.7", - "@trpc/client": "^10.18.0", - "@trpc/react-query": "^10.18.0", + "@trpc/client": "^10.29.1", + "@trpc/react-query": "^10.29.1", "allotment": "^1.12.1", "async-mutex": "^0.3.2", "axios": "^1.3.4", @@ -42,7 +41,7 @@ "comlink": "^4.3.1", "cronosjs": "^1.7.1", "dayjs": "^1.10.4", - "electron-trpc": "^0.4.2", + "electron-trpc": "^0.5.0", "event-source-polyfill": "^1.0.25", "fflate": "^0.7.4", "file-saver": "^2.0.5", @@ -75,6 +74,7 @@ "zustand": "^3.3.1" }, "devDependencies": { + "@trpc/server": "^10.29.1", "@playwright/test": "^1.26.0", "@types/file-saver": "^2.0.5", "@types/hookrouter": "^2.2.5", diff --git a/apps/web/src/app-effects.js b/apps/web/src/app-effects.js index dfd24b523..f312e3176 100644 --- a/apps/web/src/app-effects.js +++ b/apps/web/src/app-effects.js @@ -30,7 +30,6 @@ import { introduceFeatures, showUpgradeReminderDialogs } from "./common"; import { AppEventManager, AppEvents } from "./common/app-events"; import { db } from "./common/db"; import { EV, EVENTS } from "@notesnook/core/common"; -import { EVENTS as DESKTOP_APP_EVENTS } from "@notesnook/desktop"; import { registerKeyMap } from "./common/key-map"; import { isUserPremium } from "./hooks/use-is-user-premium"; import { @@ -46,6 +45,7 @@ import { updateStatus, removeStatus, getStatus } from "./hooks/use-status"; import { showToast } from "./utils/toast"; import { interruptedOnboarding } from "./components/dialogs/onboarding-dialog"; import { hashNavigate } from "./navigation"; +import { desktop } from "./common/desktop-bridge"; export default function AppEffects({ setShow }) { const refreshNavItems = useStore((store) => store.refreshNavItems); @@ -198,19 +198,26 @@ export default function AppEffects({ setShow }) { }, [isSystemThemeDark, followSystemTheme, setTheme]); useEffect(() => { - AppEventManager.subscribe(DESKTOP_APP_EVENTS.createItem, ({ itemType }) => { - switch (itemType) { - case "note": - hashNavigate("/notes/create", { addNonce: true, replace: true }); - break; - case "notebook": - hashNavigate("/notebooks/create", { replace: true }); - break; - case "reminder": - hashNavigate("/reminders/create", { replace: true }); - break; - } - }); + const { unsubscribe } = + desktop?.bridge.onCreateItem.subscribe(undefined, { + onData(itemType) { + switch (itemType) { + case "note": + hashNavigate("/notes/create", { addNonce: true, replace: true }); + break; + case "notebook": + hashNavigate("/notebooks/create", { replace: true }); + break; + case "reminder": + hashNavigate("/reminders/create", { replace: true }); + break; + } + } + }) || {}; + + return () => { + unsubscribe(); + }; }, []); return ; diff --git a/apps/web/src/commands/index.js b/apps/web/src/commands/index.js deleted file mode 100644 index 2dadfcff7..000000000 --- a/apps/web/src/commands/index.js +++ /dev/null @@ -1,38 +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 { AppEventManager } from "../common/app-events"; -import { isDesktop } from "../utils/platform"; - -export function invokeCommand(type, payload = {}) { - if (!isDesktop()) return; - - // window.api.send("fromRenderer", { - // type, - // ...payload - // }); -} - -// if (isDesktop()) { -// window.api.receive("fromMain", (args) => { -// console.log(args); -// const { type, ...other } = args; -// AppEventManager.publish(type, other); -// }); -// } diff --git a/apps/web/src/commands/show-notification.js b/apps/web/src/commands/show-notification.js deleted file mode 100644 index 56ce63dfe..000000000 --- a/apps/web/src/commands/show-notification.js +++ /dev/null @@ -1,40 +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 { EVENTS } from "@notesnook/desktop"; -import { AppEventManager } from "../common/app-events"; -import { invokeCommand } from "./index"; - -/** - * - * @param {import("electron").NotificationConstructorOptions & { tag: string }} options - * @param {() => void} onClicked - */ -export default function showNotification(options, onClicked) { - invokeCommand("showNotification", options); - const { unsubscribe } = AppEventManager.subscribe( - EVENTS.notificationClicked, - ({ tag }) => { - if (tag === options.tag) { - onClicked(); - unsubscribe(); - } - } - ); -} diff --git a/apps/web/src/common/app-events.js b/apps/web/src/common/app-events.js index 7f8b208c6..638f6ac42 100644 --- a/apps/web/src/common/app-events.js +++ b/apps/web/src/common/app-events.js @@ -23,5 +23,15 @@ export const AppEventManager = new EventManager(); export const AppEvents = { UPDATE_ATTACHMENT_PROGRESS: "updateAttachmentProgress", UPDATE_STATUS: "updateStatus", - REMOVE_STATUS: "removeStatus" + REMOVE_STATUS: "removeStatus", + + checkingForUpdate: "checkingForUpdate", + updateAvailable: "updateAvailable", + updateDownloadProgress: "updateDownloadProgress", + updateDownloadCompleted: "updateDownloadCompleted", + updateNotAvailable: "updateNotAvailable", + updateError: "updateError", + themeChanged: "themeChanged", + notificationClicked: "notificationClicked", + createItem: "createItem" }; diff --git a/apps/web/src/common/desktop-client.ts b/apps/web/src/common/desktop-bridge/bridge.ts similarity index 51% rename from apps/web/src/common/desktop-client.ts rename to apps/web/src/common/desktop-bridge/bridge.ts index 316a84f2e..3a3c5367d 100644 --- a/apps/web/src/common/desktop-client.ts +++ b/apps/web/src/common/desktop-bridge/bridge.ts @@ -19,31 +19,47 @@ along with this program. If not, see . import { createTRPCProxyClient } from "@trpc/client"; import { ipcLink } from "electron-trpc/renderer"; -import { AppRouter, createRPCServer, IClient } from "@notesnook/desktop"; -import "@notesnook/desktop/dist/rpc/browser"; +import type { AppRouter } from "@notesnook/desktop"; +import { AppEventManager, AppEvents } from "../app-events"; export const desktop = createTRPCProxyClient({ links: [ipcLink()] }); -const client: IClient = { - onCheckingForUpdate: function (): void { - throw new Error("Function not implemented."); - }, - onUpdateAvailable() {}, - onUpdateDownloadProgress() {}, - onUpdateDownloadCompleted() {}, - onUpdateNotAvailable() {}, - onThemeChanged: function (theme: "system" | "light" | "dark"): void { - throw new Error("Function not implemented."); - }, - onNotificationClicked: function (tag: string): void { - throw new Error("Function not implemented."); - }, - onCreateItem: function (type: "note" | "notebook" | "reminder") { - console.log("GOT", type); - return type; - } -}; +desktop.updater.onChecking.subscribe( + undefined, + attachListener(AppEvents.checkingForUpdate) +); -createRPCServer(window.RPCTransport, client); +desktop.updater.onAvailable.subscribe( + undefined, + attachListener(AppEvents.updateAvailable) +); + +desktop.updater.onDownloaded.subscribe( + undefined, + attachListener(AppEvents.updateDownloadCompleted) +); + +desktop.updater.onDownloadProgress.subscribe( + undefined, + attachListener(AppEvents.updateDownloadProgress) +); + +desktop.updater.onNotAvailable.subscribe( + undefined, + attachListener(AppEvents.updateNotAvailable) +); + +desktop.updater.onError.subscribe( + undefined, + attachListener(AppEvents.updateError) +); + +function attachListener(event: string) { + return { + onData(...args: any[]) { + AppEventManager.publish(event, ...args); + } + }; +} diff --git a/apps/desktop/src/rpc/browser.ts b/apps/web/src/common/desktop-bridge/index.ts similarity index 78% rename from apps/desktop/src/rpc/browser.ts rename to apps/web/src/common/desktop-bridge/index.ts index b8c895197..00101cd97 100644 --- a/apps/desktop/src/rpc/browser.ts +++ b/apps/web/src/common/desktop-bridge/index.ts @@ -16,10 +16,10 @@ 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 . */ -/* eslint-disable no-var */ -import { ITransport } from "./types"; +import { type desktop as bridge } from "./bridge"; -declare global { - var RPCTransport: ITransport; -} +export const desktop: typeof bridge | undefined = + process.env.REACT_APP_PLATFORM === "desktop" + ? require("./bridge").desktop + : undefined; diff --git a/apps/web/src/common/index.js b/apps/web/src/common/index.js index 35a382477..46be4fb33 100644 --- a/apps/web/src/common/index.js +++ b/apps/web/src/common/index.js @@ -38,6 +38,7 @@ import { PATHS } from "@notesnook/desktop"; import { TaskManager } from "./task-manager"; import { EVENTS } from "@notesnook/core/common"; import { getFormattedDate } from "../utils/time"; +import { desktop } from "./desktop-bridge"; export const CREATE_BUTTON_MAP = { notes: { @@ -103,7 +104,7 @@ export async function createBackup() { PATHS.backupsDirectory ); const filePath = `${directory}/${filename}.${ext}`; - saveFile(filePath, data); + await desktop?.integration.saveFile.query(filePath, data); showToast("success", `Backup saved at ${filePath}.`); } else { FileSaver.saveAs(new Blob([Buffer.from(data)]), `${filename}.${ext}`); diff --git a/apps/web/src/hooks/use-auto-updater.js b/apps/web/src/hooks/use-auto-updater.ts similarity index 71% rename from apps/web/src/hooks/use-auto-updater.js rename to apps/web/src/hooks/use-auto-updater.ts index 4a9deeae2..f9a2f6175 100644 --- a/apps/web/src/hooks/use-auto-updater.js +++ b/apps/web/src/hooks/use-auto-updater.ts @@ -18,21 +18,30 @@ along with this program. If not, see . */ import { useEffect, useState } from "react"; -import { AppEventManager } from "../common/app-events"; -import { EVENTS } from "@notesnook/desktop"; import { showUpdateAvailableNotice, showUpdateReadyNotice } from "../common/dialog-controller"; import { isDesktop } from "../utils/platform"; import { checkForUpdate } from "../utils/updater"; +import { AppEventManager, AppEvents } from "../common/app-events"; -var checkingForUpdateTimeout = 0; +type CompletedUpdateStatus = { type: "completed"; version: string }; +type DownloadingUpdateStatus = { type: "downloading"; progress: number }; +type AvailableUpdateStatus = { type: "available"; version: string }; +type GenericUpdateStatus = { type: "checking" | "updated" }; +type UpdateStatus = + | AvailableUpdateStatus + | CompletedUpdateStatus + | DownloadingUpdateStatus + | GenericUpdateStatus; + +let checkingForUpdateTimeout = 0; export function useAutoUpdater() { - const [status, setStatus] = useState(); + const [status, setStatus] = useState(); useEffect(() => { - function changeStatus(status) { + function changeStatus(status?: UpdateStatus) { clearTimeout(checkingForUpdateTimeout); setStatus(status); } @@ -41,10 +50,10 @@ export function useAutoUpdater() { changeStatus({ type: "checking" }); checkingForUpdateTimeout = setTimeout(() => { changeStatus({ type: "updated" }); - }, 10000); + }, 10000) as unknown as number; } - function updateAvailable(info) { + function updateAvailable(info: { version: string }) { changeStatus({ type: "available", version: info.version @@ -59,33 +68,33 @@ export function useAutoUpdater() { else changeStatus(); } - function updateDownloadCompleted(info) { + function updateDownloadCompleted(info: { version: string }) { changeStatus({ type: "completed", version: info.version }); showUpdateReadyNotice({ version: info.version }); } - function updateDownloadProgress(progressInfo) { + function updateDownloadProgress(progressInfo: { percent: number }) { changeStatus({ type: "downloading", progress: progressInfo.percent }); } const checkingForUpdateEvent = AppEventManager.subscribe( - EVENTS.checkingForUpdate, + AppEvents.checkingForUpdate, checkingForUpdate ); const updateAvailableEvent = AppEventManager.subscribe( - EVENTS.updateAvailable, + AppEvents.updateAvailable, updateAvailable ); const updateNotAvailableEvent = AppEventManager.subscribe( - EVENTS.updateNotAvailable, + AppEvents.updateNotAvailable, updateNotAvailable ); const updateCompletedEvent = AppEventManager.subscribe( - EVENTS.updateDownloadCompleted, + AppEvents.updateDownloadCompleted, updateDownloadCompleted ); const updateProgressEvent = AppEventManager.subscribe( - EVENTS.updateDownloadProgress, + AppEvents.updateDownloadProgress, updateDownloadProgress ); diff --git a/apps/web/src/hooks/use-desktop-integration.ts b/apps/web/src/hooks/use-desktop-integration.ts index b7dd5a150..a0c04aeac 100644 --- a/apps/web/src/hooks/use-desktop-integration.ts +++ b/apps/web/src/hooks/use-desktop-integration.ts @@ -18,7 +18,7 @@ along with this program. If not, see . */ import { useCallback, useEffect, useState } from "react"; -import { desktop } from "../common/desktop-client"; +import { desktop } from "../common/desktop-bridge"; export type DesktopIntegrationSettings = { autoStart: boolean; @@ -31,7 +31,7 @@ export default function useDesktopIntegration() { const [settings, changeSettings] = useState(); const setupDesktopIntegration = useCallback(async () => { - const settings = await desktop.integration.desktopIntegration.query(); + const settings = await desktop?.integration.desktopIntegration.query(); changeSettings(settings); return settings; }, []); @@ -46,7 +46,7 @@ export default function useDesktopIntegration() { async (_settings: Partial) => { if (!settings) return; - await desktop.integration.setDesktopIntegration.mutate({ + await desktop?.integration.setDesktopIntegration.mutate({ ...settings, ..._settings }); diff --git a/apps/web/src/hooks/use-privacy-mode.ts b/apps/web/src/hooks/use-privacy-mode.ts index 26712e73b..0920aff01 100644 --- a/apps/web/src/hooks/use-privacy-mode.ts +++ b/apps/web/src/hooks/use-privacy-mode.ts @@ -18,19 +18,19 @@ along with this program. If not, see . */ import { useCallback, useEffect, useState } from "react"; -import { desktop } from "../common/desktop-client"; +import { desktop } from "../common/desktop-bridge"; export default function usePrivacyMode() { const [privacyMode, setPrivacyMode] = useState(false); useEffect(() => { (async function () { - setPrivacyMode(await desktop.integration.privacyMode.query()); + setPrivacyMode((await desktop?.integration.privacyMode.query()) || false); })(); }, []); const set = useCallback(async (privacyMode) => { - await desktop.integration.setPrivacyMode.mutate(privacyMode); + await desktop?.integration.setPrivacyMode.mutate(privacyMode); setPrivacyMode(privacyMode); }, []); diff --git a/apps/web/src/hooks/use-spell-checker.ts b/apps/web/src/hooks/use-spell-checker.ts index e32ace9ee..a64aec9b8 100644 --- a/apps/web/src/hooks/use-spell-checker.ts +++ b/apps/web/src/hooks/use-spell-checker.ts @@ -18,7 +18,7 @@ along with this program. If not, see . */ import { useCallback, useEffect, useState } from "react"; -import { desktop } from "../common/desktop-client"; +import { desktop } from "../common/desktop-bridge"; export type Language = { code: string; name: string }; export type SpellCheckerOptions = { @@ -32,9 +32,10 @@ export default function useSpellChecker() { const loadSpellChecker = useCallback(async () => { setSpellChecker({ - enabledLanguages: await desktop.spellChecker.enabledLanguages.query(), - languages: await desktop.spellChecker.languages.query(), - enabled: await desktop.spellChecker.isEnabled.query() + enabledLanguages: + (await desktop?.spellChecker.enabledLanguages.query()) || [], + languages: (await desktop?.spellChecker.languages.query()) || [], + enabled: (await desktop?.spellChecker.isEnabled.query()) || false }); }, []); @@ -46,7 +47,7 @@ export default function useSpellChecker() { const toggle = useCallback( async (enabled: boolean) => { - await desktop.spellChecker.toggle.mutate(enabled); + await desktop?.spellChecker.toggle.mutate(enabled); await loadSpellChecker(); }, [loadSpellChecker] @@ -54,7 +55,7 @@ export default function useSpellChecker() { const setLanguages = useCallback( async (languages: string[]) => { - await desktop.spellChecker.setLanguages.mutate(languages); + await desktop?.spellChecker.setLanguages.mutate(languages); await loadSpellChecker(); }, [loadSpellChecker] diff --git a/apps/web/src/hooks/use-system-theme.js b/apps/web/src/hooks/use-system-theme.ts similarity index 79% rename from apps/web/src/hooks/use-system-theme.js rename to apps/web/src/hooks/use-system-theme.ts index 4760f9b76..86946a361 100644 --- a/apps/web/src/hooks/use-system-theme.js +++ b/apps/web/src/hooks/use-system-theme.ts @@ -17,10 +17,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { EVENTS } from "@notesnook/desktop"; import { useEffect, useState } from "react"; -import { AppEventManager } from "../common/app-events"; import useMediaQuery from "./use-media-query"; +import { desktop } from "../common/desktop-bridge"; function useSystemTheme() { const isDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); @@ -31,12 +30,12 @@ function useSystemTheme() { }, [isDarkMode]); useEffect(() => { - function onThemeChanged({ theme }) { - setSystemTheme(theme); - } - AppEventManager.subscribe(EVENTS.themeChanged, onThemeChanged); + const { unsubscribe } = + desktop?.integration.onThemeChanged.subscribe(undefined, { + onData: setSystemTheme + }) || {}; return () => { - AppEventManager.unsubscribe(EVENTS.themeChanged, onThemeChanged); + unsubscribe?.(); }; }, []); diff --git a/apps/web/src/hooks/use-zoom-factor.ts b/apps/web/src/hooks/use-zoom-factor.ts index 995ad847d..10d4df5a9 100644 --- a/apps/web/src/hooks/use-zoom-factor.ts +++ b/apps/web/src/hooks/use-zoom-factor.ts @@ -18,19 +18,19 @@ along with this program. If not, see . */ import { useCallback, useEffect, useState } from "react"; -import { desktop } from "../common/desktop-client"; +import { desktop } from "../common/desktop-bridge"; export default function useZoomFactor() { const [zoom, setZoom] = useState(1.0); useEffect(() => { (async function () { - setZoom(await desktop.integration.zoomFactor.query()); + setZoom((await desktop?.integration.zoomFactor.query()) || 1.0); })(); }, []); const set = useCallback(async (zoomFactor) => { - await desktop.integration.setZoomFactor.mutate(zoomFactor); + await desktop?.integration.setZoomFactor.mutate(zoomFactor); setZoom(zoomFactor); }, []); diff --git a/apps/web/src/index.tsx b/apps/web/src/index.tsx index 31334b84e..5dc2ba080 100644 --- a/apps/web/src/index.tsx +++ b/apps/web/src/index.tsx @@ -18,8 +18,7 @@ along with this program. If not, see . */ import "@notesnook/core/types"; -import { EVENTS } from "@notesnook/desktop"; -import { AppEventManager } from "./common/app-events"; +import { AppEventManager, AppEvents } from "./common/app-events"; import { render } from "react-dom"; import { getCurrentHash, getCurrentPath, makeURL } from "./navigation"; import Config from "./utils/config"; @@ -30,7 +29,6 @@ import { AuthProps } from "./views/auth"; global.Buffer = Buffer; initalizeLogger(); -if (process.env.REACT_APP_PLATFORM === "desktop") require("./commands"); type Route = { component: () => Promise<{ @@ -188,7 +186,7 @@ async function initializeServiceWorker() { const { formatted } = await getServiceWorkerVersion( registration.waiting ); - AppEventManager.publish(EVENTS.updateDownloadCompleted, { + AppEventManager.publish(AppEvents.updateDownloadCompleted, { version: formatted }); } diff --git a/apps/web/src/stores/reminder-store.js b/apps/web/src/stores/reminder-store.js index 06d7bdff2..06d0593a1 100644 --- a/apps/web/src/stores/reminder-store.js +++ b/apps/web/src/stores/reminder-store.js @@ -27,7 +27,7 @@ import dayjs from "dayjs"; import Config from "../utils/config"; import { store as notestore } from "./note-store"; import { isDesktop, isTesting } from "../utils/platform"; -import { desktop } from "../common/desktop-client"; +import { desktop } from "../common/desktop-bridge"; class ReminderStore extends BaseStore { reminders = []; @@ -99,26 +99,25 @@ function scheduleReminder(id, reminder, cron) { } if (isDesktop()) { - await desktop.integration.showNotification.query( - { - title: reminder.title, - body: reminder.description, - silent: reminder.priority === "silent", - timeoutType: reminder.priority === "urgent" ? "never" : "default", - urgency: - reminder.priority === "urgent" - ? "critical" - : reminder.priority === "vibrate" - ? "normal" - : "low", - focusOnClick: true, - tag: id - }, - async () => { - await desktop.integration.bringToFront.query(); - showReminderPreviewDialog(reminder); - } - ); + const tag = await desktop?.integration.showNotification.query({ + title: reminder.title, + body: reminder.description, + silent: reminder.priority === "silent", + timeoutType: reminder.priority === "urgent" ? "never" : "default", + urgency: + reminder.priority === "urgent" + ? "critical" + : reminder.priority === "vibrate" + ? "normal" + : "low", + focusOnClick: true, + tag: id + }); + + if (tag) { + await desktop?.integration.bringToFront.query(); + showReminderPreviewDialog(reminder); + } } else { const notification = new Notification(reminder.title, { body: reminder.description, diff --git a/apps/web/src/utils/compressor.ts b/apps/web/src/utils/compressor.ts index 488c10a2e..43adfd0da 100644 --- a/apps/web/src/utils/compressor.ts +++ b/apps/web/src/utils/compressor.ts @@ -23,7 +23,7 @@ import Worker from "worker-loader?filename=static/workers/compressor.worker.[con import type { Compressor as CompressorWorker } from "./compressor.worker"; import { wrap, Remote } from "comlink"; import { isDesktop } from "./platform"; -import { desktop } from "../common/desktop-client"; +import { desktop } from "../common/desktop-bridge"; export class Compressor { private worker!: globalThis.Worker; @@ -38,13 +38,13 @@ export class Compressor { async compress(data: string) { if (isDesktop()) - return await desktop.compress.gzip.query({ data, level: 6 }); + return await desktop?.compress.gzip.query({ data, level: 6 }); return await this.compressor.gzip({ data, level: 6 }); } async decompress(data: string) { - if (isDesktop()) return await desktop.compress.gunzip.query(data); + if (isDesktop()) return await desktop?.compress.gunzip.query(data); return await this.compressor.gunzip({ data }); } diff --git a/apps/web/src/utils/updater.ts b/apps/web/src/utils/updater.ts index aa40096ea..198191f56 100644 --- a/apps/web/src/utils/updater.ts +++ b/apps/web/src/utils/updater.ts @@ -17,16 +17,15 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { EVENTS } from "@notesnook/desktop"; -import { AppEventManager } from "../common/app-events"; -import { desktop } from "../common/desktop-client"; +import { AppEventManager, AppEvents } from "../common/app-events"; +import { desktop } from "../common/desktop-bridge"; import { isDesktop } from "./platform"; import { appVersion, getServiceWorkerVersion } from "./version"; export async function checkForUpdate() { - if (isDesktop()) await desktop.updater.check.query(); + if (isDesktop()) await desktop?.updater.check.query(); else { - AppEventManager.publish(EVENTS.checkingForUpdate); + AppEventManager.publish(AppEvents.checkingForUpdate); const registrations = (await navigator.serviceWorker?.getRegistrations()) || []; @@ -41,19 +40,19 @@ export async function checkForUpdate() { continue; } - AppEventManager.publish(EVENTS.updateDownloadCompleted, { + AppEventManager.publish(AppEvents.updateDownloadCompleted, { version: workerVersion.formatted }); return; } } - AppEventManager.publish(EVENTS.updateNotAvailable); + AppEventManager.publish(AppEvents.updateNotAvailable); } } export async function downloadUpdate() { - if (isDesktop()) await desktop.updater.download.query(); + if (isDesktop()) await desktop?.updater.download.query(); else { console.log("Force updating"); if (!("serviceWorker" in navigator)) return; @@ -63,7 +62,7 @@ export async function downloadUpdate() { } export async function installUpdate() { - if (isDesktop()) await desktop.updater.install.query(); + if (isDesktop()) await desktop?.updater.install.query(); else { const registrations = (await navigator.serviceWorker?.getRegistrations()) || []; diff --git a/apps/web/src/views/settings.js b/apps/web/src/views/settings.js index 2c7f4c12f..71097e797 100644 --- a/apps/web/src/views/settings.js +++ b/apps/web/src/views/settings.js @@ -87,7 +87,7 @@ import { writeText } from "clipboard-polyfill"; import { useEditorConfig } from "../components/editor/context"; import { getFonts } from "@notesnook/editor"; import { formatDate } from "@notesnook/core/utils/date"; -import { desktop } from "../common/desktop-client"; +import { desktop } from "../common/desktop-bridge"; function subscriptionStatusToString(user) { const status = user?.subscription?.type; @@ -938,7 +938,7 @@ function Settings() { variant="list" onClick={async () => { const location = - await desktop.integration.selectDirectory.query({ + await desktop?.integration.selectDirectory.query({ title: "Select where Notesnook should save backups", defaultPath: backupStorageLocation || PATHS.backupsDirectory @@ -1156,7 +1156,7 @@ function Settings() {