From 2a66de37dc27c6e0659b86cbd00db74ebaef6a5d Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Sun, 3 Apr 2022 15:30:52 -0400 Subject: [PATCH] Notifications (#23) * notifications and typescriptification * add copyright and license notice --- .../configs/webpack.config.main.prod.ts | 2 +- .../configs/webpack.config.preload.dev.ts | 73 +++++ .../configs/webpack.config.renderer.dev.ts | 18 +- gui/package-lock.json | 242 ++++++++++++++ gui/package.json | 3 + gui/src/main/daemon.ts | 94 ++++++ gui/src/main/main.ts | 69 +++- gui/src/main/{preload.js => preload.ts} | 306 ++++++++---------- gui/src/renderer/Main.tsx | 83 ++--- gui/src/renderer/components/FriendsModal.tsx | 6 +- gui/src/renderer/components/MessageList.tsx | 74 ++--- gui/src/renderer/components/Outbox/Outbox.tsx | 6 +- gui/src/renderer/components/Read.tsx | 2 +- gui/src/renderer/components/Write.tsx | 4 +- gui/src/renderer/components/cmd-k/utils.ts | 2 +- gui/src/renderer/preload.d.ts | 33 ++ gui/src/{renderer => }/types.ts | 0 17 files changed, 756 insertions(+), 261 deletions(-) create mode 100644 gui/helpers/configs/webpack.config.preload.dev.ts create mode 100644 gui/src/main/daemon.ts rename gui/src/main/{preload.js => preload.ts} (57%) create mode 100644 gui/src/renderer/preload.d.ts rename gui/src/{renderer => }/types.ts (100%) diff --git a/gui/helpers/configs/webpack.config.main.prod.ts b/gui/helpers/configs/webpack.config.main.prod.ts index 5dc15f5d..c100b337 100644 --- a/gui/helpers/configs/webpack.config.main.prod.ts +++ b/gui/helpers/configs/webpack.config.main.prod.ts @@ -34,7 +34,7 @@ const configuration: webpack.Configuration = { entry: { main: path.join(webpackPaths.srcMainPath, "main.ts"), - preload: path.join(webpackPaths.srcMainPath, "preload.js"), + preload: path.join(webpackPaths.srcMainPath, "preload.ts"), }, output: { diff --git a/gui/helpers/configs/webpack.config.preload.dev.ts b/gui/helpers/configs/webpack.config.preload.dev.ts new file mode 100644 index 00000000..5bbe0410 --- /dev/null +++ b/gui/helpers/configs/webpack.config.preload.dev.ts @@ -0,0 +1,73 @@ +// +// Copyright 2022 Anysphere, Inc. +// SPDX-License-Identifier: GPL-3.0-only +// + +import path from "path"; +import webpack from "webpack"; +import { merge } from "webpack-merge"; +import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer"; +import baseConfig from "./webpack.config.base"; +import webpackPaths from "./webpack.paths"; +import checkNodeEnv from "../scripts/check-node-env"; + +// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's +// at the dev webpack config is not accidentally run in a production environment +if (process.env.NODE_ENV === "production") { + checkNodeEnv("development"); +} + +const configuration: webpack.Configuration = { + devtool: "inline-source-map", + + mode: "development", + + target: "electron-preload", + + entry: path.join(webpackPaths.srcMainPath, "preload.ts"), + + output: { + path: webpackPaths.dllPath, + filename: "preload.js", + }, + + plugins: [ + new BundleAnalyzerPlugin({ + analyzerMode: process.env.ANALYZE === "true" ? "server" : "disabled", + }), + + /** + * Create global constants which can be configured at compile time. + * + * Useful for allowing different behaviour between development builds and + * release builds + * + * NODE_ENV should be production so that modules do not perform certain + * development checks + * + * By default, use 'development' as NODE_ENV. This can be overriden with + * 'staging', for example, by changing the ENV variables in the npm scripts + */ + new webpack.EnvironmentPlugin({ + NODE_ENV: "development", + }), + + new webpack.LoaderOptionsPlugin({ + debug: true, + }), + ], + + /** + * Disables webpack processing of __dirname and __filename. + * If you run the bundle in node.js it falls back to these values of node.js. + * https://github.com/webpack/webpack/issues/2010 + */ + node: { + __dirname: false, + __filename: false, + }, + + watch: true, +}; + +export default merge(baseConfig, configuration); diff --git a/gui/helpers/configs/webpack.config.renderer.dev.ts b/gui/helpers/configs/webpack.config.renderer.dev.ts index 58c9ea5a..088d0f9c 100644 --- a/gui/helpers/configs/webpack.config.renderer.dev.ts +++ b/gui/helpers/configs/webpack.config.renderer.dev.ts @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-3.0-only // +import "webpack-dev-server"; import path from "path"; import fs from "fs"; import webpack from "webpack"; @@ -138,7 +139,6 @@ const configuration: webpack.Configuration = { __filename: false, }, - // @ts-ignore devServer: { port, compress: true, @@ -150,15 +150,27 @@ const configuration: webpack.Configuration = { historyApiFallback: { verbose: true, }, - onBeforeSetupMiddleware() { + setupMiddlewares(middlewares) { + console.log("Starting preload.js builder..."); + const preloadProcess = spawn("npm", ["run", "start:preload"], { + shell: true, + stdio: "inherit", + }) + .on("close", (code: number) => process.exit(code!)) + .on("error", (spawnError) => console.error(spawnError)); + console.log("Starting Main Process..."); spawn("npm", ["run", "start:main"], { shell: true, env: process.env, stdio: "inherit", }) - .on("close", (code: number) => process.exit(code!)) + .on("close", (code: number) => { + preloadProcess.kill(); + process.exit(code!); + }) .on("error", (spawnError) => console.error(spawnError)); + return middlewares; }, }, }; diff --git a/gui/package-lock.json b/gui/package-lock.json index bf1d31a2..b518f5c7 100644 --- a/gui/package-lock.json +++ b/gui/package-lock.json @@ -32,6 +32,7 @@ "@types/react": "^17.0.38", "@types/react-dom": "^17.0.11", "@types/react-test-renderer": "^17.0.1", + "@types/webpack-bundle-analyzer": "^4.4.1", "@types/webpack-env": "^1.16.3", "@vscode/codicons": "^0.0.27", "babel-loader": "^8.2.3", @@ -63,6 +64,7 @@ "typescript": "^4.5.4", "url-loader": "^4.1.1", "webpack": "^5.65.0", + "webpack-bundle-analyzer": "^4.5.0", "webpack-cli": "^4.9.1", "webpack-dev-server": "^4.7.4", "webpack-merge": "^5.8.0" @@ -3135,6 +3137,12 @@ } } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.21", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", + "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "dev": true + }, "node_modules/@popperjs/core": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz", @@ -10325,6 +10333,17 @@ "source-map": "^0.6.0" } }, + "node_modules/@types/webpack-bundle-analyzer": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.1.tgz", + "integrity": "sha512-yQAj3l7bIYL+QRRlNJt6gyP+zrXZOlgaR4wsX0WY4yzZIbv41ZibREfZvuYjxY0iVtvQQlbhx0AeokkCuqUAQg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "tapable": "^2.2.0", + "webpack": "^5" + } + }, "node_modules/@types/webpack-env": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.3.tgz", @@ -14950,6 +14969,12 @@ "react": ">=16.12.0" } }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, "node_modules/duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -17540,6 +17565,21 @@ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", "dev": true }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -20531,6 +20571,15 @@ "rimraf": "bin.js" } }, + "node_modules/mrmime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.0.tgz", + "integrity": "sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -21246,6 +21295,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, "node_modules/optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -24867,6 +24925,20 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" }, + "node_modules/sirv": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", + "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "dev": true, + "dependencies": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^1.0.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -26284,6 +26356,15 @@ "node": ">=0.6" } }, + "node_modules/totalist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", + "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -27622,6 +27703,68 @@ } } }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz", + "integrity": "sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ==", + "dev": true, + "dependencies": { + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "opener": "^1.5.2", + "sirv": "^1.0.7", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", + "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/webpack-cli": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.1.tgz", @@ -30476,6 +30619,12 @@ "source-map": "^0.7.3" } }, + "@polka/url": { + "version": "1.0.0-next.21", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", + "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "dev": true + }, "@popperjs/core": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz", @@ -36180,6 +36329,17 @@ } } }, + "@types/webpack-bundle-analyzer": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.1.tgz", + "integrity": "sha512-yQAj3l7bIYL+QRRlNJt6gyP+zrXZOlgaR4wsX0WY4yzZIbv41ZibREfZvuYjxY0iVtvQQlbhx0AeokkCuqUAQg==", + "dev": true, + "requires": { + "@types/node": "*", + "tapable": "^2.2.0", + "webpack": "^5" + } + }, "@types/webpack-env": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.3.tgz", @@ -39856,6 +40016,12 @@ "tslib": "^2.3.0" } }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -41886,6 +42052,15 @@ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", "dev": true }, + "gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "requires": { + "duplexer": "^0.1.2" + } + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -44143,6 +44318,12 @@ } } }, + "mrmime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.0.tgz", + "integrity": "sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ==", + "dev": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -44700,6 +44881,12 @@ "is-wsl": "^2.2.0" } }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true + }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -47441,6 +47628,17 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" }, + "sirv": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", + "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "dev": true, + "requires": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^1.0.0" + } + }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -48557,6 +48755,12 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, + "totalist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", + "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "dev": true + }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -49596,6 +49800,44 @@ "webpack-sources": "^3.2.2" } }, + "webpack-bundle-analyzer": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz", + "integrity": "sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ==", + "dev": true, + "requires": { + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "opener": "^1.5.2", + "sirv": "^1.0.7", + "ws": "^7.3.1" + }, + "dependencies": { + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "ws": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", + "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", + "dev": true, + "requires": {} + } + } + }, "webpack-cli": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.1.tgz", diff --git a/gui/package.json b/gui/package.json index a575ddd0..04e8496d 100644 --- a/gui/package.json +++ b/gui/package.json @@ -17,6 +17,7 @@ "postinstall": "ts-node helpers/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./helpers/configs/webpack.config.renderer.dev.dll.ts", "start": "ts-node ./helpers/scripts/check-port-in-use.js && npm run start:renderer", "start:main": "cross-env NODE_ENV=development electron -r ts-node/register/transpile-only ./src/main/main.ts", + "start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./helpers/configs/webpack.config.preload.dev.ts", "start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./helpers/configs/webpack.config.renderer.dev.ts", "storybook": "start-storybook -p 6006", "build-storybook": "build-storybook" @@ -34,6 +35,7 @@ "@types/react": "^17.0.38", "@types/react-dom": "^17.0.11", "@types/react-test-renderer": "^17.0.1", + "@types/webpack-bundle-analyzer": "^4.4.1", "@types/webpack-env": "^1.16.3", "@vscode/codicons": "^0.0.27", "babel-loader": "^8.2.3", @@ -65,6 +67,7 @@ "typescript": "^4.5.4", "url-loader": "^4.1.1", "webpack": "^5.65.0", + "webpack-bundle-analyzer": "^4.5.0", "webpack-cli": "^4.9.1", "webpack-dev-server": "^4.7.4", "webpack-merge": "^5.8.0" diff --git a/gui/src/main/daemon.ts b/gui/src/main/daemon.ts new file mode 100644 index 00000000..0c0948fa --- /dev/null +++ b/gui/src/main/daemon.ts @@ -0,0 +1,94 @@ +// +// Copyright 2022 Anysphere, Inc. +// SPDX-License-Identifier: GPL-3.0-only +// + +const grpc = require("@grpc/grpc-js"); +import daemonS from "../daemon/schema/daemon_grpc_pb"; +import daemonM from "../daemon/schema/daemon_pb"; +import { Message } from "../types"; +import path from "path"; + +function get_socket_path() { + if (process.env.XDG_RUNTIME_DIR) { + return path.join( + process.env.XDG_RUNTIME_DIR, + "anysphere", + "anysphere.sock" + ); + } else if (process.env.XDG_CONFIG_HOME) { + return path.join( + process.env.XDG_CONFIG_HOME, + "anysphere", + "run", + "anysphere.sock" + ); + } else if (process.env.HOME) { + return path.join(process.env.HOME, ".anysphere", "run", "anysphere.sock"); + } else { + process.stderr.write( + "$HOME or $XDG_CONFIG_HOME or $XDG_RUNTIME_DIR not set! Cannot find socket, aborting. :(" + ); + throw new Error("$HOME or $XDG_CONFIG_HOME or $XDG_RUNTIME_DIR not set!"); + } +} + +function get_socket_address() { + return "unix://" + get_socket_path(); +} + +export function getDaemonClient(): daemonS.DaemonClient { + return new daemonS.DaemonClient( + get_socket_address(), + grpc.credentials.createInsecure() + ); +} + +export function convertProtobufIncomingMessageToTypedMessage( + m: daemonM.IncomingMessage +): Message | null { + console.log("seconds", m.getReceivedTimestamp().getSeconds()); + var d = new Date( + m.getReceivedTimestamp().getSeconds() * 1e3 + + m.getReceivedTimestamp().getNanos() / 1e6 + ); + const M = m.getM(); + return M + ? { + id: M.getId(), + from: m.getFrom(), + to: "me", + message: M.getMessage(), + timestamp: d, + type: "incoming", + } + : null; +} + +export function convertProtobufOutgoingMessageToTypedMessage( + m: daemonM.OutgoingMessage +): Message | null { + console.log("seconds", m.getWrittenTimestamp().getSeconds()); + var d = new Date( + m.getWrittenTimestamp().getSeconds() * 1e3 + + m.getWrittenTimestamp().getNanos() / 1e6 + ); + const M = m.getM(); + return M + ? { + id: M.getId(), + from: "me", + to: m.getTo(), + message: M.getMessage(), + timestamp: d, + type: "outgoing", + } + : null; +} + +export function truncate(str: string, maxLength: number) { + if (str.length <= maxLength) { + return str; + } + return str.substring(0, maxLength - 3) + "..."; +} diff --git a/gui/src/main/main.ts b/gui/src/main/main.ts index 281037f0..ce9f4c0d 100644 --- a/gui/src/main/main.ts +++ b/gui/src/main/main.ts @@ -6,10 +6,17 @@ /* eslint global-require: off, no-console: off, promise/always-return: off */ import path from "path"; -import { app, BrowserWindow, session, shell } from "electron"; +import { app, BrowserWindow, session, shell, Notification } from "electron"; import MenuBuilder from "./menu"; import { resolveHtmlPath } from "./util"; +import { + getDaemonClient, + convertProtobufIncomingMessageToTypedMessage, + truncate, +} from "./daemon"; +import daemonM from "../daemon/schema/daemon_pb"; + if (process.env.NODE_ENV === "production") { const sourceMapSupport = require("source-map-support"); sourceMapSupport.install(); @@ -54,7 +61,9 @@ const createWindow = async () => { icon: getAssetPath("icon.png"), titleBarStyle: "hidden", webPreferences: { - preload: path.join(__dirname, "preload.js"), + preload: app.isPackaged + ? path.join(__dirname, "preload.js") + : path.join(__dirname, "../../helpers/dll/preload.js"), }, }); @@ -100,6 +109,55 @@ app.on("window-all-closed", () => { } }); +function registerForNotifications() { + const daemonClient = getDaemonClient(); + const request = new daemonM.GetMessagesRequest(); + request.setFilter(daemonM.GetMessagesRequest.Filter.NEW); + var call = daemonClient.getMessagesStreamed(request); + + let firstTime = true; + + call.on("data", function (r) { + if (firstTime) { + firstTime = false; + return; + } + try { + const lm = r.getMessagesList(); + const l = lm.map(convertProtobufIncomingMessageToTypedMessage); + // notify!!! + for (const m of l) { + const notification = new Notification({ + title: m.from, + body: truncate(m.message, 50), + }); + notification.show(); + } + } catch (e) { + console.log(`error in getNewMessagesStreamed: ${e}`); + } + console.log("got all messages streamed", r); + }); + call.on("end", function () { + // The server has finished sending + // TODO(arvid): resubscribe? + console.log("getNewMessagesStreamed end"); + }); + call.on("error", function (e) { + // An error has occurred and the stream has been closed. + // TODO(arvid): resubscribe? + console.log("getNewMessagesStreamed error", e); + }); + call.on("status", function (status) { + // process status + console.log("getNewMessagesStreamed status", status); + }); + return () => { + console.log("cancelling notifications!"); + call.cancel(); + }; +} + app .whenReady() .then(() => { @@ -111,6 +169,13 @@ app createWindow(); } }); + + // set up notifications! + const cancelNotifications = registerForNotifications(); + + app.on("will-quit", () => { + cancelNotifications(); + }); }) .catch(console.log); diff --git a/gui/src/main/preload.js b/gui/src/main/preload.ts similarity index 57% rename from gui/src/main/preload.js rename to gui/src/main/preload.ts index 4055ab03..d03f11ea 100644 --- a/gui/src/main/preload.js +++ b/gui/src/main/preload.ts @@ -3,51 +3,21 @@ // SPDX-License-Identifier: GPL-3.0-only // -const { contextBridge, clipboard } = require("electron"); -const { promisify } = require("util"); -var grpc = require("@grpc/grpc-js"); -const daemonM = require("../daemon/schema/daemon_pb"); -const daemonS = require("../daemon/schema/daemon_grpc_pb"); -const path = require("path"); - -function get_socket_path() { - if (process.env.XDG_RUNTIME_DIR) { - return path.join( - process.env.XDG_RUNTIME_DIR, - "anysphere", - "anysphere.sock" - ); - } else if (process.env.XDG_CONFIG_HOME) { - return path.join( - process.env.XDG_CONFIG_HOME, - "anysphere", - "run", - "anysphere.sock" - ); - } else if (process.env.HOME) { - return path.join(process.env.HOME, ".anysphere", "run", "anysphere.sock"); - } else { - process.stderr( - "$HOME or $XDG_CONFIG_HOME or $XDG_RUNTIME_DIR not set! Cannot find socket, aborting. :(" - ); - throw new Error("$HOME or $XDG_CONFIG_HOME or $XDG_RUNTIME_DIR not set!"); - } -} - -function get_socket_address() { - return "unix://" + get_socket_path(); -} - -const daemonClient = new daemonS.DaemonClient( - get_socket_address(), - grpc.credentials.createInsecure() -); - -console.log(`SOCKET ADDRESS: ${get_socket_address()}`); +import { contextBridge, clipboard } from "electron"; +import { promisify } from "util"; +import grpc from "@grpc/grpc-js"; +import daemonM from "../daemon/schema/daemon_pb"; +import { + getDaemonClient, + convertProtobufIncomingMessageToTypedMessage, + convertProtobufOutgoingMessageToTypedMessage, +} from "./daemon"; +import { Message } from "../types"; +const daemonClient = getDaemonClient(); const FAKE_DATA = process.env.ASPHR_FAKE_DATA === "true"; -contextBridge.exposeInMainWorld("send", async (message, to) => { +contextBridge.exposeInMainWorld("send", async (message: string, to: string) => { if (FAKE_DATA) { return true; } @@ -57,6 +27,7 @@ contextBridge.exposeInMainWorld("send", async (message, to) => { const sendMessage = promisify(daemonClient.sendMessage).bind(daemonClient); try { const response = await sendMessage(request); + console.log("send response", response); return true; } catch (e) { console.log(`error in send: ${e}`); @@ -64,42 +35,10 @@ contextBridge.exposeInMainWorld("send", async (message, to) => { } }); -contextBridge.exposeInMainWorld("copyToClipboard", async (s) => { +contextBridge.exposeInMainWorld("copyToClipboard", async (s: string) => { clipboard.writeText(s, "selection"); }); -function convertProtobufIncomingMessageToTypedMessage(m) { - console.log("seconds", m.getReceivedTimestamp().getSeconds()); - var d = new Date( - m.getReceivedTimestamp().getSeconds() * 1e3 + - m.getReceivedTimestamp().getNanos() / 1e6 - ); - return { - id: m.getM().getId(), - from: m.getFrom(), - to: "me", - message: m.getM().getMessage(), - timestamp: d, - type: "incoming", - }; -} - -function convertProtobufOutgoingMessageToTypedMessage(m) { - console.log("seconds", m.getWrittenTimestamp().getSeconds()); - var d = new Date( - m.getWrittenTimestamp().getSeconds() * 1e3 + - m.getWrittenTimestamp().getNanos() / 1e6 - ); - return { - id: m.getM().getId(), - from: "me", - to: m.getTo(), - message: m.getM().getMessage(), - timestamp: d, - type: "outgoing", - }; -} - contextBridge.exposeInMainWorld("getNewMessages", async () => { if (FAKE_DATA) { return [ @@ -123,11 +62,11 @@ contextBridge.exposeInMainWorld("getNewMessages", async () => { } const request = new daemonM.GetMessagesRequest(); request.setFilter(daemonM.GetMessagesRequest.Filter.NEW); - const getNewMessages = promisify(daemonClient.getNewMessages).bind( - daemonClient - ); + const getNewMessages = promisify(daemonClient.getMessages).bind(daemonClient); try { - const response = await getNewMessages(request); + const response = (await getNewMessages( + request + )) as daemonM.GetMessagesResponse; const lm = response.getMessagesList(); const l = lm.map(convertProtobufIncomingMessageToTypedMessage); return l; @@ -139,7 +78,7 @@ contextBridge.exposeInMainWorld("getNewMessages", async () => { contextBridge.exposeInMainWorld( "generateFriendKey", - async (requestedFriend) => { + async (requestedFriend: string) => { if (FAKE_DATA) { return { friend: "sualeh", @@ -152,31 +91,34 @@ contextBridge.exposeInMainWorld( daemonClient ); try { - const response = await generateFriendKey(request); + const response = (await generateFriendKey( + request + )) as daemonM.GenerateFriendKeyResponse; return { friend: requestedFriend, key: response.getKey(), }; } catch (e) { console.log(`error in generateFriendKey: ${e}`); - return []; + return null; } } ); contextBridge.exposeInMainWorld( "addFriend", - async (requestedFriend, requestedFriendKey) => { + async (requestedFriend: string, requestedFriendKey: string) => { const request = new daemonM.AddFriendRequest(); request.setName(requestedFriend); request.setKey(requestedFriendKey); const addFriend = promisify(daemonClient.addFriend).bind(daemonClient); try { const response = await addFriend(request); + console.log("addFriend response", response); return true; } catch (e) { console.log(`error in addFriend: ${e}`); - return e; + return false; } } ); @@ -220,11 +162,11 @@ contextBridge.exposeInMainWorld("getAllMessages", async () => { } const request = new daemonM.GetMessagesRequest(); request.setFilter(daemonM.GetMessagesRequest.Filter.ALL); - const getAllMessages = promisify(daemonClient.getAllMessages).bind( - daemonClient - ); + const getAllMessages = promisify(daemonClient.getMessages).bind(daemonClient); try { - const response = await getAllMessages(request); + const response = (await getAllMessages( + request + )) as daemonM.GetMessagesResponse; const lm = response.getMessagesList(); const l = lm.map(convertProtobufIncomingMessageToTypedMessage); return l; @@ -234,69 +176,87 @@ contextBridge.exposeInMainWorld("getAllMessages", async () => { } }); -contextBridge.exposeInMainWorld("getAllMessagesStreamed", (f) => { - const request = new daemonM.GetMessagesRequest(); - request.setFilter(daemonM.GetMessagesRequest.Filter.ALL); - var call = daemonClient.getMessagesStreamed(request); - call.on("data", function (r) { - try { - const lm = r.getMessagesList(); - const l = lm.map(convertProtobufIncomingMessageToTypedMessage); - f(l); - } catch (e) { - console.log(`error in getAllMessagesStreamed: ${e}`); - } - console.log("got all messages streamed", r); - }); - call.on("end", function () { - // The server has finished sending - console.log("getAllMessagesStreamed end"); - }); - call.on("error", function (e) { - // An error has occurred and the stream has been closed. - console.log("getAllMessagesStreamed error", e); - }); - call.on("status", function (status) { - // process status - console.log("getAllMessagesStreamed status", status); - }); - return () => { - console.log("cancelling grpc!"); - call.cancel(); - }; -}); +contextBridge.exposeInMainWorld( + "getAllMessagesStreamed", + (messageHandler: (_: Message[]) => void) => { + const request = new daemonM.GetMessagesRequest(); + request.setFilter(daemonM.GetMessagesRequest.Filter.ALL); + var call = daemonClient.getMessagesStreamed(request); + call.on("data", function (r: daemonM.GetMessagesResponse) { + try { + const lm = r.getMessagesList(); + const l = lm.map(convertProtobufIncomingMessageToTypedMessage); + let l2: Message[] = []; + for (const m of l) { + if (m) { + l2.push(m); + } + } + messageHandler(l2); + } catch (e) { + console.log(`error in getAllMessagesStreamed: ${e}`); + } + console.log("got all messages streamed", r); + }); + call.on("end", function () { + // The server has finished sending + console.log("getAllMessagesStreamed end"); + }); + call.on("error", function (e: Error) { + // An error has occurred and the stream has been closed. + console.log("getAllMessagesStreamed error", e); + }); + call.on("status", function (status: grpc.StatusObject) { + // process status + console.log("getAllMessagesStreamed status", status); + }); + return () => { + console.log("cancelling grpc!"); + call.cancel(); + }; + } +); -contextBridge.exposeInMainWorld("getNewMessagesStreamed", (f) => { - const request = new daemonM.GetMessagesRequest(); - request.setFilter(daemonM.GetMessagesRequest.Filter.NEW); - var call = daemonClient.getMessagesStreamed(request); - call.on("data", function (r) { - try { - const lm = r.getMessagesList(); - const l = lm.map(convertProtobufIncomingMessageToTypedMessage); - f(l); - } catch (e) { - console.log(`error in getNewMessagesStreamed: ${e}`); - } - console.log("got all messages streamed", r); - }); - call.on("end", function () { - // The server has finished sending - console.log("getNewMessagesStreamed end"); - }); - call.on("error", function (e) { - // An error has occurred and the stream has been closed. - console.log("getNewMessagesStreamed error", e); - }); - call.on("status", function (status) { - // process status - console.log("getNewMessagesStreamed status", status); - }); - return () => { - console.log("cancelling grpc!"); - call.cancel(); - }; -}); +contextBridge.exposeInMainWorld( + "getNewMessagesStreamed", + (messageHandler: (_: Message[]) => void) => { + const request = new daemonM.GetMessagesRequest(); + request.setFilter(daemonM.GetMessagesRequest.Filter.NEW); + var call = daemonClient.getMessagesStreamed(request); + call.on("data", function (r: daemonM.GetMessagesResponse) { + try { + const lm = r.getMessagesList(); + const l = lm.map(convertProtobufIncomingMessageToTypedMessage); + let l2: Message[] = []; + for (const m of l) { + if (m) { + l2.push(m); + } + } + messageHandler(l2); + } catch (e) { + console.log(`error in getNewMessagesStreamed: ${e}`); + } + console.log("got all messages streamed", r); + }); + call.on("end", function () { + // The server has finished sending + console.log("getNewMessagesStreamed end"); + }); + call.on("error", function (e: Error) { + // An error has occurred and the stream has been closed. + console.log("getNewMessagesStreamed error", e); + }); + call.on("status", function (status: grpc.StatusObject) { + // process status + console.log("getNewMessagesStreamed status", status); + }); + return () => { + console.log("cancelling grpc!"); + call.cancel(); + }; + } +); contextBridge.exposeInMainWorld("getOutboxMessages", async () => { if (FAKE_DATA) { @@ -340,7 +300,9 @@ contextBridge.exposeInMainWorld("getOutboxMessages", async () => { daemonClient ); try { - const response = await getOutboxMessages(request); + const response = (await getOutboxMessages( + request + )) as daemonM.GetOutboxMessagesResponse; const lm = response.getMessagesList(); const l = lm.map(convertProtobufOutgoingMessageToTypedMessage); return l; @@ -377,7 +339,9 @@ contextBridge.exposeInMainWorld("getSentMessages", async () => { daemonClient ); try { - const response = await getSentMessages(request); + const response = (await getSentMessages( + request + )) as daemonM.GetSentMessagesResponse; const lm = response.getMessagesList(); const l = lm.map(convertProtobufOutgoingMessageToTypedMessage); return l; @@ -409,7 +373,9 @@ contextBridge.exposeInMainWorld("getFriendList", async () => { daemonClient ); try { - const response = await getFriendList(request); + const response = (await getFriendList( + request + )) as daemonM.GetFriendListResponse; const lm = response.getFriendInfosList(); const l = lm.map((m) => { return { @@ -424,12 +390,13 @@ contextBridge.exposeInMainWorld("getFriendList", async () => { } }); -contextBridge.exposeInMainWorld("messageSeen", async (message_id) => { +contextBridge.exposeInMainWorld("messageSeen", async (message_id: string) => { const request = new daemonM.MessageSeenRequest(); request.setId(message_id); const messageSeen = promisify(daemonClient.messageSeen).bind(daemonClient); try { const response = await messageSeen(request); + console.log("messageSeen response", response); return true; } catch (e) { console.log(`error in send: ${e}`); @@ -441,7 +408,7 @@ contextBridge.exposeInMainWorld("hasRegistered", async () => { const request = new daemonM.GetStatusRequest(); const getStatus = promisify(daemonClient.getStatus).bind(daemonClient); try { - const response = await getStatus(request); + const response = (await getStatus(request)) as daemonM.GetStatusResponse; return response.getRegistered(); } catch (e) { console.log(`error in hasRegistered: ${e}`); @@ -449,19 +416,24 @@ contextBridge.exposeInMainWorld("hasRegistered", async () => { } }); -contextBridge.exposeInMainWorld("register", async (username, accessKey) => { - const request = new daemonM.RegisterUserRequest(); - request.setName(username); - request.setBetaKey(accessKey); - const register = promisify(daemonClient.registerUser).bind(daemonClient); - try { - const response = await register(request); - return true; - } catch (e) { - console.log(`error in register: ${e}`); +contextBridge.exposeInMainWorld( + "register", + async (username: string, accessKey: string) => { + const request = new daemonM.RegisterUserRequest(); + request.setName(username); + request.setBetaKey(accessKey); + const register = promisify(daemonClient.registerUser).bind(daemonClient); + try { + const response = await register(request); + console.log("register response", response); + return true; + } catch (e) { + console.log(`error in register: ${e}`); + } + return false; } - return false; -}); +); + contextBridge.exposeInMainWorld("isPlatformMac", () => { return process.platform === "darwin"; }); diff --git a/gui/src/renderer/Main.tsx b/gui/src/renderer/Main.tsx index 1be18f41..816c5baa 100644 --- a/gui/src/renderer/Main.tsx +++ b/gui/src/renderer/Main.tsx @@ -11,7 +11,7 @@ import Write from "./components/Write"; import FriendsModal from "./components/FriendsModal"; import { InitFriendModal } from "./components/FriendsModal"; import { RegisterModal } from "./components/RegisterModal"; -import { Message } from "./types"; +import { Message } from "../types"; import { Tab, TabType, TabContainer, useTabs } from "./components/Tabs"; import { randomString, truncate } from "./utils"; import { CmdK } from "./components/cmd-k/CmdK"; @@ -59,7 +59,7 @@ function Main() { return; } } - (window as any).messageSeen(message.id); + window.messageSeen(message.id); const readTab = { type: TabType.Read, name: `${truncate(message.message, 10)} - ${message.from}`, @@ -86,7 +86,7 @@ function Main() { const send = React.useCallback( (content: string, to: string) => { - (window as any).send(content, to).then((s: boolean) => { + window.send(content, to).then((s: boolean) => { if (s) { statusState.setStatus({ message: `Message sent to ${to}!`, @@ -136,18 +136,17 @@ function Main() { const openAddFriendModal = React.useCallback( (friend: string) => { - (window as any) + window .generateFriendKey(friend) - .then(({ friend, key }: { friend: string; key: string }) => { - setModal( - { - (window as any) - .addFriend(friend, key) - .then((successOrError: any) => { + .then((friendKeyMaybe: null | { friend: string; key: string }) => { + if (friendKeyMaybe) { + setModal( + { + window.addFriend(friend, key).then((successOrError: any) => { if (successOrError === true) { statusState.setStatus({ message: `Added ${friend}!`, @@ -164,10 +163,18 @@ function Main() { statusState.setVisible(); } }); - closeModal(); - }} - /> - ); + closeModal(); + }} + /> + ); + } else { + statusState.setStatus({ + message: `Could not generate friend key for ${friend}`, + action: () => {}, + actionName: null, + }); + statusState.setVisible(); + } }); }, [setModal, closeModal, statusState] @@ -235,32 +242,30 @@ function Main() { } React.useEffect(() => { - (window as any).hasRegistered().then((registered: boolean) => { + window.hasRegistered().then((registered: boolean) => { if (!registered) { setModal( {}} // should not be able to close modal by clicking outside onRegister={(username: string, key: string) => { - (window as any) - .register(username, key) - .then((registered: boolean) => { - if (registered) { - closeModal(); - statusState.setStatus({ - message: `Registered as ${username}.`, - action: () => {}, - actionName: null, - }); - statusState.setVisible(); - } else { - statusState.setStatus({ - message: `Unable to register. Perhaps incorrect access key?`, - action: () => {}, - actionName: null, - }); - statusState.setVisible(); - } - }); + window.register(username, key).then((registered: boolean) => { + if (registered) { + closeModal(); + statusState.setStatus({ + message: `Registered as ${username}.`, + action: () => {}, + actionName: null, + }); + statusState.setVisible(); + } else { + statusState.setStatus({ + message: `Unable to register. Perhaps incorrect access key?`, + action: () => {}, + actionName: null, + }); + statusState.setVisible(); + } + }); }} /> ); diff --git a/gui/src/renderer/components/FriendsModal.tsx b/gui/src/renderer/components/FriendsModal.tsx index 19e99c26..86e186eb 100644 --- a/gui/src/renderer/components/FriendsModal.tsx +++ b/gui/src/renderer/components/FriendsModal.tsx @@ -5,7 +5,7 @@ import * as React from "react"; import Modal from "./Modal"; -import { Friend } from "../types"; +import { Friend } from "../../types"; import { SelectableList, ListItem } from "./SelectableList"; function FriendsModal({ @@ -19,7 +19,7 @@ function FriendsModal({ const [friends, setFriends] = React.useState([]); React.useEffect(() => { - (window as any).getFriendList().then((friends: Friend[]) => { + window.getFriendList().then((friends: Friend[]) => { setFriends(friends); }); }, []); @@ -304,7 +304,7 @@ export function InitFriendModal({ diff --git a/gui/src/renderer/components/MessageList.tsx b/gui/src/renderer/components/MessageList.tsx index 7e5853ba..194d9c4b 100644 --- a/gui/src/renderer/components/MessageList.tsx +++ b/gui/src/renderer/components/MessageList.tsx @@ -4,7 +4,7 @@ // import * as React from "react"; -import { Message } from "../types"; +import { Message } from "../../types"; import { truncate, formatTime } from "../utils"; import { SelectableList } from "./SelectableList"; @@ -59,54 +59,50 @@ function MessageList(props: { React.useEffect(() => { if (props.messages === "new") { setMessages([]); - let cancel = (window as any).getNewMessagesStreamed( - (messages: Message[]) => { - setMessages((prev: Message[]) => { - // merge new messages with old messages, and sort them by timestamp - let new_messages = messages.concat(prev); - new_messages.sort((a, b) => { - // sort based on timestamp - if (a.timestamp > b.timestamp) { - return -1; - } else if (a.timestamp < b.timestamp) { - return 1; - } else { - return 0; - } - }); - return new_messages; + let cancel = window.getNewMessagesStreamed((messages: Message[]) => { + setMessages((prev: Message[]) => { + // merge new messages with old messages, and sort them by timestamp + let new_messages = messages.concat(prev); + new_messages.sort((a, b) => { + // sort based on timestamp + if (a.timestamp > b.timestamp) { + return -1; + } else if (a.timestamp < b.timestamp) { + return 1; + } else { + return 0; + } }); - } - ); + return new_messages; + }); + }); return cancel; } else if (props.messages === "all") { setMessages([]); - let cancel = (window as any).getAllMessagesStreamed( - (messages: Message[]) => { - setMessages((prev: Message[]) => { - // merge new messages with old messages, and sort them by timestamp - let new_messages = messages.concat(prev); - new_messages.sort((a, b) => { - // sort based on timestamp - if (a.timestamp > b.timestamp) { - return -1; - } else if (a.timestamp < b.timestamp) { - return 1; - } else { - return 0; - } - }); - return new_messages; + let cancel = window.getAllMessagesStreamed((messages: Message[]) => { + setMessages((prev: Message[]) => { + // merge new messages with old messages, and sort them by timestamp + let new_messages = messages.concat(prev); + new_messages.sort((a, b) => { + // sort based on timestamp + if (a.timestamp > b.timestamp) { + return -1; + } else if (a.timestamp < b.timestamp) { + return 1; + } else { + return 0; + } }); - } - ); + return new_messages; + }); + }); return cancel; } else if (props.messages === "outbox") { - (window as any).getOutboxMessages().then((messages: Message[]) => { + window.getOutboxMessages().then((messages: Message[]) => { setMessages(messages); }); } else if (props.messages === "sent") { - (window as any).getSentMessages().then((messages: Message[]) => { + window.getSentMessages().then((messages: Message[]) => { setMessages(messages); }); } diff --git a/gui/src/renderer/components/Outbox/Outbox.tsx b/gui/src/renderer/components/Outbox/Outbox.tsx index 5d41a6d7..94a31962 100644 --- a/gui/src/renderer/components/Outbox/Outbox.tsx +++ b/gui/src/renderer/components/Outbox/Outbox.tsx @@ -4,7 +4,7 @@ // import * as React from "react"; -import { Message } from "../../types"; +import { Message } from "../../../types"; import { truncate, formatTime } from "../../utils"; import { SelectableList } from "../SelectableList"; @@ -57,11 +57,11 @@ export default function OutboxMessageList(props: { React.useEffect(() => { if (props.messages === "new") { - (window as any).getNewMessages().then((messages: Message[]) => { + window.getNewMessages().then((messages: Message[]) => { setMessages(messages); }); } else { - (window as any).getAllMessages().then((messages: Message[]) => { + window.getAllMessages().then((messages: Message[]) => { setMessages(messages); }); } diff --git a/gui/src/renderer/components/Read.tsx b/gui/src/renderer/components/Read.tsx index 6febf702..ef025b05 100644 --- a/gui/src/renderer/components/Read.tsx +++ b/gui/src/renderer/components/Read.tsx @@ -4,7 +4,7 @@ // import * as React from "react"; -import { Message } from "../types"; +import { Message } from "../../types"; function Read({ message, onClose }: { message: Message; onClose: () => void }) { React.useEffect(() => { diff --git a/gui/src/renderer/components/Write.tsx b/gui/src/renderer/components/Write.tsx index 197321b6..59321786 100644 --- a/gui/src/renderer/components/Write.tsx +++ b/gui/src/renderer/components/Write.tsx @@ -4,7 +4,7 @@ // import * as React from "react"; -import { Friend } from "../types"; +import { Friend } from "../../types"; import { useSearch, useFocus } from "../utils"; import { SelectableList, ListItem } from "./SelectableList"; @@ -131,7 +131,7 @@ function Write(props: { const [contextTestareaFocusRef, setContextTestareaFocusRef] = useFocus(); React.useEffect(() => { - (window as any).getFriendList().then((friends: Friend[]) => { + window.getFriendList().then((friends: Friend[]) => { setFriends(friends); }); }, []); diff --git a/gui/src/renderer/components/cmd-k/utils.ts b/gui/src/renderer/components/cmd-k/utils.ts index c15ac38f..92c241f0 100644 --- a/gui/src/renderer/components/cmd-k/utils.ts +++ b/gui/src/renderer/components/cmd-k/utils.ts @@ -42,7 +42,7 @@ export function shouldRejectKeystrokes( } const SSR = typeof window === "undefined"; -const isMac = !SSR && (window as any).isPlatformMac(); +const isMac = !SSR && window.isPlatformMac(); export function isModKey( event: KeyboardEvent | MouseEvent | React.KeyboardEvent ) { diff --git a/gui/src/renderer/preload.d.ts b/gui/src/renderer/preload.d.ts new file mode 100644 index 00000000..0cc6e24a --- /dev/null +++ b/gui/src/renderer/preload.d.ts @@ -0,0 +1,33 @@ +// +// Copyright 2022 Anysphere, Inc. +// SPDX-License-Identifier: GPL-3.0-only +// + +import { Message, Friend } from "../types"; + +declare global { + interface Window { + send(message: string, to: string): Promise; + copyToClipboard(s: string): void; + getNewMessages(): Promise; + getAllMessages(): Promise; + getNewMessagesStreamed(messageHandler: (_: Message[]) => void): () => void; + getAllMessagesStreamed(messageHandler: (_: Message[]) => void): () => void; + generateFriendKey( + requestedFriend: string + ): Promise; + addFriend( + requestedFriend: string, + requestedFriendKey: string + ): Promise; + messageSeen(message_id: string): Promise; + hasRegistered(): Promise; + register(username: string, accessKey: string): Promise; + isPlatformMac(): boolean; + getFriendList(): Promise; + getOutboxMessages(): Promise; + getSentMessages(): Promise; + } +} + +export {}; diff --git a/gui/src/renderer/types.ts b/gui/src/types.ts similarity index 100% rename from gui/src/renderer/types.ts rename to gui/src/types.ts