From cd007b40e1f2cf2f23a429c51a4971d6770b3312 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Sat, 26 Feb 2022 01:12:00 -0600 Subject: [PATCH 001/174] Make the SDK friendly to locally link and develop on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix https://github.com/vector-im/hydrogen-web/issues/686 Fix https://github.com/vector-im/hydrogen-web/issues/682 Instead of deleting the whole `target/` directory, leave it alone so the symlink driving the `npm link`/`yarn link` stays in tact. Leave Vite builds in their build directories (`/lib-build`/`/asset-build`) so you can `vite build --watch` to build on local changes and still have a consisent place to reference in the `package.json` `exports`. Previously, everything relied on `build.sh` which does a bunch of moving and renaming and made it hard to rebuild on changes. Add back support for CommonJS (adding the `package.json` `exports`). The last piece is making sure the `?url` imports (`import workerPath from 'hydrogen-view-sdk/main.js?url';`) work still. It looks like this may have just been solved via https://github.com/vitejs/vite/issues/6725 -> https://github.com/vitejs/vite/pull/7073 (literally 2 days ago) and we just need to wait for the next Vite release 🎉 --- scripts/sdk/base-manifest.json | 13 ++++++++++++- scripts/sdk/build.sh | 12 +++--------- scripts/sdk/create-manifest.js | 16 +--------------- vite.sdk-assets-config.js | 19 +++++++++++++++++++ 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index 3ee2ca3b..a7e287e4 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -2,6 +2,17 @@ "name": "hydrogen-view-sdk", "description": "Embeddable matrix client library, including view components", "version": "0.0.5", - "main": "./hydrogen.es.js", + "main": "./hydrogen.cjs.js", + "exports": { + ".": { + "import": "./lib-build/hydrogen.es.js", + "require": "./lib-build/hydrogen.cjs.js" + }, + "./paths/vite": "./paths/vite.js", + "./style.css": "./asset-build/assets/index.css", + "./main.js": "./asset-build/assets/download-sandbox.html", + "./download-sandbox.html": "./asset-build/assets/download-sandbox.html", + "./assets/*": "./asset-build/assets/*" + }, "type": "module" } diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index 5534601e..9063d7fb 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -1,5 +1,5 @@ #!/bin/bash -rm -rf target +rm -rf target/* yarn run vite build -c vite.sdk-assets-config.js yarn run vite build -c vite.sdk-lib-config.js yarn tsc -p tsconfig-declaration.json @@ -10,13 +10,7 @@ mkdir target/paths cp doc/SDK.md target/README.md pushd target pushd asset-build/assets -mv main.*.js ../../main.js -mv index.*.css ../../style.css -mv download-sandbox.*.html ../../download-sandbox.html -rm *.js *.wasm -mv ./* ../../ +rm !(main).js *.wasm popd -rm -rf asset-build -mv lib-build/* . -rm -rf lib-build +rm index.html popd diff --git a/scripts/sdk/create-manifest.js b/scripts/sdk/create-manifest.js index b420e679..9d5cebb2 100755 --- a/scripts/sdk/create-manifest.js +++ b/scripts/sdk/create-manifest.js @@ -3,21 +3,7 @@ const fs = require("fs"); const appManifest = require("../../package.json"); const baseSDKManifest = require("./base-manifest.json"); /* - need to leave exports out of base-manifest.json because of #vite-bug, - with the downside that we can't support environments that support - both esm and commonjs modules, so we pick just esm. - ``` - "exports": { - ".": { - "import": "./hydrogen.es.js", - "require": "./hydrogen.cjs.js" - }, - "./paths/vite": "./paths/vite.js", - "./style.css": "./style.css" - }, - ``` - - Also need to leave typescript type definitions out until the + Need to leave typescript type definitions out until the typescript conversion is complete and all imports in the d.ts files exists. ``` diff --git a/vite.sdk-assets-config.js b/vite.sdk-assets-config.js index 90720966..ebb95e4e 100644 --- a/vite.sdk-assets-config.js +++ b/vite.sdk-assets-config.js @@ -2,10 +2,29 @@ const path = require("path"); const mergeOptions = require('merge-options'); const commonOptions = require("./vite.common-config.js"); +const pathsToExport = [ + "index.js", + "index.css", + "download-sandbox.html" +]; + export default mergeOptions(commonOptions, { root: "src/", base: "./", build: { outDir: "../target/asset-build/", + rollupOptions: { + output: { + assetFileNames: (chunkInfo) => { + // Get rid of the hash so we can consistently reference these + // files in our `package.json` `exports` + if(pathsToExport.includes(path.basename(chunkInfo.name))) { + return "assets/[name].[ext]"; + } + + return "assets/[name]-[hash][extname]"; + } + } + } }, }); From 8fb2b2755a489f70e5ef32c6ffdc4441010d47b2 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Sat, 26 Feb 2022 03:08:16 -0600 Subject: [PATCH 002/174] Fix typos pointing to wrong files --- scripts/sdk/base-manifest.json | 2 +- vite.sdk-assets-config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index a7e287e4..71ef28e2 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -10,7 +10,7 @@ }, "./paths/vite": "./paths/vite.js", "./style.css": "./asset-build/assets/index.css", - "./main.js": "./asset-build/assets/download-sandbox.html", + "./main.js": "./asset-build/assets/main.js", "./download-sandbox.html": "./asset-build/assets/download-sandbox.html", "./assets/*": "./asset-build/assets/*" }, diff --git a/vite.sdk-assets-config.js b/vite.sdk-assets-config.js index ebb95e4e..0d769734 100644 --- a/vite.sdk-assets-config.js +++ b/vite.sdk-assets-config.js @@ -3,7 +3,7 @@ const mergeOptions = require('merge-options'); const commonOptions = require("./vite.common-config.js"); const pathsToExport = [ - "index.js", + "main.js", "index.css", "download-sandbox.html" ]; From 0023ab34baf0666b9fa573edcec8861db6d62056 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Sat, 26 Feb 2022 05:19:59 -0600 Subject: [PATCH 003/174] Add a placeholder for upgrading vite to comment on --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8fa27f47..cec780ae 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "regenerator-runtime": "^0.13.7", "text-encoding": "^0.7.0", "typescript": "^4.3.5", - "vite": "^2.6.14", + "vite": "todo: wait for next Vite release", "xxhashjs": "^0.2.2", "bs58": "^4.0.1" }, From 95d17303c34bde1ceb125055262e379febce3e4f Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 5 Apr 2022 17:16:55 -0500 Subject: [PATCH 004/174] Update Vite which includes fixes to importing `*.js?url` with `exports` Update to Vite which includes https://github.com/vitejs/vite/pull/7098 --- package.json | 2 +- yarn.lock | 224 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 179 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 17719801..9b785c35 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "regenerator-runtime": "^0.13.7", "text-encoding": "^0.7.0", "typescript": "^4.3.5", - "vite": "todo: wait for next Vite release", + "vite": "^2.9.1", "xxhashjs": "^0.2.2" }, "dependencies": { diff --git a/yarn.lock b/yarn.lock index 7bcefdd4..71fe419e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -498,66 +498,141 @@ entities@^2.0.0: version "4.2.8" resolved "https://github.com/bwindels/es6-promise.git#112f78f5829e627055b0ff56a52fecb63f6003b1" +esbuild-android-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.31.tgz#4b7dfbbeee62b3495ba78220b85fb590eb68d5bf" + integrity sha512-MYkuJ91w07nGmr4EouejOZK2j/f5TCnsKxY8vRr2+wpKKfHD1LTJK28VbZa+y1+AL7v1V9G98ezTUwsV3CmXNw== + esbuild-android-arm64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz#3fc3ff0bab76fe35dd237476b5d2b32bb20a3d44" integrity sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg== +esbuild-android-arm64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.31.tgz#24c3d693924e044fb0d23206c3e627502b10b930" + integrity sha512-0rkH/35s7ZVcsw6nS0IAkR0dekSbjZGWdlOAf3jV0lGoPqqw0x6/TmaV9w7DQgUERTH1ApmPlpAMU4kVkCq9Jg== + esbuild-darwin-64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz#8e9169c16baf444eacec60d09b24d11b255a8e72" integrity sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ== +esbuild-darwin-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.31.tgz#285fbdb6dc74d4410f43dee59e6a14ebff82a9d7" + integrity sha512-kP6xPZHxtJa36Hb0jC05L3VzQSZBW2f3bpnQS20czXTRGEmM2GDiYpGdI5g2QYaw6vC4PYXjnigq8usd9g9jnQ== + esbuild-darwin-arm64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz#1b07f893b632114f805e188ddfca41b2b778229a" integrity sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ== +esbuild-darwin-arm64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.31.tgz#b39c471a8134ce2c7811eb96fab9c500b256261c" + integrity sha512-1ZMog4hkNsdBGtDDtsftUqX6S9N52gEx4vX5aVehsSptgoBFIar1XrPiBTQty7YNH+bJasTpSVaZQgElCVvPKQ== + esbuild-freebsd-64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz#0b8b7eca1690c8ec94c75680c38c07269c1f4a85" integrity sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA== +esbuild-freebsd-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.31.tgz#7ca700ef60ae12154bae63094ad41b21c6ae1a23" + integrity sha512-Zo0BYj7QpVFWoUpkv6Ng0RO2eJ4zk/WDaHMO88+jr5HuYmxsOre0imgwaZVPquTuJnCvL1G48BFucJ3tFflSeQ== + esbuild-freebsd-arm64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz#2e1a6c696bfdcd20a99578b76350b41db1934e52" integrity sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ== +esbuild-freebsd-arm64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.31.tgz#f793085c7184fcd08649b8d185edc5c2ce112e82" + integrity sha512-t85bS6jbRpmdjr4pdr/FY/fpx8lo1vv9S7BAs2EsXKJQhRDMIiC3QW+k2acYJoRuqirlvJcJVFQGCq/PfyC1kA== + esbuild-linux-32@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz#6fd39f36fc66dd45b6b5f515728c7bbebc342a69" integrity sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g== +esbuild-linux-32@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.31.tgz#cac97ec7da6fbde0c21dbe08babd0d2a034f317d" + integrity sha512-XYtOk/GodSkv+UOYVwryGpGPuFnszsMvRMKq6cIUfFfdssHuKDsU9IZveyCG44J106J39ABenQ5EetbYtVJHUw== + esbuild-linux-64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz#9cb8e4bcd7574e67946e4ee5f1f1e12386bb6dd3" integrity sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA== +esbuild-linux-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.31.tgz#ec94cd5228e6777d2feb3c24a1fe1cbf8817d6da" + integrity sha512-Zf9CZxAxaXWHLqCg/QZ/hs0RU0XV3IBxV+ENQzy00S4QOTnZAvSLgPciILHHrVJ0lPIlb4XzAqlLM5y6iI2LIw== + esbuild-linux-arm64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz#3891aa3704ec579a1b92d2a586122e5b6a2bfba1" integrity sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA== +esbuild-linux-arm64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.31.tgz#d119188fccd6384db5c703de24c46dacaee3e9e8" + integrity sha512-V/H0tv+xpQ9IOHM+o85oCKNNidIEc5CcnDWl0V+hPd2F03dqdbFkWPBGphx8rD4JSQn6UefUQ1iH7y1qIzO8Fw== + esbuild-linux-arm@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz#8a00e99e6a0c6c9a6b7f334841364d8a2b4aecfe" integrity sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA== +esbuild-linux-arm@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.31.tgz#63e10846886901e5632a591d44160f95c5d12ba7" + integrity sha512-RpiaeHPRlgCCDskxoyIsI49BhcDtZ4cl8+SLffizDm0yMNWP538SUg0ezQ2TTOPj3/svaGIbkRDwYtAon0Sjkg== + esbuild-linux-mips64le@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz#36b07cc47c3d21e48db3bb1f4d9ef8f46aead4f7" integrity sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg== +esbuild-linux-mips64le@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.31.tgz#1cd44f72cde6489a5d6deea7c54efa6f3d6590ee" + integrity sha512-9/oBfAckInRuUg6AEgdCLLn6KJ6UOJDOLmUinTsReVSg6AfV6wxYQJq9iQM2idRogP7GUpomJ+bvCdWXpotQRQ== + esbuild-linux-ppc64le@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz#f7e6bba40b9a11eb9dcae5b01550ea04670edad2" integrity sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ== +esbuild-linux-ppc64le@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.31.tgz#3b5ccc05e5b8ef5c494f30a61fdd27811d2bbeeb" + integrity sha512-NMcb14Pg+8q8raGkzor9/R3vQwKzgxE3694BtO2SDLBwJuL2C1dQ1ZtM1t7ZvArQBgT8RiZVxb0/3fD+qGNk7g== + +esbuild-linux-riscv64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.31.tgz#d74ca78c8ed1d9b40bc907a9e3ef6e83fc06189c" + integrity sha512-l13yvmsVfawAnoYfcpuvml+nTlrOmtdceXYufSkXl2DOb0JKcuR6ARlAzuQCDcpo49SOJy1cCxpwlOIsUQBfzA== + +esbuild-linux-s390x@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.31.tgz#1bd547b8b027e323b77a838d265cb56ece2543af" + integrity sha512-GIwV9mY3koYja9MCSkKLk1P7rj+MkPV0UsGsZ575hEcIBrXeKN9jBi6X/bxDDPEN/SUAH35cJhBNrZU4x9lEfg== + esbuild-netbsd-64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz#a2fedc549c2b629d580a732d840712b08d440038" integrity sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w== +esbuild-netbsd-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.31.tgz#964a45dbad4fac92aa0a15056e38a182735bd6c6" + integrity sha512-bJ+pyLvKQm+Obp5k7/Wk8e9Gdkls56F1aiI3uptoIfOIUqsZImH7pDyTrSufwqsFp62kO9LRuwXnjDwQtPyhFQ== + esbuild-node-loader@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/esbuild-node-loader/-/esbuild-node-loader-0.6.3.tgz#3b90012f8bc2fcbb2ef76a659482c2c99840c5e8" @@ -570,27 +645,52 @@ esbuild-openbsd-64@0.13.15: resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz#b22c0e5806d3a1fbf0325872037f885306b05cd7" integrity sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g== +esbuild-openbsd-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.31.tgz#7d2a1d46450321b0459263d3e7072e6d3924ce46" + integrity sha512-NRAAPPca05H9j9Xab0kVXK0V6/pyZGGy8d2Y8KS0BMwWEydlD4KCJDmH8/7bWCKYLRGOOCE9/GPBJyPWHFW3sg== + esbuild-sunos-64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz#d0b6454a88375ee8d3964daeff55c85c91c7cef4" integrity sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw== +esbuild-sunos-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.31.tgz#3b3e4363951cd1dda14a14fee6d94ca426108e0c" + integrity sha512-9uA+V8w9Eehu4ldb95lPWdgCMcMO5HH6pXmfkk5usn3JsSZxKdLKsXB4hYgP80wscZvVYXJl2G+KNxsUTfPhZw== + esbuild-windows-32@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz#c96d0b9bbb52f3303322582ef8e4847c5ad375a7" integrity sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw== +esbuild-windows-32@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.31.tgz#997026a41c04535bfb7c014a0458940b49145820" + integrity sha512-VGdncQTqoxD9q3v/dk0Yugbmx2FzOkcs0OemBYc1X9KXOLQYH0uQbLJIckZdZOC3J+JKSExbYFrzYCOwWPuNyA== + esbuild-windows-64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz#1f79cb9b1e1bb02fb25cd414cb90d4ea2892c294" integrity sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ== +esbuild-windows-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.31.tgz#5d4b0ea686c9b60036303b3415c472f2761bdafc" + integrity sha512-v/2ye5zBqpmCzi3bLCagStbNQlnOsY7WtMrD2Q0xZxeSIXONxji15KYtVee5o7nw4lXWbQSS1BL8G6BBMvtq4A== + esbuild-windows-arm64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz#482173070810df22a752c686509c370c3be3b3c3" integrity sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA== -esbuild@^0.13.12, esbuild@^0.13.2: +esbuild-windows-arm64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.31.tgz#4f3b9fc34c4a33abbd0171df6cbb657ccbdbfc67" + integrity sha512-RXeU42FJoG1sriNHg73h4S+5B7L/gw+8T7U9u8IWqSSEbY6fZvBh4uofugiU1szUDqqP00GHwZ09WgYe3lGZiw== + +esbuild@^0.13.12: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.15.tgz#db56a88166ee373f87dbb2d8798ff449e0450cdf" integrity sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw== @@ -613,6 +713,32 @@ esbuild@^0.13.12, esbuild@^0.13.2: esbuild-windows-64 "0.13.15" esbuild-windows-arm64 "0.13.15" +esbuild@^0.14.27: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.31.tgz#f7d0db114edc615f66d84972ee9fbd2b267f4029" + integrity sha512-QA0fUM13+JZzcvg1bdrhi7wo8Lr5IRHA9ypNn2znqxGqb66dSK6pAh01TjyBOhzZGazPQJZ1K26VrCAQJ715qA== + optionalDependencies: + esbuild-android-64 "0.14.31" + esbuild-android-arm64 "0.14.31" + esbuild-darwin-64 "0.14.31" + esbuild-darwin-arm64 "0.14.31" + esbuild-freebsd-64 "0.14.31" + esbuild-freebsd-arm64 "0.14.31" + esbuild-linux-32 "0.14.31" + esbuild-linux-64 "0.14.31" + esbuild-linux-arm "0.14.31" + esbuild-linux-arm64 "0.14.31" + esbuild-linux-mips64le "0.14.31" + esbuild-linux-ppc64le "0.14.31" + esbuild-linux-riscv64 "0.14.31" + esbuild-linux-s390x "0.14.31" + esbuild-netbsd-64 "0.14.31" + esbuild-openbsd-64 "0.14.31" + esbuild-sunos-64 "0.14.31" + esbuild-windows-32 "0.14.31" + esbuild-windows-64 "0.14.31" + esbuild-windows-arm64 "0.14.31" + escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -961,10 +1087,10 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -is-core-module@^2.2.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" - integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== +is-core-module@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== dependencies: has "^1.0.3" @@ -1108,10 +1234,10 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -nanoid@^3.1.28: - version "3.1.28" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.28.tgz#3c01bac14cb6c5680569014cc65a2f26424c6bd4" - integrity sha512-gSu9VZ2HtmoKYe/lmyPFES5nknFrHa+/DT9muUFWFMi6Jh9E1I7bkvlQ8xxf1Kos9pi9o8lBnIOkatMhKX/YUw== +nanoid@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557" + integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA== natural-compare@^1.4.0: version "1.4.0" @@ -1188,20 +1314,20 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -picocolors@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" - integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== picomatch@^2.2.3: version "2.3.0" @@ -1227,14 +1353,14 @@ postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.3.8: - version "8.3.9" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.9.tgz#98754caa06c4ee9eb59cc48bd073bb6bd3437c31" - integrity sha512-f/ZFyAKh9Dnqytx5X62jgjhhzttjZS7hMsohcI7HEI5tjELX/HxCy3EFhsRxyzGvrzFF+82XPvCS8T9TFleVJw== +postcss@^8.4.12: + version "8.4.12" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" + integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== dependencies: - nanoid "^3.1.28" - picocolors "^0.2.1" - source-map-js "^0.6.2" + nanoid "^3.3.1" + picocolors "^1.0.0" + source-map-js "^1.0.2" prelude-ls@^1.2.1: version "1.2.1" @@ -1291,13 +1417,14 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve@^1.20.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== +resolve@^1.22.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" reusify@^1.0.4: version "1.0.4" @@ -1311,10 +1438,10 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -rollup@^2.57.0: - version "2.58.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.58.0.tgz#a643983365e7bf7f5b7c62a8331b983b7c4c67fb" - integrity sha512-NOXpusKnaRpbS7ZVSzcEXqxcLDOagN6iFS8p45RkoiMqPHDLwJm758UF05KlMoCRbLBTZsPOIa887gZJ1AiXvw== +rollup@^2.59.0: + version "2.70.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.1.tgz#824b1f1f879ea396db30b0fc3ae8d2fead93523e" + integrity sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA== optionalDependencies: fsevents "~2.3.2" @@ -1368,10 +1495,10 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -source-map-js@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" - integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== source-map@~0.6.1: version "0.6.1" @@ -1418,6 +1545,11 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + table@^6.0.9: version "6.7.1" resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" @@ -1521,15 +1653,15 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -vite@^2.6.14: - version "2.6.14" - resolved "https://registry.yarnpkg.com/vite/-/vite-2.6.14.tgz#35c09a15e4df823410819a2a239ab11efb186271" - integrity sha512-2HA9xGyi+EhY2MXo0+A2dRsqsAG3eFNEVIo12olkWhOmc8LfiM+eMdrXf+Ruje9gdXgvSqjLI9freec1RUM5EA== +vite@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.1.tgz#84bce95fae210a7beb566a0af06246748066b48f" + integrity sha512-vSlsSdOYGcYEJfkQ/NeLXgnRv5zZfpAsdztkIrs7AZHV8RCMZQkwjo4DS5BnrYTqoWqLoUe1Cah4aVO4oNNqCQ== dependencies: - esbuild "^0.13.2" - postcss "^8.3.8" - resolve "^1.20.0" - rollup "^2.57.0" + esbuild "^0.14.27" + postcss "^8.4.12" + resolve "^1.22.0" + rollup "^2.59.0" optionalDependencies: fsevents "~2.3.2" From dd06d78a7218a36086dccfa7ae8553aa10005c3a Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 5 Apr 2022 18:17:14 -0500 Subject: [PATCH 005/174] Avoid ERR_REQUIRE_ESM errors when requiring SDK --- package.json | 3 ++- scripts/sdk/base-manifest.json | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9b785c35..6de800e3 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "test:postcss": "impunity --entry-point scripts/postcss/test.js ", "start": "vite --port 3000", "build": "vite build", - "build:sdk": "./scripts/sdk/build.sh" + "build:sdk": "./scripts/sdk/build.sh", + "watch:sdk": "./scripts/sdk/build.sh && yarn run vite build -c vite.sdk-lib-config.js --watch" }, "repository": { "type": "git", diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index fbc2b39c..da814c8d 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -2,7 +2,7 @@ "name": "hydrogen-view-sdk", "description": "Embeddable matrix client library, including view components", "version": "0.0.9", - "main": "./hydrogen.cjs.js", + "main": "./lib-build/hydrogen.cjs.js", "exports": { ".": { "import": "./lib-build/hydrogen.es.js", @@ -13,6 +13,5 @@ "./main.js": "./asset-build/assets/main.js", "./download-sandbox.html": "./asset-build/assets/download-sandbox.html", "./assets/*": "./asset-build/assets/*" - }, - "type": "module" + } } From 2401b7f453a417dc87635e43aa2339ed2517600c Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 5 Apr 2022 19:24:27 -0500 Subject: [PATCH 006/174] Add way to test whether SDK works in ESM and CommonJS --- package.json | 1 + scripts/sdk/test/.gitignore | 3 +++ scripts/sdk/test/deps.d.ts | 2 ++ scripts/sdk/test/esm-entry.ts | 21 +++++++++++++++++++ scripts/sdk/test/index.html | 12 +++++++++++ scripts/sdk/test/package.json | 8 +++++++ scripts/sdk/test/test-sdk-in-commonjs-env.js | 13 ++++++++++++ .../test/test-sdk-in-esm-vite-build-env.js | 19 +++++++++++++++++ 8 files changed, 79 insertions(+) create mode 100644 scripts/sdk/test/.gitignore create mode 100644 scripts/sdk/test/deps.d.ts create mode 100644 scripts/sdk/test/esm-entry.ts create mode 100644 scripts/sdk/test/index.html create mode 100644 scripts/sdk/test/package.json create mode 100644 scripts/sdk/test/test-sdk-in-commonjs-env.js create mode 100644 scripts/sdk/test/test-sdk-in-esm-vite-build-env.js diff --git a/package.json b/package.json index 6de800e3..4e0b0643 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "lint-ci": "eslint src/", "test": "impunity --entry-point src/platform/web/main.js src/platform/web/Platform.js --force-esm-dirs lib/ src/ --root-dir src/", "test:postcss": "impunity --entry-point scripts/postcss/test.js ", + "test:sdk": "cd ./scripts/sdk/test/ && yarn --no-lockfile && node test-sdk-in-esm-vite-build-env.js && node test-sdk-in-commonjs-env.js", "start": "vite --port 3000", "build": "vite build", "build:sdk": "./scripts/sdk/build.sh", diff --git a/scripts/sdk/test/.gitignore b/scripts/sdk/test/.gitignore new file mode 100644 index 00000000..cf762fe6 --- /dev/null +++ b/scripts/sdk/test/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +yarn.lock diff --git a/scripts/sdk/test/deps.d.ts b/scripts/sdk/test/deps.d.ts new file mode 100644 index 00000000..4c7d0327 --- /dev/null +++ b/scripts/sdk/test/deps.d.ts @@ -0,0 +1,2 @@ +// Keep TypeScripts from complaining about hydrogen-view-sdk not having types yet +declare module "hydrogen-view-sdk"; diff --git a/scripts/sdk/test/esm-entry.ts b/scripts/sdk/test/esm-entry.ts new file mode 100644 index 00000000..162cb6ef --- /dev/null +++ b/scripts/sdk/test/esm-entry.ts @@ -0,0 +1,21 @@ +import * as hydrogenViewSdk from "hydrogen-view-sdk"; +import downloadSandboxPath from 'hydrogen-view-sdk/download-sandbox.html?url'; +import workerPath from 'hydrogen-view-sdk/main.js?url'; +import olmWasmPath from '@matrix-org/olm/olm.wasm?url'; +import olmJsPath from '@matrix-org/olm/olm.js?url'; +import olmLegacyJsPath from '@matrix-org/olm/olm_legacy.js?url'; +const assetPaths = { + downloadSandbox: downloadSandboxPath, + worker: workerPath, + olm: { + wasm: olmWasmPath, + legacyBundle: olmLegacyJsPath, + wasmBundle: olmJsPath + } +}; +import "hydrogen-view-sdk/style.css"; + +console.log('hydrogenViewSdk', hydrogenViewSdk); +console.log('assetPaths', assetPaths); + +console.log('Entry ESM works ✅'); diff --git a/scripts/sdk/test/index.html b/scripts/sdk/test/index.html new file mode 100644 index 00000000..2ee14116 --- /dev/null +++ b/scripts/sdk/test/index.html @@ -0,0 +1,12 @@ + + + + + + Vite App + + +
+ + + diff --git a/scripts/sdk/test/package.json b/scripts/sdk/test/package.json new file mode 100644 index 00000000..a81da82c --- /dev/null +++ b/scripts/sdk/test/package.json @@ -0,0 +1,8 @@ +{ + "name": "test-sdk", + "version": "0.0.0", + "description": "", + "dependencies": { + "hydrogen-view-sdk": "link:../../../target" + } +} diff --git a/scripts/sdk/test/test-sdk-in-commonjs-env.js b/scripts/sdk/test/test-sdk-in-commonjs-env.js new file mode 100644 index 00000000..fc7245f8 --- /dev/null +++ b/scripts/sdk/test/test-sdk-in-commonjs-env.js @@ -0,0 +1,13 @@ +// Make sure the SDK can be used in a CommonJS environment. +// Usage: node scripts/sdk/test/test-sdk-in-commonjs-env.js +const hydrogenViewSdk = require('hydrogen-view-sdk'); + +// Test that the "exports" are available: +// Worker +require.resolve('hydrogen-view-sdk/main.js'); +// Styles +require.resolve('hydrogen-view-sdk/style.css'); +// Can access files in the assets/* directory +require.resolve('hydrogen-view-sdk/assets/main.js'); + +console.log('SDK works in CommonJS ✅'); diff --git a/scripts/sdk/test/test-sdk-in-esm-vite-build-env.js b/scripts/sdk/test/test-sdk-in-esm-vite-build-env.js new file mode 100644 index 00000000..6fc87da7 --- /dev/null +++ b/scripts/sdk/test/test-sdk-in-esm-vite-build-env.js @@ -0,0 +1,19 @@ +const { resolve } = require('path'); +const { build } = require('vite'); + +async function main() { + await build({ + outDir: './dist', + build: { + rollupOptions: { + input: { + main: resolve(__dirname, 'index.html') + } + } + } + }); + + console.log('SDK works in Vite build ✅'); +} + +main(); From f61bf6090eeaf6b0dea54b796bdb83b105eef250 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 19 Apr 2022 17:28:09 -0500 Subject: [PATCH 007/174] Enable extended globs for removing all but some filename !(filename) See https://github.com/vector-im/hydrogen-web/pull/693#discussion_r853534719 --- scripts/sdk/build.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index 9063d7fb..0d7e1170 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -1,4 +1,7 @@ #!/bin/bash +# Enable extended globs so we can use the `!(filename)` glob syntax +shopt -s extglob + rm -rf target/* yarn run vite build -c vite.sdk-assets-config.js yarn run vite build -c vite.sdk-lib-config.js From f56dc582a50a617c411cdde0823f0bc25e3863a3 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 20 Apr 2022 00:39:32 -0500 Subject: [PATCH 008/174] Fix tests after theme updates --- package.json | 2 +- scripts/sdk/test/esm-entry.ts | 2 +- scripts/sdk/test/test-sdk-in-commonjs-env.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index dce5380e..7a5e79cc 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "lint-ci": "eslint src/", "test": "impunity --entry-point src/platform/web/main.js src/platform/web/Platform.js --force-esm-dirs lib/ src/ --root-dir src/", "test:postcss": "impunity --entry-point scripts/postcss/tests/css-compile-variables.test.js scripts/postcss/tests/css-url-to-variables.test.js", - "test:sdk": "cd ./scripts/sdk/test/ && yarn --no-lockfile && node test-sdk-in-esm-vite-build-env.js && node test-sdk-in-commonjs-env.js", + "test:sdk": "yarn build:sdk && cd ./scripts/sdk/test/ && yarn --no-lockfile && node test-sdk-in-esm-vite-build-env.js && node test-sdk-in-commonjs-env.js", "start": "vite --port 3000", "build": "vite build", "build:sdk": "./scripts/sdk/build.sh", diff --git a/scripts/sdk/test/esm-entry.ts b/scripts/sdk/test/esm-entry.ts index 162cb6ef..1f3b7114 100644 --- a/scripts/sdk/test/esm-entry.ts +++ b/scripts/sdk/test/esm-entry.ts @@ -13,7 +13,7 @@ const assetPaths = { wasmBundle: olmJsPath } }; -import "hydrogen-view-sdk/style.css"; +import "hydrogen-view-sdk/theme-element-light.css"; console.log('hydrogenViewSdk', hydrogenViewSdk); console.log('assetPaths', assetPaths); diff --git a/scripts/sdk/test/test-sdk-in-commonjs-env.js b/scripts/sdk/test/test-sdk-in-commonjs-env.js index fc7245f8..3fd19d46 100644 --- a/scripts/sdk/test/test-sdk-in-commonjs-env.js +++ b/scripts/sdk/test/test-sdk-in-commonjs-env.js @@ -6,7 +6,7 @@ const hydrogenViewSdk = require('hydrogen-view-sdk'); // Worker require.resolve('hydrogen-view-sdk/main.js'); // Styles -require.resolve('hydrogen-view-sdk/style.css'); +require.resolve('hydrogen-view-sdk/theme-element-light.css'); // Can access files in the assets/* directory require.resolve('hydrogen-view-sdk/assets/main.js'); From f1e07b684213edd8be1651898f526dc3d568d64d Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 20 Apr 2022 11:59:49 -0500 Subject: [PATCH 009/174] Explain what is being deleted by the strange syntax See https://github.com/vector-im/hydrogen-web/pull/693#discussion_r815284713 --- scripts/sdk/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index b0040b61..5adc69c5 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -17,6 +17,7 @@ mkdir target/paths cp doc/SDK.md target/README.md pushd target pushd asset-build/assets +# Remove all `*.wasm` and `*.js` files except for `main.js` rm !(main).js *.wasm popd rm index.html From ce289baba61e40b711fecca10066c0be73542f44 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 20 Apr 2022 17:32:12 -0500 Subject: [PATCH 010/174] Remove extra space --- scripts/sdk/build.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index 5adc69c5..6a7bc93f 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -5,7 +5,6 @@ set -o pipefail # Enable extended globs so we can use the `!(filename)` glob syntax shopt -s extglob - rm -rf target/* yarn run vite build -c vite.sdk-assets-config.js yarn run vite build -c vite.sdk-lib-config.js From d053d4388fb6d72612749cc6a864272242e1b6fa Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 5 May 2022 14:58:43 -0500 Subject: [PATCH 011/174] Update Vite to avoid flakey errors in our PostCSS plugins Fix https://github.com/vector-im/hydrogen-web/issues/722 Updating Vite to includes fixes from https://github.com/vitejs/vite/issues/7822 -> https://github.com/vitejs/vite/pull/7827 --- package.json | 2 +- yarn.lock | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 7a5e79cc..49369865 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "regenerator-runtime": "^0.13.7", "text-encoding": "^0.7.0", "typescript": "^4.3.5", - "vite": "^2.9.1", + "vite": "^2.9.8", "xxhashjs": "^0.2.2" }, "dependencies": { diff --git a/yarn.lock b/yarn.lock index 71fe419e..9d882bd2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1234,10 +1234,10 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -nanoid@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557" - integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA== +nanoid@^3.3.3: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== natural-compare@^1.4.0: version "1.4.0" @@ -1353,12 +1353,12 @@ postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.12: - version "8.4.12" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" - integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== +postcss@^8.4.13: + version "8.4.13" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575" + integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA== dependencies: - nanoid "^3.3.1" + nanoid "^3.3.3" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -1653,13 +1653,13 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -vite@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.1.tgz#84bce95fae210a7beb566a0af06246748066b48f" - integrity sha512-vSlsSdOYGcYEJfkQ/NeLXgnRv5zZfpAsdztkIrs7AZHV8RCMZQkwjo4DS5BnrYTqoWqLoUe1Cah4aVO4oNNqCQ== +vite@^2.9.8: + version "2.9.8" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.8.tgz#2c2cb0790beb0fbe4b8c0995b80fe691a91c2545" + integrity sha512-zsBGwn5UT3YS0NLSJ7hnR54+vUKfgzMUh/Z9CxF1YKEBVIe213+63jrFLmZphgGI5zXwQCSmqIdbPuE8NJywPw== dependencies: esbuild "^0.14.27" - postcss "^8.4.12" + postcss "^8.4.13" resolve "^1.22.0" rollup "^2.59.0" optionalDependencies: From e54482e4c00b285ab442b55ed67411e671bebe77 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 5 May 2022 17:57:25 -0500 Subject: [PATCH 012/174] Add some comments --- scripts/sdk/build.sh | 2 ++ vite.sdk-assets-config.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index 6a7bc93f..992d2efc 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -5,6 +5,8 @@ set -o pipefail # Enable extended globs so we can use the `!(filename)` glob syntax shopt -s extglob +# Only remove the directory contents instead of the whole directory to maintain +# the `npm link`/`yarn link` symlink rm -rf target/* yarn run vite build -c vite.sdk-assets-config.js yarn run vite build -c vite.sdk-lib-config.js diff --git a/vite.sdk-assets-config.js b/vite.sdk-assets-config.js index 7535a441..beb7bb37 100644 --- a/vite.sdk-assets-config.js +++ b/vite.sdk-assets-config.js @@ -3,6 +3,8 @@ const mergeOptions = require('merge-options'); const themeBuilder = require("./scripts/build-plugins/rollup-plugin-build-themes"); const {commonOptions, compiledVariables} = require("./vite.common-config.js"); +// These paths will be saved without their hash so they havea consisent path to +// reference const pathsToExport = [ "main.js", "download-sandbox.html", From f16a2e5d22abcea46f33aa162634f1bf514257d2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 18 Apr 2022 16:17:56 +0530 Subject: [PATCH 013/174] Don't add asset hash to manifest json on build --- vite.config.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/vite.config.js b/vite.config.js index 87e3d063..a44d0917 100644 --- a/vite.config.js +++ b/vite.config.js @@ -16,25 +16,33 @@ export default defineConfig(({mode}) => { sourcemap: true, rollupOptions: { output: { - assetFileNames: (asset) => asset.name.includes("config.json") ? "assets/[name][extname]": "assets/[name].[hash][extname]", + assetFileNames: (asset) => + asset.name.includes("config.json") || + asset.name.match(/theme-.+\.json/) + ? "assets/[name][extname]" + : "assets/[name].[hash][extname]", }, }, }, plugins: [ themeBuilder({ themeConfig: { - themes: {"element": "./src/platform/web/ui/css/themes/element"}, + themes: { + element: "./src/platform/web/ui/css/themes/element", + }, default: "element", }, - compiledVariables + compiledVariables, }), // important this comes before service worker // otherwise the manifest and the icons it refers to won't be cached injectWebManifest("assets/manifest.json"), injectServiceWorker("./src/platform/web/sw.js", ["index.html"], { // placeholders to replace at end of build by chunk name - "index": {DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH}, - "sw": definePlaceholders + index: { + DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH, + }, + sw: definePlaceholders, }), ], define: definePlaceholders, From 541cd96eeb3ef9d8ab0174b51bc89b2e98659434 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 18 Apr 2022 16:49:11 +0530 Subject: [PATCH 014/174] Add script to cleanup after build --- package.json | 2 +- scripts/cleanup.sh | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100755 scripts/cleanup.sh diff --git a/package.json b/package.json index 5b53790e..679000e1 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test": "impunity --entry-point src/platform/web/main.js src/platform/web/Platform.js --force-esm-dirs lib/ src/ --root-dir src/", "test:postcss": "impunity --entry-point scripts/postcss/tests/css-compile-variables.test.js scripts/postcss/tests/css-url-to-variables.test.js", "start": "vite --port 3000", - "build": "vite build", + "build": "vite build && ./scripts/cleanup.sh", "build:sdk": "./scripts/sdk/build.sh" }, "repository": { diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh new file mode 100755 index 00000000..cdad04eb --- /dev/null +++ b/scripts/cleanup.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# Remove icons created in .tmp +rm -rf .tmp From cc2c74fdff151683cda8b2287de875be4ef620e0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 19 Apr 2022 12:16:02 +0530 Subject: [PATCH 015/174] Generate theme summary on build --- .../build-plugins/rollup-plugin-build-themes.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index e7a2bb2b..01969b1a 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -231,6 +231,7 @@ module.exports = function buildThemes(options) { generateBundle(_, bundle) { const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); + const themeSummary = {}; for (const [location, chunkArray] of chunkMap) { const manifest = require(`${location}/manifest.json`); const compiledVariables = options.compiledVariables.get(location); @@ -249,6 +250,22 @@ module.exports = function buildThemes(options) { source: JSON.stringify(manifest), }); } + /** + * Generate a mapping from theme name to asset hashed location of said theme in build output. + * This can be used to enumerate themes during runtime. + */ + for (const [, chunkArray] of chunkMap) { + chunkArray.forEach((chunk) => { + const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); + const assetHashedFileName = assetMap.get(chunk.fileName).fileName; + themeSummary[`${name}-${variant}`] = assetHashedFileName; + }); + } + this.emitFile({ + type: "asset", + name: "theme-summary.json", + source: JSON.stringify(themeSummary), + }); }, } } From daae7442bb0175edf02dc6bc3e1030e4d4701dee Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 14:29:31 +0530 Subject: [PATCH 016/174] Create theme chooser --- .../rollup-plugin-build-themes.js | 14 ++++++++++ .../session/settings/SettingsViewModel.js | 8 ++++++ src/platform/web/Platform.js | 27 +++++++++++++++++++ .../web/ui/session/settings/SettingsView.js | 9 +++++++ 4 files changed, 58 insertions(+) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 01969b1a..805590f4 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -31,6 +31,17 @@ function appendVariablesToCSS(variables, cssSource) { return cssSource + getRootSectionWithVariables(variables); } +function addThemesToConfig(bundle, themeSummary) { + for (const [fileName, info] of Object.entries(bundle)) { + if (fileName === "assets/config.json") { + const source = new TextDecoder().decode(info.source); + const config = JSON.parse(source); + config["themes"] = themeSummary; + info.source = new TextEncoder().encode(JSON.stringify(config)); + } + } +} + function parseBundle(bundle) { const chunkMap = new Map(); const assetMap = new Map(); @@ -215,6 +226,7 @@ module.exports = function buildThemes(options) { type: "text/css", media: "(prefers-color-scheme: dark)", href: `./${darkThemeLocation}`, + class: "default-theme", } }, { @@ -224,6 +236,7 @@ module.exports = function buildThemes(options) { type: "text/css", media: "(prefers-color-scheme: light)", href: `./${lightThemeLocation}`, + class: "default-theme", } }, ]; @@ -261,6 +274,7 @@ module.exports = function buildThemes(options) { themeSummary[`${name}-${variant}`] = assetHashedFileName; }); } + addThemesToConfig(bundle, themeSummary); this.emitFile({ type: "asset", name: "theme-summary.json", diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 7464a659..649983e5 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -127,6 +127,14 @@ export class SettingsViewModel extends ViewModel { return this._formatBytes(this._estimate?.usage); } + get themes() { + return this.platform.themes; + } + + setTheme(name) { + this.platform.setTheme(name); + } + _formatBytes(n) { if (typeof n === "number") { return Math.round(n / (1024 * 1024)).toFixed(1) + " MB"; diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 7d66301d..6ed4509d 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -307,6 +307,33 @@ export class Platform { return DEFINE_VERSION; } + get themes() { + return Object.keys(this.config["themes"]); + } + + setTheme(themeName) { + const themeLocation = this.config["themes"][themeName]; + if (!themeLocation) { + throw new Error(`Cannot find theme location for theme "${themeName}"!`); + } + this._replaceStylesheet(themeLocation); + } + + _replaceStylesheet(newPath) { + // remove default theme + const defaultStylesheets = document.getElementsByClassName("default-theme"); + for (const tag of defaultStylesheets) { + tag.remove(); + } + // add new theme + const head = document.querySelector("head"); + const styleTag = document.createElement("link"); + styleTag.href = `./${newPath}`; + styleTag.rel = "stylesheet"; + styleTag.type = "text/css"; + head.appendChild(styleTag); + } + dispose() { this._disposables.dispose(); } diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 93e44307..b2c78f58 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,6 +97,7 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), + row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)), ); settingNodes.push( t.h3("Application"), @@ -135,4 +136,12 @@ export class SettingsView extends TemplateView { vm.i18n`no resizing`; })]; } + + _themeOptions(t, vm) { + const optionTags = []; + for (const name of vm.themes) { + optionTags.push(t.option({value: name}, name)); + } + return t.select({onChange: (e) => vm.setTheme(e.target.value)}, optionTags); + } } From ecb83bb277d036767eb3ded9053a2979454dd3b4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 14:47:16 +0530 Subject: [PATCH 017/174] Store and load theme from setting --- src/domain/session/settings/SettingsViewModel.js | 1 + src/platform/web/Platform.js | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 649983e5..0bdb1355 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -133,6 +133,7 @@ export class SettingsViewModel extends ViewModel { setTheme(name) { this.platform.setTheme(name); + this.platform.settingsStorage.setString("theme", name); } _formatBytes(n) { diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 6ed4509d..d1b96909 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -178,6 +178,14 @@ export class Platform { this._serviceWorkerHandler, this._config.push ); + await this._loadThemeFromSetting(); + } + + async _loadThemeFromSetting() { + const themeName = await this.settingsStorage.getString("theme"); + if (themeName) { + this.setTheme(themeName); + } } _createLogger(isDevelopment) { From c611d3f85c2ceab9967d4d8109df100b07ff4a41 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 15:56:45 +0530 Subject: [PATCH 018/174] Select current theme in dropdown --- .../build-plugins/rollup-plugin-build-themes.js | 9 ++++++--- .../session/settings/SettingsViewModel.js | 6 ++++++ src/platform/web/Platform.js | 17 +++++++++++++++++ .../web/ui/session/settings/SettingsView.js | 8 +++++--- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 805590f4..f36db855 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -31,12 +31,13 @@ function appendVariablesToCSS(variables, cssSource) { return cssSource + getRootSectionWithVariables(variables); } -function addThemesToConfig(bundle, themeSummary) { +function addThemesToConfig(bundle, themeSummary, defaultThemes) { for (const [fileName, info] of Object.entries(bundle)) { if (fileName === "assets/config.json") { const source = new TextDecoder().decode(info.source); const config = JSON.parse(source); config["themes"] = themeSummary; + config["defaultTheme"] = defaultThemes; info.source = new TextEncoder().encode(JSON.stringify(config)); } } @@ -83,7 +84,7 @@ function parseBundle(bundle) { } module.exports = function buildThemes(options) { - let manifest, variants, defaultDark, defaultLight; + let manifest, variants, defaultDark, defaultLight,defaultThemes = {}; let isDevelopment = false; const virtualModuleId = '@theme/' const resolvedVirtualModuleId = '\0' + virtualModuleId; @@ -110,9 +111,11 @@ module.exports = function buildThemes(options) { // This is the default theme, stash the file name for later if (details.dark) { defaultDark = fileName; + defaultThemes["dark"] = `${name}-${variant}`; } else { defaultLight = fileName; + defaultThemes["light"] = `${name}-${variant}`; } } // emit the css as built theme bundle @@ -274,7 +277,7 @@ module.exports = function buildThemes(options) { themeSummary[`${name}-${variant}`] = assetHashedFileName; }); } - addThemesToConfig(bundle, themeSummary); + addThemesToConfig(bundle, themeSummary, defaultThemes); this.emitFile({ type: "asset", name: "theme-summary.json", diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 0bdb1355..083c209e 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -50,6 +50,7 @@ export class SettingsViewModel extends ViewModel { this.minSentImageSizeLimit = 400; this.maxSentImageSizeLimit = 4000; this.pushNotifications = new PushNotificationStatus(); + this._activeTheme = undefined; } get _session() { @@ -76,6 +77,7 @@ export class SettingsViewModel extends ViewModel { this.sentImageSizeLimit = await this.platform.settingsStorage.getInt("sentImageSizeLimit"); this.pushNotifications.supported = await this.platform.notificationService.supportsPush(); this.pushNotifications.enabled = await this._session.arePushNotificationsEnabled(); + this._activeTheme = await this.platform.getActiveTheme(); this.emitChange(""); } @@ -131,6 +133,10 @@ export class SettingsViewModel extends ViewModel { return this.platform.themes; } + get activeTheme() { + return this._activeTheme; + } + setTheme(name) { this.platform.setTheme(name); this.platform.settingsStorage.setString("theme", name); diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index d1b96909..415081ba 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -319,6 +319,23 @@ export class Platform { return Object.keys(this.config["themes"]); } + async getActiveTheme() { + // check if theme is set via settings + let theme = await this.settingsStorage.getString("theme"); + if (theme) { + return theme; + } + // return default theme + if (window.matchMedia) { + if (window.matchMedia("(prefers-color-scheme: dark)")) { + return this.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)")) { + return this.config["defaultTheme"].light; + } + } + return undefined; + } + setTheme(themeName) { const themeLocation = this.config["themes"][themeName]; if (!themeLocation) { diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index b2c78f58..3e62bba6 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,7 +97,9 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), - row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)), + t.map(vm => vm.activeTheme, (theme, t) => { + return row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)); + }), ); settingNodes.push( t.h3("Application"), @@ -137,10 +139,10 @@ export class SettingsView extends TemplateView { })]; } - _themeOptions(t, vm) { + _themeOptions(t, vm, activeTheme) { const optionTags = []; for (const name of vm.themes) { - optionTags.push(t.option({value: name}, name)); + optionTags.push(t.option({value: name, selected: name === activeTheme}, name)); } return t.select({onChange: (e) => vm.setTheme(e.target.value)}, optionTags); } From 12a70469eb963b4ea01f442a603fce8282240afa Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 16:01:02 +0530 Subject: [PATCH 019/174] Fix formatting --- scripts/build-plugins/rollup-plugin-build-themes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index f36db855..9776fb92 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -84,7 +84,7 @@ function parseBundle(bundle) { } module.exports = function buildThemes(options) { - let manifest, variants, defaultDark, defaultLight,defaultThemes = {}; + let manifest, variants, defaultDark, defaultLight, defaultThemes = {}; let isDevelopment = false; const virtualModuleId = '@theme/' const resolvedVirtualModuleId = '\0' + virtualModuleId; From af9cbd727f017ffd6d3b420938ec0023f5d535cc Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 16:33:31 +0530 Subject: [PATCH 020/174] Remove existing stylesheets when changing themes --- scripts/build-plugins/rollup-plugin-build-themes.js | 4 ++-- src/platform/web/Platform.js | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 9776fb92..f429bc80 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -229,7 +229,7 @@ module.exports = function buildThemes(options) { type: "text/css", media: "(prefers-color-scheme: dark)", href: `./${darkThemeLocation}`, - class: "default-theme", + class: "theme", } }, { @@ -239,7 +239,7 @@ module.exports = function buildThemes(options) { type: "text/css", media: "(prefers-color-scheme: light)", href: `./${lightThemeLocation}`, - class: "default-theme", + class: "theme", } }, ]; diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 415081ba..24480f59 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -345,17 +345,15 @@ export class Platform { } _replaceStylesheet(newPath) { - // remove default theme - const defaultStylesheets = document.getElementsByClassName("default-theme"); - for (const tag of defaultStylesheets) { - tag.remove(); - } - // add new theme const head = document.querySelector("head"); + // remove default theme + document.querySelectorAll(".theme").forEach(e => e.remove()); + // add new theme const styleTag = document.createElement("link"); styleTag.href = `./${newPath}`; styleTag.rel = "stylesheet"; styleTag.type = "text/css"; + styleTag.className = "theme"; head.appendChild(styleTag); } From bb3368959ffd36782261a3dc1cea64e2f14ee6c8 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 17:24:58 +0530 Subject: [PATCH 021/174] Use sh instead of bash --- scripts/cleanup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh index cdad04eb..6917af5e 100755 --- a/scripts/cleanup.sh +++ b/scripts/cleanup.sh @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh # Remove icons created in .tmp rm -rf .tmp From c39f0d2efb5a39ee2b34998cb996a12dd82e845e Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 17:49:16 +0530 Subject: [PATCH 022/174] Don't show theme chooser on dev --- src/domain/session/settings/SettingsViewModel.js | 4 +++- src/platform/web/ui/session/settings/SettingsView.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 083c209e..9d2a4f3e 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -77,7 +77,9 @@ export class SettingsViewModel extends ViewModel { this.sentImageSizeLimit = await this.platform.settingsStorage.getInt("sentImageSizeLimit"); this.pushNotifications.supported = await this.platform.notificationService.supportsPush(); this.pushNotifications.enabled = await this._session.arePushNotificationsEnabled(); - this._activeTheme = await this.platform.getActiveTheme(); + if (!import.meta.env.DEV) { + this._activeTheme = await this.platform.getActiveTheme(); + } this.emitChange(""); } diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 3e62bba6..eef3bc64 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -93,12 +93,12 @@ export class SettingsView extends TemplateView { ]); }) ); - + settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), t.map(vm => vm.activeTheme, (theme, t) => { - return row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)); + return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)): null; }), ); settingNodes.push( From 5204fe5c99a6ad576c94718e9846351116e070e0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 10 May 2022 14:22:37 +0530 Subject: [PATCH 023/174] This emitFile is no longer needed --- scripts/build-plugins/rollup-plugin-build-themes.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index f429bc80..871e8fab 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -278,11 +278,6 @@ module.exports = function buildThemes(options) { }); } addThemesToConfig(bundle, themeSummary, defaultThemes); - this.emitFile({ - type: "asset", - name: "theme-summary.json", - source: JSON.stringify(themeSummary), - }); }, } } From e8a4ab5ecc37dcb6150fb7573da12a773ea2ac72 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 10 May 2022 16:58:06 +0530 Subject: [PATCH 024/174] built-asset must be a mapping A mapping from theme-name to location of css file --- scripts/build-plugins/rollup-plugin-build-themes.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 871e8fab..d3733e01 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -253,8 +253,13 @@ module.exports = function buildThemes(options) { const compiledVariables = options.compiledVariables.get(location); const derivedVariables = compiledVariables["derived-variables"]; const icon = compiledVariables["icon"]; + const builtAsset = {}; + for (const chunk of chunkArray) { + const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); + builtAsset[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; + } manifest.source = { - "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), + "built-asset": builtAsset, "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, "derived-variables": derivedVariables, "icon": icon From 855298bdaf7b8eaa7ae08d0dc154f524709df8f4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 12:40:32 +0530 Subject: [PATCH 025/174] Read from manifest --- .../rollup-plugin-build-themes.js | 24 +++++++------------ src/platform/web/Platform.js | 17 +++++++++++-- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index d3733e01..e9251224 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -31,12 +31,12 @@ function appendVariablesToCSS(variables, cssSource) { return cssSource + getRootSectionWithVariables(variables); } -function addThemesToConfig(bundle, themeSummary, defaultThemes) { +function addThemesToConfig(bundle, manifestLocations, defaultThemes) { for (const [fileName, info] of Object.entries(bundle)) { if (fileName === "assets/config.json") { const source = new TextDecoder().decode(info.source); const config = JSON.parse(source); - config["themes"] = themeSummary; + config["themeManifests"] = manifestLocations; config["defaultTheme"] = defaultThemes; info.source = new TextEncoder().encode(JSON.stringify(config)); } @@ -247,13 +247,17 @@ module.exports = function buildThemes(options) { generateBundle(_, bundle) { const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); - const themeSummary = {}; + const manifestLocations = []; for (const [location, chunkArray] of chunkMap) { const manifest = require(`${location}/manifest.json`); const compiledVariables = options.compiledVariables.get(location); const derivedVariables = compiledVariables["derived-variables"]; const icon = compiledVariables["icon"]; const builtAsset = {}; + /** + * Generate a mapping from theme name to asset hashed location of said theme in build output. + * This can be used to enumerate themes during runtime. + */ for (const chunk of chunkArray) { const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); builtAsset[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; @@ -265,24 +269,14 @@ module.exports = function buildThemes(options) { "icon": icon }; const name = `theme-${manifest.name}.json`; + manifestLocations.push(`assets/${name}`); this.emitFile({ type: "asset", name, source: JSON.stringify(manifest), }); } - /** - * Generate a mapping from theme name to asset hashed location of said theme in build output. - * This can be used to enumerate themes during runtime. - */ - for (const [, chunkArray] of chunkMap) { - chunkArray.forEach((chunk) => { - const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); - const assetHashedFileName = assetMap.get(chunk.fileName).fileName; - themeSummary[`${name}-${variant}`] = assetHashedFileName; - }); - } - addThemesToConfig(bundle, themeSummary, defaultThemes); + addThemesToConfig(bundle, manifestLocations, defaultThemes); }, } } diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 24480f59..7b803e24 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -164,6 +164,8 @@ export class Platform { this._disposables = new Disposables(); this._olmPromise = undefined; this._workerPromise = undefined; + // Mapping from theme-name to asset hashed location of css file + this._themeMapping = {}; } async init() { @@ -178,9 +180,20 @@ export class Platform { this._serviceWorkerHandler, this._config.push ); + this._themeMapping = await this._createThemeMappingFromManifests(); await this._loadThemeFromSetting(); } + async _createThemeMappingFromManifests() { + const mapping = {}; + const manifests = this.config["themeManifests"]; + for (const manifestLocation of manifests) { + const {body}= await this.request(manifestLocation, {method: "GET", format: "json", cache: true}).response(); + Object.assign(mapping, body["source"]["built-asset"]); + } + return mapping; + } + async _loadThemeFromSetting() { const themeName = await this.settingsStorage.getString("theme"); if (themeName) { @@ -316,7 +329,7 @@ export class Platform { } get themes() { - return Object.keys(this.config["themes"]); + return Object.keys(this._themeMapping); } async getActiveTheme() { @@ -337,7 +350,7 @@ export class Platform { } setTheme(themeName) { - const themeLocation = this.config["themes"][themeName]; + const themeLocation = this._themeMapping[themeName]; if (!themeLocation) { throw new Error(`Cannot find theme location for theme "${themeName}"!`); } From 213f87378b5b9bd7e3fa573cf4c4e599ea974dfd Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 12:46:12 +0530 Subject: [PATCH 026/174] Use t.if instead of t.map --- src/platform/web/ui/session/settings/SettingsView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index eef3bc64..a6b3e363 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,7 +97,7 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), - t.map(vm => vm.activeTheme, (theme, t) => { + t.if(vm => vm.activeTheme, (theme, t) => { return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)): null; }), ); From 2761789f452eed2457b257037534076986a69f0a Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 14:58:14 +0530 Subject: [PATCH 027/174] Move theme code to separate file --- .../session/settings/SettingsViewModel.js | 7 +- src/platform/web/Platform.js | 55 ++------------ src/platform/web/ThemeLoader.ts | 76 +++++++++++++++++++ .../web/ui/session/settings/SettingsView.js | 7 +- 4 files changed, 90 insertions(+), 55 deletions(-) create mode 100644 src/platform/web/ThemeLoader.ts diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 9d2a4f3e..5c89236f 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -78,7 +78,7 @@ export class SettingsViewModel extends ViewModel { this.pushNotifications.supported = await this.platform.notificationService.supportsPush(); this.pushNotifications.enabled = await this._session.arePushNotificationsEnabled(); if (!import.meta.env.DEV) { - this._activeTheme = await this.platform.getActiveTheme(); + this._activeTheme = await this.platform.themeLoader.getActiveTheme(); } this.emitChange(""); } @@ -132,7 +132,7 @@ export class SettingsViewModel extends ViewModel { } get themes() { - return this.platform.themes; + return this.platform.themeLoader.themes; } get activeTheme() { @@ -140,8 +140,7 @@ export class SettingsViewModel extends ViewModel { } setTheme(name) { - this.platform.setTheme(name); - this.platform.settingsStorage.setString("theme", name); + this.platform.themeLoader.setTheme(name); } _formatBytes(n) { diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 7b803e24..2481d256 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -38,6 +38,7 @@ import {downloadInIframe} from "./dom/download.js"; import {Disposables} from "../../utils/Disposables"; import {parseHTML} from "./parsehtml.js"; import {handleAvatarError} from "./ui/avatar"; +import {ThemeLoader} from "./ThemeLoader"; function addScript(src) { return new Promise(function (resolve, reject) { @@ -164,8 +165,7 @@ export class Platform { this._disposables = new Disposables(); this._olmPromise = undefined; this._workerPromise = undefined; - // Mapping from theme-name to asset hashed location of css file - this._themeMapping = {}; + this._themeLoader = new ThemeLoader(this); } async init() { @@ -180,25 +180,9 @@ export class Platform { this._serviceWorkerHandler, this._config.push ); - this._themeMapping = await this._createThemeMappingFromManifests(); - await this._loadThemeFromSetting(); - } - - async _createThemeMappingFromManifests() { - const mapping = {}; const manifests = this.config["themeManifests"]; - for (const manifestLocation of manifests) { - const {body}= await this.request(manifestLocation, {method: "GET", format: "json", cache: true}).response(); - Object.assign(mapping, body["source"]["built-asset"]); - } - return mapping; - } - - async _loadThemeFromSetting() { - const themeName = await this.settingsStorage.getString("theme"); - if (themeName) { - this.setTheme(themeName); - } + await this._themeLoader.init(manifests); + await this._themeLoader.loadThemeFromSetting(); } _createLogger(isDevelopment) { @@ -328,36 +312,11 @@ export class Platform { return DEFINE_VERSION; } - get themes() { - return Object.keys(this._themeMapping); + get themeLoader() { + return this._themeLoader; } - async getActiveTheme() { - // check if theme is set via settings - let theme = await this.settingsStorage.getString("theme"); - if (theme) { - return theme; - } - // return default theme - if (window.matchMedia) { - if (window.matchMedia("(prefers-color-scheme: dark)")) { - return this.config["defaultTheme"].dark; - } else if (window.matchMedia("(prefers-color-scheme: light)")) { - return this.config["defaultTheme"].light; - } - } - return undefined; - } - - setTheme(themeName) { - const themeLocation = this._themeMapping[themeName]; - if (!themeLocation) { - throw new Error(`Cannot find theme location for theme "${themeName}"!`); - } - this._replaceStylesheet(themeLocation); - } - - _replaceStylesheet(newPath) { + replaceStylesheet(newPath) { const head = document.querySelector("head"); // remove default theme document.querySelectorAll(".theme").forEach(e => e.remove()); diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts new file mode 100644 index 00000000..6c81a4d6 --- /dev/null +++ b/src/platform/web/ThemeLoader.ts @@ -0,0 +1,76 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import type {Platform} from "./Platform.js"; + +export class ThemeLoader { + private _platform: Platform; + private _themeMapping: Record = {}; + + constructor(platform: Platform) { + this._platform = platform; + } + + async init(manifestLocations: Iterable>): Promise { + for (const manifestLocation of manifestLocations) { + const { body } = await this._platform + .request(manifestLocation, { + method: "GET", + format: "json", + cache: true, + }) + .response(); + Object.assign(this._themeMapping, body["source"]["built-asset"]); + } + } + + async loadThemeFromSetting() { + const themeName = await this._platform.settingsStorage.getString( "theme"); + if (themeName) { + this.setTheme(themeName); + } + } + + setTheme(themeName: string) { + const themeLocation = this._themeMapping[themeName]; + if (!themeLocation) { + throw new Error( `Cannot find theme location for theme "${themeName}"!`); + } + this._platform.replaceStylesheet(themeLocation); + this._platform.settingsStorage.setString("theme", themeName); + } + + get themes(): string[] { + return Object.keys(this._themeMapping); + } + + async getActiveTheme(): Promise { + // check if theme is set via settings + let theme = await this._platform.settingsStorage.getString("theme"); + if (theme) { + return theme; + } + // return default theme + if (window.matchMedia) { + if (window.matchMedia("(prefers-color-scheme: dark)")) { + return this._platform.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)")) { + return this._platform.config["defaultTheme"].light; + } + } + return undefined; + } +} diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index a6b3e363..69827c33 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,8 +97,8 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), - t.if(vm => vm.activeTheme, (theme, t) => { - return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)): null; + t.if(vm => vm.activeTheme, (t, vm) => { + return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)): null; }), ); settingNodes.push( @@ -139,7 +139,8 @@ export class SettingsView extends TemplateView { })]; } - _themeOptions(t, vm, activeTheme) { + _themeOptions(t, vm) { + const activeTheme = vm.activeTheme; const optionTags = []; for (const name of vm.themes) { optionTags.push(t.option({value: name, selected: name === activeTheme}, name)); From c26dc04b520c16f1e0de3c23df2da983ce8e0525 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 15:03:32 +0530 Subject: [PATCH 028/174] Fix type --- src/platform/web/ThemeLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 6c81a4d6..256aed54 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -24,7 +24,7 @@ export class ThemeLoader { this._platform = platform; } - async init(manifestLocations: Iterable>): Promise { + async init(manifestLocations: string[]): Promise { for (const manifestLocation of manifestLocations) { const { body } = await this._platform .request(manifestLocation, { From 174adc075573716d2c697b4ac8af55d144714c47 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 15:38:37 +0530 Subject: [PATCH 029/174] Move platform dependent code to Platform --- src/platform/web/Platform.js | 11 ++++++++++- src/platform/web/ThemeLoader.ts | 10 ++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 2481d256..e2628351 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -38,7 +38,7 @@ import {downloadInIframe} from "./dom/download.js"; import {Disposables} from "../../utils/Disposables"; import {parseHTML} from "./parsehtml.js"; import {handleAvatarError} from "./ui/avatar"; -import {ThemeLoader} from "./ThemeLoader"; +import {ThemeLoader, COLOR_SCHEME_PREFERENCE} from "./ThemeLoader"; function addScript(src) { return new Promise(function (resolve, reject) { @@ -316,6 +316,15 @@ export class Platform { return this._themeLoader; } + get preferredColorScheme() { + if (window.matchMedia("(prefers-color-scheme: dark)")) { + return COLOR_SCHEME_PREFERENCE.DARK; + } else if (window.matchMedia("(prefers-color-scheme: light)")) { + return COLOR_SCHEME_PREFERENCE.LIGHT; + } + return undefined; + } + replaceStylesheet(newPath) { const head = document.querySelector("head"); // remove default theme diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 256aed54..713c7a62 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -16,6 +16,8 @@ limitations under the License. import type {Platform} from "./Platform.js"; +export enum COLOR_SCHEME_PREFERENCE { DARK, LIGHT, } + export class ThemeLoader { private _platform: Platform; private _themeMapping: Record = {}; @@ -64,12 +66,12 @@ export class ThemeLoader { return theme; } // return default theme - if (window.matchMedia) { - if (window.matchMedia("(prefers-color-scheme: dark)")) { + const preference = this._platform.preferredColorScheme; + switch (preference) { + case COLOR_SCHEME_PREFERENCE.DARK: return this._platform.config["defaultTheme"].dark; - } else if (window.matchMedia("(prefers-color-scheme: light)")) { + case COLOR_SCHEME_PREFERENCE.LIGHT: return this._platform.config["defaultTheme"].light; - } } return undefined; } From cc88245933596f55c787b0bc028dbf33bb2518e4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 15:46:12 +0530 Subject: [PATCH 030/174] Create themeLoader only if not dev --- src/platform/web/Platform.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index e2628351..ed117b3d 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -165,7 +165,7 @@ export class Platform { this._disposables = new Disposables(); this._olmPromise = undefined; this._workerPromise = undefined; - this._themeLoader = new ThemeLoader(this); + this._themeLoader = import.meta.env.DEV? null: new ThemeLoader(this); } async init() { @@ -181,8 +181,8 @@ export class Platform { this._config.push ); const manifests = this.config["themeManifests"]; - await this._themeLoader.init(manifests); - await this._themeLoader.loadThemeFromSetting(); + await this._themeLoader?.init(manifests); + await this._themeLoader?.loadThemeFromSetting(); } _createLogger(isDevelopment) { From d5bc9f5d7d340c62d2fe7cf33d9fbb0f8219b57c Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Thu, 12 May 2022 12:48:34 +0530 Subject: [PATCH 031/174] Update src/platform/web/Platform.js Co-authored-by: Bruno Windels <274386+bwindels@users.noreply.github.com> --- src/platform/web/Platform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index ed117b3d..e222691b 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -317,7 +317,7 @@ export class Platform { } get preferredColorScheme() { - if (window.matchMedia("(prefers-color-scheme: dark)")) { + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { return COLOR_SCHEME_PREFERENCE.DARK; } else if (window.matchMedia("(prefers-color-scheme: light)")) { return COLOR_SCHEME_PREFERENCE.LIGHT; From 4231037345708677a85fdc4d43425098910eb623 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Thu, 12 May 2022 12:48:41 +0530 Subject: [PATCH 032/174] Update src/platform/web/Platform.js Co-authored-by: Bruno Windels <274386+bwindels@users.noreply.github.com> --- src/platform/web/Platform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index e222691b..5a921aaf 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -319,7 +319,7 @@ export class Platform { get preferredColorScheme() { if (window.matchMedia("(prefers-color-scheme: dark)").matches) { return COLOR_SCHEME_PREFERENCE.DARK; - } else if (window.matchMedia("(prefers-color-scheme: light)")) { + } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { return COLOR_SCHEME_PREFERENCE.LIGHT; } return undefined; From b3063447399812d22e7eddeaaee61223f352c219 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 12:55:08 +0530 Subject: [PATCH 033/174] Add explaining comment --- src/platform/web/ThemeLoader.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 713c7a62..ab799b0a 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -35,6 +35,11 @@ export class ThemeLoader { cache: true, }) .response(); + /* + After build has finished, the source section of each theme manifest + contains `built-asset` which is a mapping from the theme-name to the + location of the css file in build. + */ Object.assign(this._themeMapping, body["source"]["built-asset"]); } } From 654e83a5f98d18c5e5d1339be677e3b8e81fded2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 13:28:11 +0530 Subject: [PATCH 034/174] Remove method --- src/platform/web/Platform.js | 2 +- src/platform/web/ThemeLoader.ts | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 5a921aaf..662525d8 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -182,7 +182,7 @@ export class Platform { ); const manifests = this.config["themeManifests"]; await this._themeLoader?.init(manifests); - await this._themeLoader?.loadThemeFromSetting(); + this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme()); } _createLogger(isDevelopment) { diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index ab799b0a..34b3c7d0 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -44,13 +44,6 @@ export class ThemeLoader { } } - async loadThemeFromSetting() { - const themeName = await this._platform.settingsStorage.getString( "theme"); - if (themeName) { - this.setTheme(themeName); - } - } - setTheme(themeName: string) { const themeLocation = this._themeMapping[themeName]; if (!themeLocation) { From 0984aeb5708c3fe0cbbc925055ab8d2c42b0d3a1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 13:39:57 +0530 Subject: [PATCH 035/174] Move code to ThemeLoader --- src/platform/web/Platform.js | 9 --------- src/platform/web/ThemeLoader.ts | 10 ++++------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 662525d8..a142ace7 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -316,15 +316,6 @@ export class Platform { return this._themeLoader; } - get preferredColorScheme() { - if (window.matchMedia("(prefers-color-scheme: dark)").matches) { - return COLOR_SCHEME_PREFERENCE.DARK; - } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { - return COLOR_SCHEME_PREFERENCE.LIGHT; - } - return undefined; - } - replaceStylesheet(newPath) { const head = document.querySelector("head"); // remove default theme diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 34b3c7d0..4c0ec6d3 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -64,12 +64,10 @@ export class ThemeLoader { return theme; } // return default theme - const preference = this._platform.preferredColorScheme; - switch (preference) { - case COLOR_SCHEME_PREFERENCE.DARK: - return this._platform.config["defaultTheme"].dark; - case COLOR_SCHEME_PREFERENCE.LIGHT: - return this._platform.config["defaultTheme"].light; + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + return this._platform.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { + return this._platform.config["defaultTheme"].light; } return undefined; } From e63440527a99172b31b684e9f98551e171674342 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 13:43:19 +0530 Subject: [PATCH 036/174] Move condition to binding --- src/platform/web/ui/session/settings/SettingsView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 69827c33..dd7bbc03 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,8 +97,8 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), - t.if(vm => vm.activeTheme, (t, vm) => { - return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)): null; + t.if(vm => !import.meta.env.DEV && vm.activeTheme, (t, vm) => { + return row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)); }), ); settingNodes.push( From 4ddfd3b5086d2fcc247bc14cae8386a8302ef6a1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 14:31:28 +0530 Subject: [PATCH 037/174] built-asset --> built-assets --- scripts/build-plugins/rollup-plugin-build-themes.js | 6 +++--- src/platform/web/ThemeLoader.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index e9251224..c45c5aaa 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -253,17 +253,17 @@ module.exports = function buildThemes(options) { const compiledVariables = options.compiledVariables.get(location); const derivedVariables = compiledVariables["derived-variables"]; const icon = compiledVariables["icon"]; - const builtAsset = {}; + const builtAssets = {}; /** * Generate a mapping from theme name to asset hashed location of said theme in build output. * This can be used to enumerate themes during runtime. */ for (const chunk of chunkArray) { const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); - builtAsset[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; + builtAssets[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; } manifest.source = { - "built-asset": builtAsset, + "built-assets": builtAssets, "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, "derived-variables": derivedVariables, "icon": icon diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 4c0ec6d3..0234a04a 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -37,10 +37,10 @@ export class ThemeLoader { .response(); /* After build has finished, the source section of each theme manifest - contains `built-asset` which is a mapping from the theme-name to the + contains `built-assets` which is a mapping from the theme-name to the location of the css file in build. */ - Object.assign(this._themeMapping, body["source"]["built-asset"]); + Object.assign(this._themeMapping, body["source"]["built-assets"]); } } From 9ba153439026535cf28b3301537fbf9c442e608d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 16:03:06 +0530 Subject: [PATCH 038/174] Remove unused import --- src/platform/web/Platform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index a142ace7..02990ea0 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -38,7 +38,7 @@ import {downloadInIframe} from "./dom/download.js"; import {Disposables} from "../../utils/Disposables"; import {parseHTML} from "./parsehtml.js"; import {handleAvatarError} from "./ui/avatar"; -import {ThemeLoader, COLOR_SCHEME_PREFERENCE} from "./ThemeLoader"; +import {ThemeLoader} from "./ThemeLoader"; function addScript(src) { return new Promise(function (resolve, reject) { From 34e8b609174822fab062e1773bae98f0ede29f89 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 16:02:03 +0530 Subject: [PATCH 039/174] Create config.json in root --- .../build-plugins/rollup-plugin-build-themes.js | 2 +- vite.config.js | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index c45c5aaa..3cb9ed0c 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -33,7 +33,7 @@ function appendVariablesToCSS(variables, cssSource) { function addThemesToConfig(bundle, manifestLocations, defaultThemes) { for (const [fileName, info] of Object.entries(bundle)) { - if (fileName === "assets/config.json") { + if (fileName === "config.json") { const source = new TextDecoder().decode(info.source); const config = JSON.parse(source); config["themeManifests"] = manifestLocations; diff --git a/vite.config.js b/vite.config.js index a44d0917..2e4895d2 100644 --- a/vite.config.js +++ b/vite.config.js @@ -16,11 +16,17 @@ export default defineConfig(({mode}) => { sourcemap: true, rollupOptions: { output: { - assetFileNames: (asset) => - asset.name.includes("config.json") || - asset.name.match(/theme-.+\.json/) - ? "assets/[name][extname]" - : "assets/[name].[hash][extname]", + assetFileNames: (asset) => { + if (asset.name.includes("config.json")) { + return "[name][extname]"; + } + else if (asset.name.match(/theme-.+\.json/)) { + return "assets/[name][extname]"; + } + else { + return "assets/[name].[hash][extname]"; + } + } }, }, }, From b2d787b96c4c5fcf41d912b94389faeb6ae0f250 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Tue, 17 May 2022 15:55:15 +0200 Subject: [PATCH 040/174] fix wrong extension in import --- src/matrix/room/Room.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index 796474d3..38982786 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -23,7 +23,7 @@ import {WrappedError} from "../error.js" import {Heroes} from "./members/Heroes.js"; import {AttachmentUpload} from "./AttachmentUpload.js"; import {DecryptionSource} from "../e2ee/common.js"; -import {iterateResponseStateEvents} from "./common.js"; +import {iterateResponseStateEvents} from "./common"; import {PowerLevels, EVENT_TYPE as POWERLEVELS_EVENT_TYPE } from "./PowerLevels.js"; const EVENT_ENCRYPTED_TYPE = "m.room.encrypted"; From b725269c7a6ea62cb0a1e2aaec90c9b8b54f7001 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 18 May 2022 00:21:56 -0500 Subject: [PATCH 041/174] Clean up index.html in the right spot --- scripts/sdk/build.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index 992d2efc..5e1632d3 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -16,10 +16,10 @@ mkdir target/paths # this doesn't work, the ?url imports need to be in the consuming project, so disable for now # ./scripts/sdk/transform-paths.js ./src/platform/web/sdk/paths/vite.js ./target/paths/vite.js cp doc/SDK.md target/README.md -pushd target -pushd asset-build/assets +pushd target/asset-build +rm index.html +popd +pushd target/asset-build/assets # Remove all `*.wasm` and `*.js` files except for `main.js` rm !(main).js *.wasm popd -rm index.html -popd From 1b22a48b5415ddb8803c1d1200d2d201259231a4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 14:23:41 +0530 Subject: [PATCH 042/174] Treat theme-manifests the same way as config --- src/platform/web/sw.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index a9a92979..ce83e8c2 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -95,8 +95,8 @@ let pendingFetchAbortController = new AbortController(); async function handleRequest(request) { try { - if (request.url.includes("config.json")) { - return handleConfigRequest(request); + if (request.url.includes("config.json") || /theme-.+\.json/.test(request.url)) { + return handleSpecialRequest(request); } const url = new URL(request.url); // rewrite / to /index.html so it hits the cache @@ -123,9 +123,13 @@ async function handleRequest(request) { } } -async function handleConfigRequest(request) { +/** + * For some files (config.json and theme manifests) we satisfy the request from cache, + * but at the same time we refresh the cache with up-to-date content of the file + */ +async function handleSpecialRequest(request) { let response = await readCache(request); - const networkResponsePromise = fetchAndUpdateConfig(request); + const networkResponsePromise = fetchAndUpdateCache(request); if (response) { return response; } else { @@ -133,7 +137,7 @@ async function handleConfigRequest(request) { } } -async function fetchAndUpdateConfig(request) { +async function fetchAndUpdateCache(request) { const response = await fetch(request, { signal: pendingFetchAbortController.signal, headers: { From 660a08db3eb967b3adb9fc96549bef27ba989359 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 14:41:52 +0530 Subject: [PATCH 043/174] Give a better name --- src/platform/web/sw.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index ce83e8c2..6b07f42e 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -96,7 +96,7 @@ let pendingFetchAbortController = new AbortController(); async function handleRequest(request) { try { if (request.url.includes("config.json") || /theme-.+\.json/.test(request.url)) { - return handleSpecialRequest(request); + return handleStaleWhileRevalidateRequest(request); } const url = new URL(request.url); // rewrite / to /index.html so it hits the cache @@ -124,10 +124,10 @@ async function handleRequest(request) { } /** - * For some files (config.json and theme manifests) we satisfy the request from cache, - * but at the same time we refresh the cache with up-to-date content of the file + * Stale-while-revalidate caching for certain files + * see https://developer.chrome.com/docs/workbox/caching-strategies-overview/#stale-while-revalidate */ -async function handleSpecialRequest(request) { +async function handleStaleWhileRevalidateRequest(request) { let response = await readCache(request); const networkResponsePromise = fetchAndUpdateCache(request); if (response) { From 7426d17e33784baaab4daf219374163d37712f89 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 15:42:49 +0530 Subject: [PATCH 044/174] Precache config and theme manifest --- vite.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite.config.js b/vite.config.js index a44d0917..362f8f81 100644 --- a/vite.config.js +++ b/vite.config.js @@ -37,7 +37,7 @@ export default defineConfig(({mode}) => { // important this comes before service worker // otherwise the manifest and the icons it refers to won't be cached injectWebManifest("assets/manifest.json"), - injectServiceWorker("./src/platform/web/sw.js", ["index.html"], { + injectServiceWorker("./src/platform/web/sw.js", ["index.html", "assets/config.json", "assets/theme-element.json"], { // placeholders to replace at end of build by chunk name index: { DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH, From 7952a34d64d43cbd56ec789909ee76a1063efc51 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 16:09:09 +0530 Subject: [PATCH 045/174] Add logging --- src/platform/web/Platform.js | 28 +++++++------ src/platform/web/ThemeLoader.ts | 72 ++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 02990ea0..25439ad0 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -169,20 +169,22 @@ export class Platform { } async init() { - if (!this._config) { - if (!this._configURL) { - throw new Error("Neither config nor configURL was provided!"); + await this.logger.run("Platform init", async () => { + if (!this._config) { + if (!this._configURL) { + throw new Error("Neither config nor configURL was provided!"); + } + const {body}= await this.request(this._configURL, {method: "GET", format: "json", cache: true}).response(); + this._config = body; } - const {body}= await this.request(this._configURL, {method: "GET", format: "json", cache: true}).response(); - this._config = body; - } - this.notificationService = new NotificationService( - this._serviceWorkerHandler, - this._config.push - ); - const manifests = this.config["themeManifests"]; - await this._themeLoader?.init(manifests); - this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme()); + this.notificationService = new NotificationService( + this._serviceWorkerHandler, + this._config.push + ); + const manifests = this.config["themeManifests"]; + await this._themeLoader?.init(manifests); + this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme()); + }); } _createLogger(isDevelopment) { diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 0234a04a..5d93ad68 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -27,30 +27,34 @@ export class ThemeLoader { } async init(manifestLocations: string[]): Promise { - for (const manifestLocation of manifestLocations) { - const { body } = await this._platform - .request(manifestLocation, { - method: "GET", - format: "json", - cache: true, - }) - .response(); - /* - After build has finished, the source section of each theme manifest - contains `built-assets` which is a mapping from the theme-name to the - location of the css file in build. - */ - Object.assign(this._themeMapping, body["source"]["built-assets"]); - } + await this._platform.logger.run("ThemeLoader.init", async () => { + for (const manifestLocation of manifestLocations) { + const { body } = await this._platform + .request(manifestLocation, { + method: "GET", + format: "json", + cache: true, + }) + .response(); + /* + After build has finished, the source section of each theme manifest + contains `built-assets` which is a mapping from the theme-name to the + location of the css file in build. + */ + Object.assign(this._themeMapping, body["source"]["built-assets"]); + } + }); } setTheme(themeName: string) { - const themeLocation = this._themeMapping[themeName]; - if (!themeLocation) { - throw new Error( `Cannot find theme location for theme "${themeName}"!`); - } - this._platform.replaceStylesheet(themeLocation); - this._platform.settingsStorage.setString("theme", themeName); + this._platform.logger.run("ThemeLoader.setTheme", () => { + const themeLocation = this._themeMapping[themeName]; + if (!themeLocation) { + throw new Error( `Cannot find theme location for theme "${themeName}"!`); + } + this._platform.replaceStylesheet(themeLocation); + this._platform.settingsStorage.setString("theme", themeName); + }); } get themes(): string[] { @@ -58,17 +62,19 @@ export class ThemeLoader { } async getActiveTheme(): Promise { - // check if theme is set via settings - let theme = await this._platform.settingsStorage.getString("theme"); - if (theme) { - return theme; - } - // return default theme - if (window.matchMedia("(prefers-color-scheme: dark)").matches) { - return this._platform.config["defaultTheme"].dark; - } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { - return this._platform.config["defaultTheme"].light; - } - return undefined; + return await this._platform.logger.run("ThemeLoader.getActiveTheme", async () => { + // check if theme is set via settings + let theme = await this._platform.settingsStorage.getString("theme"); + if (theme) { + return theme; + } + // return default theme + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + return this._platform.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { + return this._platform.config["defaultTheme"].light; + } + return undefined; + }); } } From 683ffa9ed376ae3d5197c6dda5789db2588b33dc Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 17:31:17 +0530 Subject: [PATCH 046/174] injectServiceWorker plugin should accept callback --- scripts/build-plugins/service-worker.js | 3 ++- vite.config.js | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/build-plugins/service-worker.js b/scripts/build-plugins/service-worker.js index 805f6000..85619545 100644 --- a/scripts/build-plugins/service-worker.js +++ b/scripts/build-plugins/service-worker.js @@ -8,7 +8,7 @@ function contentHash(str) { return hasher.digest(); } -function injectServiceWorker(swFile, otherUnhashedFiles, placeholdersPerChunk) { +function injectServiceWorker(swFile, findUnhashedFileNamesFromBundle, placeholdersPerChunk) { const swName = path.basename(swFile); let root; let version; @@ -31,6 +31,7 @@ function injectServiceWorker(swFile, otherUnhashedFiles, placeholdersPerChunk) { logger = config.logger; }, generateBundle: async function(options, bundle) { + const otherUnhashedFiles = findUnhashedFileNamesFromBundle(bundle); const unhashedFilenames = [swName].concat(otherUnhashedFiles); const unhashedFileContentMap = unhashedFilenames.reduce((map, fileName) => { const chunkOrAsset = bundle[fileName]; diff --git a/vite.config.js b/vite.config.js index 362f8f81..53a7f452 100644 --- a/vite.config.js +++ b/vite.config.js @@ -37,7 +37,7 @@ export default defineConfig(({mode}) => { // important this comes before service worker // otherwise the manifest and the icons it refers to won't be cached injectWebManifest("assets/manifest.json"), - injectServiceWorker("./src/platform/web/sw.js", ["index.html", "assets/config.json", "assets/theme-element.json"], { + injectServiceWorker("./src/platform/web/sw.js", findUnhashedFileNamesFromBundle, { // placeholders to replace at end of build by chunk name index: { DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH, @@ -48,3 +48,16 @@ export default defineConfig(({mode}) => { define: definePlaceholders, }); }); + +function findUnhashedFileNamesFromBundle(bundle) { + const names = ["index.html"]; + for (const fileName of Object.keys(bundle)) { + if (fileName.includes("config.json")) { + names.push(fileName); + } + if (/theme-.+\.json/.test(fileName)) { + names.push(fileName); + } + } + return names; +} From a550788788b16aed4a7c29dfeace5800355eb40c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 18:56:28 +0530 Subject: [PATCH 047/174] Remove some logging + use wrapOrRun --- src/platform/web/Platform.js | 4 +- src/platform/web/ThemeLoader.ts | 65 +++++++++++++++------------------ 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 25439ad0..2a691580 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -169,7 +169,7 @@ export class Platform { } async init() { - await this.logger.run("Platform init", async () => { + await this.logger.run("Platform init", async (log) => { if (!this._config) { if (!this._configURL) { throw new Error("Neither config nor configURL was provided!"); @@ -183,7 +183,7 @@ export class Platform { ); const manifests = this.config["themeManifests"]; await this._themeLoader?.init(manifests); - this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme()); + this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme(), log); }); } diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 5d93ad68..72865279 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -14,10 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +import type {ILogItem} from "../../logging/types.js"; import type {Platform} from "./Platform.js"; -export enum COLOR_SCHEME_PREFERENCE { DARK, LIGHT, } - export class ThemeLoader { private _platform: Platform; private _themeMapping: Record = {}; @@ -27,27 +26,25 @@ export class ThemeLoader { } async init(manifestLocations: string[]): Promise { - await this._platform.logger.run("ThemeLoader.init", async () => { - for (const manifestLocation of manifestLocations) { - const { body } = await this._platform - .request(manifestLocation, { - method: "GET", - format: "json", - cache: true, - }) - .response(); - /* - After build has finished, the source section of each theme manifest - contains `built-assets` which is a mapping from the theme-name to the - location of the css file in build. - */ - Object.assign(this._themeMapping, body["source"]["built-assets"]); - } - }); + for (const manifestLocation of manifestLocations) { + const { body } = await this._platform + .request(manifestLocation, { + method: "GET", + format: "json", + cache: true, + }) + .response(); + /* + After build has finished, the source section of each theme manifest + contains `built-assets` which is a mapping from the theme-name to the + location of the css file in build. + */ + Object.assign(this._themeMapping, body["source"]["built-assets"]); + } } - setTheme(themeName: string) { - this._platform.logger.run("ThemeLoader.setTheme", () => { + setTheme(themeName: string, log?: ILogItem) { + this._platform.logger.wrapOrRun(log, "setTheme", () => { const themeLocation = this._themeMapping[themeName]; if (!themeLocation) { throw new Error( `Cannot find theme location for theme "${themeName}"!`); @@ -62,19 +59,17 @@ export class ThemeLoader { } async getActiveTheme(): Promise { - return await this._platform.logger.run("ThemeLoader.getActiveTheme", async () => { - // check if theme is set via settings - let theme = await this._platform.settingsStorage.getString("theme"); - if (theme) { - return theme; - } - // return default theme - if (window.matchMedia("(prefers-color-scheme: dark)").matches) { - return this._platform.config["defaultTheme"].dark; - } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { - return this._platform.config["defaultTheme"].light; - } - return undefined; - }); + // check if theme is set via settings + let theme = await this._platform.settingsStorage.getString("theme"); + if (theme) { + return theme; + } + // return default theme + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + return this._platform.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { + return this._platform.config["defaultTheme"].light; + } + return undefined; } } From 03ab1ee2c7a48721e763f8b301c162b6625940b8 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 17:48:03 +0200 Subject: [PATCH 048/174] log theme being loaded --- src/platform/web/ThemeLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 72865279..d9aaabb6 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -44,7 +44,7 @@ export class ThemeLoader { } setTheme(themeName: string, log?: ILogItem) { - this._platform.logger.wrapOrRun(log, "setTheme", () => { + this._platform.logger.wrapOrRun(log, {l: "change theme", id: themeName}, () => { const themeLocation = this._themeMapping[themeName]; if (!themeLocation) { throw new Error( `Cannot find theme location for theme "${themeName}"!`); From 7a197c0a1a4319b329ec025fb5fdea23a7950c6a Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 20:44:04 +0200 Subject: [PATCH 049/174] add deployment instruction now that we have a config file --- README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ee25ae6..431551ee 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,32 @@ Hydrogen's goals are: - It is a standalone webapp, but can also be easily embedded into an existing website/webapp to add chat capabilities. - Loading (unused) parts of the application after initial page load should be supported +For embedded usage, see the [SDK instructions](doc/SDK.md). + If you find this interesting, come and discuss on [`#hydrogen:matrix.org`](https://matrix.to/#/#hydrogen:matrix.org). # How to use -Hydrogen is deployed to [hydrogen.element.io](https://hydrogen.element.io). You can run it locally `yarn install` (only the first time) and `yarn start` in the terminal, and point your browser to `http://localhost:3000`. If you prefer, you can also [use docker](doc/docker.md). +Hydrogen is deployed to [hydrogen.element.io](https://hydrogen.element.io). You can also deploy Hydrogen on your own web server: -Hydrogen uses symbolic links in the codebase, so if you are on Windows, have a look at [making git & symlinks work](https://github.com/git-for-windows/git/wiki/Symbolic-Links) there. + 1. Download the [latest release package](https://github.com/vector-im/hydrogen-web/releases). + 1. Extract the package to the public directory of your web server. + 1. If this is your first deploy: + 1. copy `config.sample.json` to `config.json` and if needed, make any modifications. + 1. Disable caching entirely on the server (they will still be cached client-side by the service worker) for: + - `index.html` + - `sw.js` + - `config.json` + - All theme manifests referenced in the `themeManifests` of `config.json`, these files are typically called `theme-{name}.json`. + +## Set up a dev environment + +You can run Hydrogen locally by the following commands in the terminal: + + - `yarn install` (only the first time) + - `yarn start` in the terminal + +Now point your browser to `http://localhost:3000`. If you prefer, you can also [use docker](doc/docker.md). # FAQ From f21e10327059469e4a96cd1e7da8b2c640e80951 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 20:46:38 +0200 Subject: [PATCH 050/174] add newlines to config file when rewriting with theme stuff --- scripts/build-plugins/rollup-plugin-build-themes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 3cb9ed0c..da2db73b 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -38,7 +38,7 @@ function addThemesToConfig(bundle, manifestLocations, defaultThemes) { const config = JSON.parse(source); config["themeManifests"] = manifestLocations; config["defaultTheme"] = defaultThemes; - info.source = new TextEncoder().encode(JSON.stringify(config)); + info.source = new TextEncoder().encode(JSON.stringify(config, undefined, 2)); } } } From 7b0591be46cb041437ec8f7f44f7008881396893 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 20:51:50 +0200 Subject: [PATCH 051/174] explain that push section of config usually doesn't need to be touched --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 431551ee..1a457741 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Hydrogen is deployed to [hydrogen.element.io](https://hydrogen.element.io). You 1. Download the [latest release package](https://github.com/vector-im/hydrogen-web/releases). 1. Extract the package to the public directory of your web server. 1. If this is your first deploy: - 1. copy `config.sample.json` to `config.json` and if needed, make any modifications. + 1. copy `config.sample.json` to `config.json` and if needed, make any modifications (unless you've set up your own [sygnal](https://github.com/matrix-org/sygnal) instance, you don't need to change anything in the `push` section). 1. Disable caching entirely on the server (they will still be cached client-side by the service worker) for: - `index.html` - `sw.js` From 0e46aed0df32e98e67a82f5f0834657df0a86425 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 20:52:18 +0200 Subject: [PATCH 052/174] rename config file to config.sample.json when packaging --- scripts/package.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/package.sh b/scripts/package.sh index 8146fe58..6ad136a3 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -2,6 +2,9 @@ VERSION=$(jq -r ".version" package.json) PACKAGE=hydrogen-web-$VERSION.tar.gz yarn build pushd target +# move config file so we don't override it +# when deploying a new version +mv config.json config.sample.json tar -czvf ../$PACKAGE ./ popd echo $PACKAGE From 1555b0f4bcddc541a09bd63e6fcb7c4bcb521817 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 21:41:31 +0200 Subject: [PATCH 053/174] put a message in container node when config file is not found --- src/platform/web/Platform.js | 40 ++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 2a691580..8e079c85 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -169,22 +169,32 @@ export class Platform { } async init() { - await this.logger.run("Platform init", async (log) => { - if (!this._config) { - if (!this._configURL) { - throw new Error("Neither config nor configURL was provided!"); + try { + await this.logger.run("Platform init", async (log) => { + if (!this._config) { + if (!this._configURL) { + throw new Error("Neither config nor configURL was provided!"); + } + const {status, body}= await this.request(this._configURL, {method: "GET", format: "json", cache: true}).response(); + if (status === 404) { + throw new Error(`Could not find ${this._configURL}. Did you copy over config.sample.json?`); + } else if (status >= 400) { + throw new Error(`Got status ${status} while trying to fetch ${this._configURL}`); + } + this._config = body; } - const {body}= await this.request(this._configURL, {method: "GET", format: "json", cache: true}).response(); - this._config = body; - } - this.notificationService = new NotificationService( - this._serviceWorkerHandler, - this._config.push - ); - const manifests = this.config["themeManifests"]; - await this._themeLoader?.init(manifests); - this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme(), log); - }); + this.notificationService = new NotificationService( + this._serviceWorkerHandler, + this._config.push + ); + const manifests = this.config["themeManifests"]; + await this._themeLoader?.init(manifests); + this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme(), log); + }); + } catch (err) { + this._container.innerText = err.message; + throw err; + } } _createLogger(isDevelopment) { From 13428bd03c7ec3821352ab13eb631fb0bbe23e94 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 21:41:47 +0200 Subject: [PATCH 054/174] allow updating cache of unhashed assets (like config) in service worker --- src/platform/web/sw.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index 6b07f42e..088bc059 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -160,8 +160,14 @@ async function updateCache(request, response) { cache.put(request, response.clone()); } else if (request.url.startsWith(baseURL)) { let assetName = request.url.substr(baseURL.length); + let cacheName; if (HASHED_CACHED_ON_REQUEST_ASSETS.includes(assetName)) { - const cache = await caches.open(hashedCacheName); + cacheName = hashedCacheName; + } else if (UNHASHED_PRECACHED_ASSETS.includes(assetName)) { + cacheName = unhashedCacheName; + } + if (cacheName) { + const cache = await caches.open(cacheName); await cache.put(request, response.clone()); } } From 514d5c0a50060cb92f7aa46c3ef915f5dbe06710 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 19:44:39 +0000 Subject: [PATCH 055/174] add notes about client side caching --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1a457741..6c447024 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,14 @@ Hydrogen is deployed to [hydrogen.element.io](https://hydrogen.element.io). You 1. Extract the package to the public directory of your web server. 1. If this is your first deploy: 1. copy `config.sample.json` to `config.json` and if needed, make any modifications (unless you've set up your own [sygnal](https://github.com/matrix-org/sygnal) instance, you don't need to change anything in the `push` section). - 1. Disable caching entirely on the server (they will still be cached client-side by the service worker) for: + 1. Disable caching entirely on the server for: - `index.html` - `sw.js` - `config.json` - All theme manifests referenced in the `themeManifests` of `config.json`, these files are typically called `theme-{name}.json`. - + + These resources will still be cached client-side by the service worker. Because of this; you'll still need to refresh the app twice before config.json changes are applied. + ## Set up a dev environment You can run Hydrogen locally by the following commands in the terminal: From ed8c98558db9eeeb38ec2786d428f153a639288a Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 21:45:45 +0200 Subject: [PATCH 056/174] release v0.2.29 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 679000e1..4fa5f34b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydrogen-web", - "version": "0.2.28", + "version": "0.2.29", "description": "A javascript matrix client prototype, trying to minize RAM usage by offloading as much as possible to IndexedDB", "directories": { "doc": "doc" From 11d7535c238ea1bdaccfca67180455ab8802d05f Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Tue, 31 May 2022 13:38:34 +0200 Subject: [PATCH 057/174] add some basic tests (with mock utils) for DeviceTracker --- src/matrix/e2ee/DeviceTracker.js | 122 +++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/src/matrix/e2ee/DeviceTracker.js b/src/matrix/e2ee/DeviceTracker.js index 0068a1f9..c640b04a 100644 --- a/src/matrix/e2ee/DeviceTracker.js +++ b/src/matrix/e2ee/DeviceTracker.js @@ -363,3 +363,125 @@ export class DeviceTracker { return await txn.deviceIdentities.getByCurve25519Key(curve25519Key); } } + +import {createMockStorage} from "../../mocks/Storage"; +import {Instance as NullLoggerInstance} from "../../logging/NullLogger"; + +export function tests() { + + function createUntrackedRoomMock(roomId, joinedUserIds, invitedUserIds = []) { + return { + isTrackingMembers: false, + isEncrypted: true, + loadMemberList: () => { + const joinedMembers = joinedUserIds.map(userId => {return {membership: "join", roomId, userId};}); + const invitedMembers = invitedUserIds.map(userId => {return {membership: "invite", roomId, userId};}); + const members = joinedMembers.concat(invitedMembers); + const memberMap = members.reduce((map, member) => { + map.set(member.userId, member); + return map; + }, new Map()); + return {members: memberMap, release() {}} + }, + writeIsTrackingMembers(isTrackingMembers) { + if (this.isTrackingMembers !== isTrackingMembers) { + return isTrackingMembers; + } + return undefined; + }, + applyIsTrackingMembersChanges(isTrackingMembers) { + if (isTrackingMembers !== undefined) { + this.isTrackingMembers = isTrackingMembers; + } + }, + } + } + + function createQueryKeysHSApiMock(createKey = (algorithm, userId, deviceId) => `${algorithm}:${userId}:${deviceId}:key`) { + return { + queryKeys(payload) { + const {device_keys: deviceKeys} = payload; + const userKeys = Object.entries(deviceKeys).reduce((userKeys, [userId, deviceIds]) => { + if (deviceIds.length === 0) { + deviceIds = ["device1"]; + } + userKeys[userId] = deviceIds.filter(d => d === "device1").reduce((deviceKeys, deviceId) => { + deviceKeys[deviceId] = { + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "device_id": deviceId, + "keys": { + [`curve25519:${deviceId}`]: createKey("curve25519", userId, deviceId), + [`ed25519:${deviceId}`]: createKey("ed25519", userId, deviceId), + }, + "signatures": { + [userId]: { + [`ed25519:${deviceId}`]: `ed25519:${userId}:${deviceId}:signature` + } + }, + "unsigned": { + "device_display_name": `${userId} Phone` + }, + "user_id": userId + }; + return deviceKeys; + }, {}); + return userKeys; + }, {}); + const response = {device_keys: userKeys}; + return { + async response() { + return response; + } + }; + } + }; + } + const roomId = "!abc:hs.tld"; + + return { + "trackRoom only writes joined members": async assert => { + const storage = await createMockStorage(); + const tracker = new DeviceTracker({ + storage, + getSyncToken: () => "token", + olmUtil: {ed25519_verify: () => {}}, // valid if it does not throw + ownUserId: "@alice:hs.tld", + ownDeviceId: "ABCD", + }); + const room = createUntrackedRoomMock(roomId, ["@alice:hs.tld", "@bob:hs.tld"], ["@charly:hs.tld"]); + await tracker.trackRoom(room, NullLoggerInstance.item); + const txn = await storage.readTxn([storage.storeNames.userIdentities]); + assert.deepEqual(await txn.userIdentities.get("@alice:hs.tld"), { + userId: "@alice:hs.tld", + roomIds: [roomId], + deviceTrackingStatus: TRACKING_STATUS_OUTDATED + }); + assert.deepEqual(await txn.userIdentities.get("@bob:hs.tld"), { + userId: "@bob:hs.tld", + roomIds: [roomId], + deviceTrackingStatus: TRACKING_STATUS_OUTDATED + }); + assert.equal(await txn.userIdentities.get("@charly:hs.tld"), undefined); + }, + "getting devices for tracked room yields correct keys": async assert => { + const storage = await createMockStorage(); + const tracker = new DeviceTracker({ + storage, + getSyncToken: () => "token", + olmUtil: {ed25519_verify: () => {}}, // valid if it does not throw + ownUserId: "@alice:hs.tld", + ownDeviceId: "ABCD", + }); + const room = createUntrackedRoomMock(roomId, ["@alice:hs.tld", "@bob:hs.tld"]); + await tracker.trackRoom(room, NullLoggerInstance.item); + const hsApi = createQueryKeysHSApiMock(); + const devices = await tracker.devicesForRoomMembers(roomId, ["@alice:hs.tld", "@bob:hs.tld"], hsApi, NullLoggerInstance.item); + assert.equal(devices.length, 2); + assert.equal(devices.find(d => d.userId === "@alice:hs.tld").ed25519Key, "ed25519:@alice:hs.tld:device1:key"); + assert.equal(devices.find(d => d.userId === "@bob:hs.tld").ed25519Key, "ed25519:@bob:hs.tld:device1:key"); + }, + } +} From 3d3d590334aa5750b1acb36842a5ca240cce0fd5 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Tue, 31 May 2022 13:39:05 +0200 Subject: [PATCH 058/174] add failing test for device with changed key being returned --- src/matrix/e2ee/DeviceTracker.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/matrix/e2ee/DeviceTracker.js b/src/matrix/e2ee/DeviceTracker.js index c640b04a..21ec89b8 100644 --- a/src/matrix/e2ee/DeviceTracker.js +++ b/src/matrix/e2ee/DeviceTracker.js @@ -483,5 +483,34 @@ export function tests() { assert.equal(devices.find(d => d.userId === "@alice:hs.tld").ed25519Key, "ed25519:@alice:hs.tld:device1:key"); assert.equal(devices.find(d => d.userId === "@bob:hs.tld").ed25519Key, "ed25519:@bob:hs.tld:device1:key"); }, + "device with changed key is ignored": async assert => { + const storage = await createMockStorage(); + const tracker = new DeviceTracker({ + storage, + getSyncToken: () => "token", + olmUtil: {ed25519_verify: () => {}}, // valid if it does not throw + ownUserId: "@alice:hs.tld", + ownDeviceId: "ABCD", + }); + const room = createUntrackedRoomMock(roomId, ["@alice:hs.tld", "@bob:hs.tld"]); + await tracker.trackRoom(room, NullLoggerInstance.item); + const hsApi = createQueryKeysHSApiMock(); + // query devices first time + await tracker.devicesForRoomMembers(roomId, ["@alice:hs.tld", "@bob:hs.tld"], hsApi, NullLoggerInstance.item); + const txn = await storage.readWriteTxn([storage.storeNames.userIdentities]); + // mark alice as outdated, so keys will be fetched again + tracker.writeDeviceChanges(["@alice:hs.tld"], txn, NullLoggerInstance.item); + await txn.complete(); + const hsApiWithChangedAliceKey = createQueryKeysHSApiMock((algo, userId, deviceId) => { + return `${algo}:${userId}:${deviceId}:${userId === "@alice:hs.tld" ? "newKey" : "key"}`; + }); + const devices = await tracker.devicesForRoomMembers(roomId, ["@alice:hs.tld", "@bob:hs.tld"], hsApiWithChangedAliceKey, NullLoggerInstance.item); + assert.equal(devices.length, 2); + assert.equal(devices.find(d => d.userId === "@alice:hs.tld").ed25519Key, "ed25519:@alice:hs.tld:device1:key"); + assert.equal(devices.find(d => d.userId === "@bob:hs.tld").ed25519Key, "ed25519:@bob:hs.tld:device1:key"); + const txn2 = await storage.readTxn([storage.storeNames.deviceIdentities]); + // also check the modified key was not stored + assert.equal((await txn2.deviceIdentities.get("@alice:hs.tld", "device1")).ed25519Key, "ed25519:@alice:hs.tld:device1:key"); + } } } From bc5164486820761912600f72acbfc132676a2b00 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Tue, 31 May 2022 13:39:23 +0200 Subject: [PATCH 059/174] reassignment is not used later on, remove --- src/matrix/e2ee/DeviceTracker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/e2ee/DeviceTracker.js b/src/matrix/e2ee/DeviceTracker.js index 21ec89b8..1bfe63ef 100644 --- a/src/matrix/e2ee/DeviceTracker.js +++ b/src/matrix/e2ee/DeviceTracker.js @@ -214,7 +214,7 @@ export class DeviceTracker { const allDeviceIdentities = []; const deviceIdentitiesToStore = []; // filter out devices that have changed their ed25519 key since last time we queried them - deviceIdentities = await Promise.all(deviceIdentities.map(async deviceIdentity => { + await Promise.all(deviceIdentities.map(async deviceIdentity => { if (knownDeviceIds.includes(deviceIdentity.deviceId)) { const existingDevice = await txn.deviceIdentities.get(deviceIdentity.userId, deviceIdentity.deviceId); if (existingDevice.ed25519Key !== deviceIdentity.ed25519Key) { From c62c8da10b9eb3fda00ea5be46677c0e5d109c4e Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Tue, 31 May 2022 13:39:35 +0200 Subject: [PATCH 060/174] fix changed key not being ignored --- src/matrix/e2ee/DeviceTracker.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/matrix/e2ee/DeviceTracker.js b/src/matrix/e2ee/DeviceTracker.js index 1bfe63ef..f8c3bca8 100644 --- a/src/matrix/e2ee/DeviceTracker.js +++ b/src/matrix/e2ee/DeviceTracker.js @@ -219,6 +219,7 @@ export class DeviceTracker { const existingDevice = await txn.deviceIdentities.get(deviceIdentity.userId, deviceIdentity.deviceId); if (existingDevice.ed25519Key !== deviceIdentity.ed25519Key) { allDeviceIdentities.push(existingDevice); + return; } } allDeviceIdentities.push(deviceIdentity); From 38c377486916b94f1e134e919e4f9ac543dbbe06 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 31 May 2022 15:30:56 -0500 Subject: [PATCH 061/174] Import assets from the assets/ directory > Will be easier towards the future when adding more assets. Probably best to keep style.css for now for backwards compat though. > > *-- https://github.com/vector-im/hydrogen-web/pull/693#discussion_r853844282* --- doc/SDK.md | 4 ++-- scripts/sdk/base-manifest.json | 2 -- scripts/sdk/test/esm-entry.ts | 2 +- scripts/sdk/test/test-sdk-in-commonjs-env.js | 2 +- vite.sdk-assets-config.js | 4 ++-- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/doc/SDK.md b/doc/SDK.md index 3f5bdb09..c8f5197f 100644 --- a/doc/SDK.md +++ b/doc/SDK.md @@ -48,8 +48,8 @@ const assetPaths = { wasmBundle: olmJsPath } }; -import "hydrogen-view-sdk/theme-element-light.css"; -// OR import "hydrogen-view-sdk/theme-element-dark.css"; +import "hydrogen-view-sdk/assets/theme-element-light.css"; +// OR import "hydrogen-view-sdk/assets/theme-element-dark.css"; async function main() { const app = document.querySelector('#app')! diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index de940752..441d4189 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -10,8 +10,6 @@ }, "./paths/vite": "./paths/vite.js", "./style.css": "./asset-build/assets/theme-element-light.css", - "./theme-element-light.css": "./asset-build/assets/theme-element-light.css", - "./theme-element-dark.css": "./asset-build/assets/theme-element-dark.css", "./main.js": "./asset-build/assets/main.js", "./download-sandbox.html": "./asset-build/assets/download-sandbox.html", "./assets/*": "./asset-build/assets/*" diff --git a/scripts/sdk/test/esm-entry.ts b/scripts/sdk/test/esm-entry.ts index 1f3b7114..17bbd8bb 100644 --- a/scripts/sdk/test/esm-entry.ts +++ b/scripts/sdk/test/esm-entry.ts @@ -13,7 +13,7 @@ const assetPaths = { wasmBundle: olmJsPath } }; -import "hydrogen-view-sdk/theme-element-light.css"; +import "hydrogen-view-sdk/assets/theme-element-light.css"; console.log('hydrogenViewSdk', hydrogenViewSdk); console.log('assetPaths', assetPaths); diff --git a/scripts/sdk/test/test-sdk-in-commonjs-env.js b/scripts/sdk/test/test-sdk-in-commonjs-env.js index 3fd19d46..333f1573 100644 --- a/scripts/sdk/test/test-sdk-in-commonjs-env.js +++ b/scripts/sdk/test/test-sdk-in-commonjs-env.js @@ -6,7 +6,7 @@ const hydrogenViewSdk = require('hydrogen-view-sdk'); // Worker require.resolve('hydrogen-view-sdk/main.js'); // Styles -require.resolve('hydrogen-view-sdk/theme-element-light.css'); +require.resolve('hydrogen-view-sdk/assets/theme-element-light.css'); // Can access files in the assets/* directory require.resolve('hydrogen-view-sdk/assets/main.js'); diff --git a/vite.sdk-assets-config.js b/vite.sdk-assets-config.js index beb7bb37..7174b8db 100644 --- a/vite.sdk-assets-config.js +++ b/vite.sdk-assets-config.js @@ -3,8 +3,8 @@ const mergeOptions = require('merge-options'); const themeBuilder = require("./scripts/build-plugins/rollup-plugin-build-themes"); const {commonOptions, compiledVariables} = require("./vite.common-config.js"); -// These paths will be saved without their hash so they havea consisent path to -// reference +// These paths will be saved without their hash so they have a consisent path to +// reference in imports. const pathsToExport = [ "main.js", "download-sandbox.html", From 9d8a578dce4018afabecaaa79c38c415e62d0d46 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 31 May 2022 15:35:48 -0500 Subject: [PATCH 062/174] Better comment --- vite.sdk-assets-config.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vite.sdk-assets-config.js b/vite.sdk-assets-config.js index 7174b8db..d7d4d064 100644 --- a/vite.sdk-assets-config.js +++ b/vite.sdk-assets-config.js @@ -3,8 +3,9 @@ const mergeOptions = require('merge-options'); const themeBuilder = require("./scripts/build-plugins/rollup-plugin-build-themes"); const {commonOptions, compiledVariables} = require("./vite.common-config.js"); -// These paths will be saved without their hash so they have a consisent path to -// reference in imports. +// These paths will be saved without their hash so they have a consisent path +// that we can reference in our `package.json` `exports`. And so people can import +// them with a consistent path. const pathsToExport = [ "main.js", "download-sandbox.html", @@ -21,7 +22,8 @@ export default mergeOptions(commonOptions, { output: { assetFileNames: (chunkInfo) => { // Get rid of the hash so we can consistently reference these - // files in our `package.json` `exports` + // files in our `package.json` `exports`. And so people can + // import them with a consistent path. if(pathsToExport.includes(path.basename(chunkInfo.name))) { return "assets/[name].[ext]"; } From 6f0ebeacb7eb3def10caa4c83fce8088a18c884e Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:27:00 +0200 Subject: [PATCH 063/174] fetch single device key in DeviceTracker --- src/matrix/e2ee/DeviceTracker.js | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/matrix/e2ee/DeviceTracker.js b/src/matrix/e2ee/DeviceTracker.js index f8c3bca8..49d9ffab 100644 --- a/src/matrix/e2ee/DeviceTracker.js +++ b/src/matrix/e2ee/DeviceTracker.js @@ -309,6 +309,7 @@ export class DeviceTracker { return await this._devicesForUserIds(roomId, userIds, txn, hsApi, log); } + /** gets devices for the given user ids that are in the given room */ async devicesForRoomMembers(roomId, userIds, hsApi, log) { const txn = await this._storage.readTxn([ this._storage.storeNames.userIdentities, @@ -316,6 +317,57 @@ export class DeviceTracker { return await this._devicesForUserIds(roomId, userIds, txn, hsApi, log); } + /** gets a single device */ + async deviceForId(userId, deviceId, hsApi, log) { + const txn = await this._storage.readTxn([ + this._storage.storeNames.deviceIdentities, + ]); + let device = await txn.deviceIdentities.get(userId, deviceId); + if (device) { + log.set("existingDevice", true); + } else { + //// BEGIN EXTRACT (deviceKeysMap) + const deviceKeyResponse = await hsApi.queryKeys({ + "timeout": 10000, + "device_keys": { + [userId]: [deviceId] + }, + "token": this._getSyncToken() + }, {log}).response(); + // verify signature + const verifiedKeysPerUser = log.wrap("verify", log => this._filterVerifiedDeviceKeys(deviceKeyResponse["device_keys"], log)); + //// END EXTRACT + + // there should only be one device in here, but still check the HS sends us the right one + const verifiedKeys = verifiedKeysPerUser + .find(vkpu => vkpu.userId === userId).verifiedKeys + .find(vk => vk["device_id"] === deviceId); + device = deviceKeysAsDeviceIdentity(verifiedKeys); + const txn = await this._storage.readWriteTxn([ + this._storage.storeNames.deviceIdentities, + ]); + // check again we don't have the device already. + // when updating all keys for a user we allow updating the + // device when the key hasn't changed so the device display name + // can be updated, but here we don't. + const existingDevice = await txn.deviceIdentities.get(userId, deviceId); + if (existingDevice) { + device = existingDevice; + log.set("existingDeviceAfterFetch", true); + } else { + try { + txn.deviceIdentities.set(device); + log.set("newDevice", true); + } catch (err) { + txn.abort(); + throw err; + } + await txn.complete(); + } + } + return device; + } + /** * @param {string} roomId [description] * @param {Array} userIds a set of user ids to try and find the identity for. Will be check to belong to roomId. From 50ae51e8937892ca838f3f2160c6114d14a8462b Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:28:49 +0200 Subject: [PATCH 064/174] encrypt call signalling message only for given device --- src/matrix/Session.js | 16 ++++++++-------- src/matrix/calls/group/GroupCall.ts | 6 +++--- src/matrix/calls/group/Member.ts | 24 ++++++++++++------------ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/matrix/Session.js b/src/matrix/Session.js index cd676fc5..26ab1702 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -79,18 +79,18 @@ export class Session { this._callHandler = new CallHandler({ clock: this._platform.clock, hsApi: this._hsApi, - encryptDeviceMessage: async (roomId, userId, message, log) => { + encryptDeviceMessage: async (roomId, userId, deviceId, message, log) => { if (!this._deviceTracker || !this._olmEncryption) { throw new Error("encryption is not enabled"); } - // TODO: just get the devices we're sending the message to, not all the room devices - // although we probably already fetched all devices to send messages in the likely e2ee room - const devices = await log.wrap("get device keys", async log => { - await this._deviceTracker.trackRoom(this.rooms.get(roomId), log); - return this._deviceTracker.devicesForRoomMembers(roomId, [userId], this._hsApi, log); + const device = await log.wrap("get device key", async log => { + return this._deviceTracker.deviceForId(userId, deviceId, this._hsApi, log); }); - const encryptedMessage = await this._olmEncryption.encrypt(message.type, message.content, devices, this._hsApi, log); - return encryptedMessage; + if (!device) { + throw new Error(`Could not find device key ${deviceId} for ${userId} in ${roomId}`); + } + const encryptedMessages = await this._olmEncryption.encrypt(message.type, message.content, [device], this._hsApi, log); + return encryptedMessages; }, storage: this._storage, webRTC: this._platform.webRTC, diff --git a/src/matrix/calls/group/GroupCall.ts b/src/matrix/calls/group/GroupCall.ts index af3966a0..b2b52e9b 100644 --- a/src/matrix/calls/group/GroupCall.ts +++ b/src/matrix/calls/group/GroupCall.ts @@ -55,7 +55,7 @@ function getDeviceFromMemberKey(key: string): string { export type Options = Omit & { emitUpdate: (call: GroupCall, params?: any) => void; - encryptDeviceMessage: (roomId: string, userId: string, message: SignallingMessage, log: ILogItem) => Promise, + encryptDeviceMessage: (roomId: string, userId: string, deviceId: string, message: SignallingMessage, log: ILogItem) => Promise, storage: Storage, logger: ILogger, }; @@ -93,8 +93,8 @@ export class GroupCall extends EventEmitter<{change: never}> { this._memberOptions = Object.assign({}, options, { confId: this.id, emitUpdate: member => this._members.update(getMemberKey(member.userId, member.deviceId), member), - encryptDeviceMessage: (userId: string, message: SignallingMessage, log) => { - return this.options.encryptDeviceMessage(this.roomId, userId, message, log); + encryptDeviceMessage: (userId: string, deviceId: string, message: SignallingMessage, log) => { + return this.options.encryptDeviceMessage(this.roomId, userId, deviceId, message, log); } }); } diff --git a/src/matrix/calls/group/Member.ts b/src/matrix/calls/group/Member.ts index 69e1eeea..e3abc49e 100644 --- a/src/matrix/calls/group/Member.ts +++ b/src/matrix/calls/group/Member.ts @@ -36,7 +36,7 @@ export type Options = Omit, log: ILogItem) => Promise, + encryptDeviceMessage: (userId: string, deviceId: string, message: SignallingMessage, log: ILogItem) => Promise, emitUpdate: (participant: Member, params?: any) => void, } @@ -217,20 +217,20 @@ export class Member { groupMessage.content.party_id = this.options.ownDeviceId; groupMessage.content.sender_session_id = this.options.sessionId; groupMessage.content.dest_session_id = this.sessionId; - // const encryptedMessages = await this.options.encryptDeviceMessage(this.member.userId, groupMessage, log); - // const payload = formatToDeviceMessagesPayload(encryptedMessages); - const payload = { - messages: { - [this.member.userId]: { - [this.deviceId]: groupMessage.content - } - } - }; + let payload; + let type: string = message.type; + const encryptedMessages = await this.options.encryptDeviceMessage(this.member.userId, this.deviceId, groupMessage, log); + if (encryptedMessages) { + payload = formatToDeviceMessagesPayload(encryptedMessages); + type = "m.room.encrypted"; + } else { + // device needs deviceId and userId + payload = formatToDeviceMessagesPayload([{content: groupMessage.content, device: this}]); + } // TODO: remove this for release log.set("payload", groupMessage.content); const request = this.options.hsApi.sendToDevice( - message.type, - //"m.room.encrypted", + type, payload, makeTxnId(), {log} From 9efe294a79bdf50e6fd7ba1ffa0a32c23fb58d3b Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:29:24 +0200 Subject: [PATCH 065/174] fetch and verify keys on olm call signalling message --- src/matrix/DeviceMessageHandler.js | 62 ++++++++++++++---------------- src/matrix/Session.js | 11 +++++- src/matrix/Sync.js | 2 +- src/matrix/e2ee/README.md | 3 ++ 4 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/matrix/DeviceMessageHandler.js b/src/matrix/DeviceMessageHandler.js index c33236aa..507e07ef 100644 --- a/src/matrix/DeviceMessageHandler.js +++ b/src/matrix/DeviceMessageHandler.js @@ -38,7 +38,6 @@ export class DeviceMessageHandler { async prepareSync(toDeviceEvents, lock, txn, log) { log.set("messageTypes", countBy(toDeviceEvents, e => e.type)); - this._handleUnencryptedCallEvents(toDeviceEvents, log); const encryptedEvents = toDeviceEvents.filter(e => e.type === "m.room.encrypted"); if (!this._olmDecryption) { log.log("can't decrypt, encryption not enabled", log.level.Warn); @@ -54,20 +53,6 @@ export class DeviceMessageHandler { } const newRoomKeys = this._megolmDecryption.roomKeysFromDeviceMessages(olmDecryptChanges.results, log); - // const callMessages = olmDecryptChanges.results.filter(dr => this._callHandler.handlesDeviceMessageEventType(dr.event?.type)); - // // load devices by sender key - // await Promise.all(callMessages.map(async dr => { - // dr.setDevice(await this._getDevice(dr.senderCurve25519Key, txn)); - // })); - // // TODO: pass this in the prep and run it in afterSync or afterSyncComplete (as callHandler can send events as well)? - // for (const dr of callMessages) { - // if (dr.device) { - // this._callHandler.handleDeviceMessage(dr.event, dr.device.userId, dr.device.deviceId, log); - // } else { - // console.error("could not deliver message because don't have device for sender key", dr.event); - // } - // } - // TODO: somehow include rooms that received a call to_device message in the sync state? // or have updates flow through event emitter? // well, we don't really need to update the room other then when a call starts or stops @@ -76,33 +61,42 @@ export class DeviceMessageHandler { } } - _handleUnencryptedCallEvents(toDeviceEvents, log) { - const callMessages = toDeviceEvents.filter(e => this._callHandler.handlesDeviceMessageEventType(e.type)); - for (const event of callMessages) { - const userId = event.sender; - const deviceId = event.content.device_id; - this._callHandler.handleDeviceMessage(event, userId, deviceId, log); - } - } - /** check that prep is not undefined before calling this */ async writeSync(prep, txn) { // write olm changes prep.olmDecryptChanges.write(txn); const didWriteValues = await Promise.all(prep.newRoomKeys.map(key => this._megolmDecryption.writeRoomKey(key, txn))); - return didWriteValues.some(didWrite => !!didWrite); + const hasNewRoomKeys = didWriteValues.some(didWrite => !!didWrite); + return { + hasNewRoomKeys, + decryptionResults: prep.olmDecryptChanges.results + }; } - - async _getDevice(senderKey, txn) { - let device = this._senderDeviceCache.get(senderKey); - if (!device) { - device = await txn.deviceIdentities.getByCurve25519Key(senderKey); - if (device) { - this._senderDeviceCache.set(device); - } + async afterSyncCompleted(decryptionResults, deviceTracker, hsApi, log) { + // if we don't have a device, we need to fetch the device keys the message claims + // and check the keys, and we should only do network requests during + // sync processing in the afterSyncCompleted step. + const callMessages = decryptionResults.filter(dr => this._callHandler.handlesDeviceMessageEventType(dr.event?.type)); + if (callMessages.length) { + await log.wrap("process call signalling messages", async log => { + for (const dr of callMessages) { + // serialize device loading, so subsequent messages for the same device take advantage of the cache + const device = await deviceTracker.deviceForId(dr.event.sender, dr.event.content.device_id, hsApi, log); + dr.setDevice(device); + if (dr.isVerified) { + this._callHandler.handleDeviceMessage(dr.event, dr.userId, dr.deviceId, log); + } else { + log.log({ + l: "could not verify olm fingerprint key matches, ignoring", + ed25519Key: dr.device.ed25519Key, + claimedEd25519Key: dr.claimedEd25519Key, + deviceId, userId, + }); + } + } + }); } - return device; } } diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 26ab1702..c80cf527 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -685,7 +685,9 @@ export class Session { async writeSync(syncResponse, syncFilterId, preparation, txn, log) { const changes = { syncInfo: null, - e2eeAccountChanges: null + e2eeAccountChanges: null, + hasNewRoomKeys: false, + deviceMessageDecryptionResults: null, }; const syncToken = syncResponse.next_batch; if (syncToken !== this.syncToken) { @@ -706,7 +708,9 @@ export class Session { } if (preparation) { - changes.hasNewRoomKeys = await log.wrap("deviceMsgs", log => this._deviceMessageHandler.writeSync(preparation, txn, log)); + const {hasNewRoomKeys, decryptionResults} = await log.wrap("deviceMsgs", log => this._deviceMessageHandler.writeSync(preparation, txn, log)); + changes.hasNewRoomKeys = hasNewRoomKeys; + changes.deviceMessageDecryptionResults = decryptionResults; } // store account data @@ -747,6 +751,9 @@ export class Session { if (changes.hasNewRoomKeys) { this._keyBackup.get()?.flush(log); } + if (changes.deviceMessageDecryptionResults) { + await this._deviceMessageHandler.afterSyncCompleted(changes.deviceMessageDecryptionResults, this._deviceTracker, this._hsApi, log); + } } _tryReplaceRoomBeingCreated(roomId, log) { diff --git a/src/matrix/Sync.js b/src/matrix/Sync.js index 4f907563..f77fa79a 100644 --- a/src/matrix/Sync.js +++ b/src/matrix/Sync.js @@ -160,7 +160,7 @@ export class Sync { const isCatchupSync = this._status.get() === SyncStatus.CatchupSync; const sessionPromise = (async () => { try { - await log.wrap("session", log => this._session.afterSyncCompleted(sessionChanges, isCatchupSync, log), log.level.Detail); + await log.wrap("session", log => this._session.afterSyncCompleted(sessionChanges, isCatchupSync, log)); } catch (err) {} // error is logged, but don't fail sessionPromise })(); diff --git a/src/matrix/e2ee/README.md b/src/matrix/e2ee/README.md index fab53880..fdb4866c 100644 --- a/src/matrix/e2ee/README.md +++ b/src/matrix/e2ee/README.md @@ -41,5 +41,8 @@ Runs before any room.prepareSync, so the new room keys can be passed to each roo - e2ee account - generate more otks if needed - upload new otks if needed or device keys if not uploaded before + - device message handler: + - fetch keys we don't know about yet for (call) to_device messages identity + - pass signalling messages to call handler - rooms - share new room keys if needed From 3edfbd2cf6f468b90e198079f2aefa4670c731f4 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:30:25 +0200 Subject: [PATCH 066/174] await hangup here, so log doesn't terminate early --- src/matrix/calls/group/Member.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/calls/group/Member.ts b/src/matrix/calls/group/Member.ts index e3abc49e..6a8158b6 100644 --- a/src/matrix/calls/group/Member.ts +++ b/src/matrix/calls/group/Member.ts @@ -157,7 +157,7 @@ export class Member { connection.logItem.wrap("disconnect", async log => { disconnectLogItem = log; if (hangup) { - connection.peerCall?.hangup(CallErrorCode.UserHangup, log); + await connection.peerCall?.hangup(CallErrorCode.UserHangup, log); } else { await connection.peerCall?.close(undefined, log); } From 83eef2be9d76417c47e54cc2ac61768b7c7624ba Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:30:41 +0200 Subject: [PATCH 067/174] log lack of persisted storage in ... persisted logs! --- src/matrix/storage/idb/StorageFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/storage/idb/StorageFactory.ts b/src/matrix/storage/idb/StorageFactory.ts index 5cb1b6e5..264d8670 100644 --- a/src/matrix/storage/idb/StorageFactory.ts +++ b/src/matrix/storage/idb/StorageFactory.ts @@ -67,7 +67,7 @@ export class StorageFactory { requestPersistedStorage().then(persisted => { // Firefox lies here though, and returns true even if the user denied the request if (!persisted) { - console.warn("no persisted storage, database can be evicted by browser"); + log.log("no persisted storage, database can be evicted by browser", log.level.Warn); } }); From a014740e72d3343c616a39e2a8d03537fa518b07 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:55:43 +0200 Subject: [PATCH 068/174] don't throw when we can't encrypt, just fall back to sending unencrypted --- src/matrix/Session.js | 16 ++++++++++------ src/matrix/calls/group/GroupCall.ts | 2 +- src/matrix/calls/group/Member.ts | 2 +- src/matrix/e2ee/DeviceTracker.js | 5 ++++- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/matrix/Session.js b/src/matrix/Session.js index c80cf527..f82ad555 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -81,16 +81,20 @@ export class Session { hsApi: this._hsApi, encryptDeviceMessage: async (roomId, userId, deviceId, message, log) => { if (!this._deviceTracker || !this._olmEncryption) { - throw new Error("encryption is not enabled"); + log.set("encryption_disabled", true); + return; } const device = await log.wrap("get device key", async log => { - return this._deviceTracker.deviceForId(userId, deviceId, this._hsApi, log); + const device = this._deviceTracker.deviceForId(userId, deviceId, this._hsApi, log); + if (!device) { + log.set("not_found", true); + } + return device; }); - if (!device) { - throw new Error(`Could not find device key ${deviceId} for ${userId} in ${roomId}`); + if (device) { + const encryptedMessages = await this._olmEncryption.encrypt(message.type, message.content, [device], this._hsApi, log); + return encryptedMessages; } - const encryptedMessages = await this._olmEncryption.encrypt(message.type, message.content, [device], this._hsApi, log); - return encryptedMessages; }, storage: this._storage, webRTC: this._platform.webRTC, diff --git a/src/matrix/calls/group/GroupCall.ts b/src/matrix/calls/group/GroupCall.ts index b2b52e9b..ddc0b517 100644 --- a/src/matrix/calls/group/GroupCall.ts +++ b/src/matrix/calls/group/GroupCall.ts @@ -55,7 +55,7 @@ function getDeviceFromMemberKey(key: string): string { export type Options = Omit & { emitUpdate: (call: GroupCall, params?: any) => void; - encryptDeviceMessage: (roomId: string, userId: string, deviceId: string, message: SignallingMessage, log: ILogItem) => Promise, + encryptDeviceMessage: (roomId: string, userId: string, deviceId: string, message: SignallingMessage, log: ILogItem) => Promise, storage: Storage, logger: ILogger, }; diff --git a/src/matrix/calls/group/Member.ts b/src/matrix/calls/group/Member.ts index 6a8158b6..4c30a6cf 100644 --- a/src/matrix/calls/group/Member.ts +++ b/src/matrix/calls/group/Member.ts @@ -36,7 +36,7 @@ export type Options = Omit, log: ILogItem) => Promise, + encryptDeviceMessage: (userId: string, deviceId: string, message: SignallingMessage, log: ILogItem) => Promise, emitUpdate: (participant: Member, params?: any) => void, } diff --git a/src/matrix/e2ee/DeviceTracker.js b/src/matrix/e2ee/DeviceTracker.js index 49d9ffab..0d0b0e8b 100644 --- a/src/matrix/e2ee/DeviceTracker.js +++ b/src/matrix/e2ee/DeviceTracker.js @@ -338,10 +338,13 @@ export class DeviceTracker { const verifiedKeysPerUser = log.wrap("verify", log => this._filterVerifiedDeviceKeys(deviceKeyResponse["device_keys"], log)); //// END EXTRACT - // there should only be one device in here, but still check the HS sends us the right one const verifiedKeys = verifiedKeysPerUser .find(vkpu => vkpu.userId === userId).verifiedKeys .find(vk => vk["device_id"] === deviceId); + // user hasn't uploaded keys for device? + if (!verifiedKeys) { + return undefined; + } device = deviceKeysAsDeviceIdentity(verifiedKeys); const txn = await this._storage.readWriteTxn([ this._storage.storeNames.deviceIdentities, From a139571e201271b19944369eafec8c41d25e6751 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 2 Jun 2022 10:58:04 +0200 Subject: [PATCH 069/174] move setting seq on outbound messages to member, is specific to_device --- src/matrix/calls/PeerCall.ts | 9 --------- src/matrix/calls/callEventTypes.ts | 2 +- src/matrix/calls/group/Member.ts | 2 ++ 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/matrix/calls/PeerCall.ts b/src/matrix/calls/PeerCall.ts index e6ce3571..ea7e9d06 100644 --- a/src/matrix/calls/PeerCall.ts +++ b/src/matrix/calls/PeerCall.ts @@ -71,7 +71,6 @@ export class PeerCall implements IDisposable { private localMedia?: LocalMedia; private localMuteSettings?: MuteSettings; // TODO: this should go in member - private seq: number = 0; // A queue for candidates waiting to go out. // We try to amalgamate candidates into a single candidate message where // possible @@ -238,7 +237,6 @@ export class PeerCall implements IDisposable { const content: MCallSDPStreamMetadataChanged = { call_id: this.callId, version: 1, - seq: this.seq++, [SDPStreamMetadataKey]: this.getSDPMetadata() }; await this.sendSignallingMessage({type: EventType.SDPStreamMetadataChangedPrefix, content}, log); @@ -263,7 +261,6 @@ export class PeerCall implements IDisposable { const content: MCallSDPStreamMetadataChanged = { call_id: this.callId, version: 1, - seq: this.seq++, [SDPStreamMetadataKey]: this.getSDPMetadata() }; await this.sendSignallingMessage({type: EventType.SDPStreamMetadataChangedPrefix, content}, log); @@ -350,7 +347,6 @@ export class PeerCall implements IDisposable { const content = { call_id: callId, version: 1, - seq: this.seq++, }; // TODO: Don't send UserHangup reason to older clients if (reason) { @@ -398,7 +394,6 @@ export class PeerCall implements IDisposable { offer, [SDPStreamMetadataKey]: this.getSDPMetadata(), version: 1, - seq: this.seq++, lifetime: CALL_TIMEOUT_MS }; await this.sendSignallingMessage({type: EventType.Invite, content}, log); @@ -409,7 +404,6 @@ export class PeerCall implements IDisposable { description: offer, [SDPStreamMetadataKey]: this.getSDPMetadata(), version: 1, - seq: this.seq++, lifetime: CALL_TIMEOUT_MS }; await this.sendSignallingMessage({type: EventType.Negotiate, content}, log); @@ -674,7 +668,6 @@ export class PeerCall implements IDisposable { description: this.peerConnection.localDescription!, [SDPStreamMetadataKey]: this.getSDPMetadata(), version: 1, - seq: this.seq++, lifetime: CALL_TIMEOUT_MS }; await this.sendSignallingMessage({type: EventType.Negotiate, content}, log); @@ -689,7 +682,6 @@ export class PeerCall implements IDisposable { const answerContent: MCallAnswer = { call_id: this.callId, version: 1, - seq: this.seq++, answer: { sdp: localDescription.sdp, type: localDescription.type, @@ -755,7 +747,6 @@ export class PeerCall implements IDisposable { content: { call_id: this.callId, version: 1, - seq: this.seq++, candidates }, }, log); diff --git a/src/matrix/calls/callEventTypes.ts b/src/matrix/calls/callEventTypes.ts index 0490b44f..09376c85 100644 --- a/src/matrix/calls/callEventTypes.ts +++ b/src/matrix/calls/callEventTypes.ts @@ -65,7 +65,6 @@ export interface CallReplacesTarget { export type MCallBase = { call_id: string; version: string | number; - seq: number; } export type MGroupCallBase = MCallBase & { @@ -74,6 +73,7 @@ export type MGroupCallBase = MCallBase & { sender_session_id: string; dest_session_id: string; party_id: string; // Should not need this? + seq: number; } export type MCallAnswer = Base & { diff --git a/src/matrix/calls/group/Member.ts b/src/matrix/calls/group/Member.ts index 4c30a6cf..90765569 100644 --- a/src/matrix/calls/group/Member.ts +++ b/src/matrix/calls/group/Member.ts @@ -53,6 +53,7 @@ const errorCodesWithoutRetry = [ class MemberConnection { public retryCount: number = 0; public peerCall?: PeerCall; + public outboundSeqCounter: number = 0; constructor( public localMedia: LocalMedia, @@ -212,6 +213,7 @@ export class Member { /** @internal */ sendSignallingMessage = async (message: SignallingMessage, log: ILogItem): Promise => { const groupMessage = message as SignallingMessage; + groupMessage.content.seq = ++this.connection!.outboundSeqCounter; groupMessage.content.conf_id = this.options.confId; groupMessage.content.device_id = this.options.ownDeviceId; groupMessage.content.party_id = this.options.ownDeviceId; From 513c05945988aabf5835fa0bdde2da9b96caad09 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 2 Jun 2022 10:59:14 +0200 Subject: [PATCH 070/174] buffer messages as long as seq numbers in between haven't been received --- src/matrix/calls/group/Member.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/matrix/calls/group/Member.ts b/src/matrix/calls/group/Member.ts index 90765569..a40100d2 100644 --- a/src/matrix/calls/group/Member.ts +++ b/src/matrix/calls/group/Member.ts @@ -18,7 +18,7 @@ import {PeerCall, CallState} from "../PeerCall"; import {makeTxnId, makeId} from "../../common"; import {EventType, CallErrorCode} from "../callEventTypes"; import {formatToDeviceMessagesPayload} from "../../common"; - +import {sortedIndex} from "../../../utils/sortedIndex"; import type {MuteSettings} from "../common"; import type {Options as PeerCallOptions, RemoteMedia} from "../PeerCall"; import type {LocalMedia} from "../LocalMedia"; @@ -53,6 +53,8 @@ const errorCodesWithoutRetry = [ class MemberConnection { public retryCount: number = 0; public peerCall?: PeerCall; + public lastProcessedSeqNr: number | undefined; + public queuedSignallingMessages: SignallingMessage[] = []; public outboundSeqCounter: number = 0; constructor( @@ -253,11 +255,19 @@ export class Member { if (message.type === EventType.Invite && !connection.peerCall) { connection.peerCall = this._createPeerCall(message.content.call_id); } + const idx = sortedIndex(connection.queuedSignallingMessages, message, (a, b) => a.content.seq - b.content.seq); + connection.queuedSignallingMessages.splice(idx, 0, message); if (connection.peerCall) { - const item = connection.peerCall.handleIncomingSignallingMessage(message, this.deviceId, connection.logItem); - syncLog.refDetached(item); - } else { - // TODO: need to buffer events until invite comes? + while ( + connection.queuedSignallingMessages.length && ( + connection.lastProcessedSeqNr === undefined || + connection.queuedSignallingMessages[0].content.seq === connection.lastProcessedSeqNr + 1 + )) { + const dequeuedMessage = connection.queuedSignallingMessages.shift()!; + const item = connection.peerCall!.handleIncomingSignallingMessage(dequeuedMessage, this.deviceId, connection.logItem); + connection.lastProcessedSeqNr = dequeuedMessage.content.seq; + syncLog.refDetached(item); + } } } else { syncLog.log({l: "member not connected", userId: this.userId, deviceId: this.deviceId}); From a530944f7d9023c16f40883be88ad6e68489ff75 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 2 Jun 2022 11:11:32 +0200 Subject: [PATCH 071/174] add logging to seq queueing --- src/matrix/calls/group/Member.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/matrix/calls/group/Member.ts b/src/matrix/calls/group/Member.ts index a40100d2..541cdf13 100644 --- a/src/matrix/calls/group/Member.ts +++ b/src/matrix/calls/group/Member.ts @@ -257,18 +257,26 @@ export class Member { } const idx = sortedIndex(connection.queuedSignallingMessages, message, (a, b) => a.content.seq - b.content.seq); connection.queuedSignallingMessages.splice(idx, 0, message); + let hasBeenDequeued = false; if (connection.peerCall) { while ( connection.queuedSignallingMessages.length && ( - connection.lastProcessedSeqNr === undefined || - connection.queuedSignallingMessages[0].content.seq === connection.lastProcessedSeqNr + 1 - )) { + connection.lastProcessedSeqNr === undefined || + connection.queuedSignallingMessages[0].content.seq === connection.lastProcessedSeqNr + 1 + ) + ) { const dequeuedMessage = connection.queuedSignallingMessages.shift()!; + if (dequeuedMessage === message) { + hasBeenDequeued = true; + } const item = connection.peerCall!.handleIncomingSignallingMessage(dequeuedMessage, this.deviceId, connection.logItem); - connection.lastProcessedSeqNr = dequeuedMessage.content.seq; syncLog.refDetached(item); + connection.lastProcessedSeqNr = dequeuedMessage.content.seq; } } + if (!hasBeenDequeued) { + syncLog.refDetached(connection.logItem.log({l: "queued signalling message", type: message.type})); + } } else { syncLog.log({l: "member not connected", userId: this.userId, deviceId: this.deviceId}); } From a52740ed1b4fd7fcacc8bd2f9a9c78ceb17b3d9f Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 2 Jun 2022 15:55:08 +0200 Subject: [PATCH 072/174] give room state handler access to member sync to get sender profile info --- src/matrix/room/Room.js | 14 ++++++++------ src/matrix/room/state/RoomStateHandlerSet.ts | 7 +++++-- src/matrix/room/state/types.ts | 5 +++-- .../room/timeline/persistence/MemberWriter.js | 6 +++++- src/matrix/room/timeline/persistence/SyncWriter.js | 2 +- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index 38982786..425401d8 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -123,7 +123,7 @@ export class Room extends BaseRoom { txn.roomState.removeAllForRoom(this.id); txn.roomMembers.removeAllForRoom(this.id); } - const {entries: newEntries, updatedEntries, newLiveKey, memberChanges} = + const {entries: newEntries, updatedEntries, newLiveKey, memberChanges, memberSync} = await log.wrap("syncWriter", log => this._syncWriter.writeSync( roomResponse, isRejoin, summaryChanges.hasFetchedMembers, txn, log), log.level.Detail); if (decryptChanges) { @@ -180,7 +180,7 @@ export class Room extends BaseRoom { removedPendingEvents = await this._sendQueue.removeRemoteEchos(roomResponse.timeline.events, txn, log); } const powerLevelsEvent = this._getPowerLevelsEvent(roomResponse); - this._runRoomStateHandlers(roomResponse, txn, log); + await this._runRoomStateHandlers(roomResponse, memberSync, txn, log); return { roomResponse, summaryChanges, @@ -453,14 +453,16 @@ export class Room extends BaseRoom { return this._sendQueue.pendingEvents; } - /** global room state handlers, run during write sync step */ - _runRoomStateHandlers(roomResponse, txn, log) { + /** global room state handlers, run during writeSync step */ + _runRoomStateHandlers(roomResponse, memberSync, txn, log) { + const promises = []; iterateResponseStateEvents(roomResponse, event => { - this._roomStateHandler.handleRoomState(this, event, txn, log); + promises.push(this._roomStateHandler.handleRoomState(this, event, memberSync, txn, log)); }); + return Promise.all(promises); } - /** local room state observers, run during after sync step */ + /** local room state observers, run during afterSync step */ _emitSyncRoomState(roomResponse) { iterateResponseStateEvents(roomResponse, event => { for (const handler of this._roomStateObservers) { diff --git a/src/matrix/room/state/RoomStateHandlerSet.ts b/src/matrix/room/state/RoomStateHandlerSet.ts index 986cb0f9..491b02ce 100644 --- a/src/matrix/room/state/RoomStateHandlerSet.ts +++ b/src/matrix/room/state/RoomStateHandlerSet.ts @@ -20,14 +20,17 @@ import type {Transaction} from "../../storage/idb/Transaction"; import type {Room} from "../Room"; import type {MemberChange} from "../members/RoomMember"; import type {RoomStateHandler} from "./types"; +import type {MemberSync} from "../timeline/persistence/MemberWriter.js"; import {BaseObservable} from "../../../observable/BaseObservable"; /** keeps track of all handlers registered with Session.observeRoomState */ export class RoomStateHandlerSet extends BaseObservable implements RoomStateHandler { - handleRoomState(room: Room, stateEvent: StateEvent, txn: Transaction, log: ILogItem) { + async handleRoomState(room: Room, stateEvent: StateEvent, memberSync: MemberSync, txn: Transaction, log: ILogItem): Promise { + const promises: Promise[] = []; for(let h of this._handlers) { - h.handleRoomState(room, stateEvent, txn, log); + promises.push(h.handleRoomState(room, stateEvent, memberSync, txn, log)); } + await Promise.all(promises); } updateRoomMembers(room: Room, memberChanges: Map) { for(let h of this._handlers) { diff --git a/src/matrix/room/state/types.ts b/src/matrix/room/state/types.ts index ef99c727..2e7167d2 100644 --- a/src/matrix/room/state/types.ts +++ b/src/matrix/room/state/types.ts @@ -19,12 +19,13 @@ import type {StateEvent} from "../../storage/types"; import type {Transaction} from "../../storage/idb/Transaction"; import type {ILogItem} from "../../../logging/types"; import type {MemberChange} from "../members/RoomMember"; +import type {MemberSync} from "../timeline/persistence/MemberWriter"; /** used for Session.observeRoomState, which observes in all room, but without loading from storage * It receives the sync write transaction, so other stores can be updated as part of the same transaction. */ export interface RoomStateHandler { - handleRoomState(room: Room, stateEvent: StateEvent, syncWriteTxn: Transaction, log: ILogItem); - updateRoomMembers(room: Room, memberChanges: Map); + handleRoomState(room: Room, stateEvent: StateEvent, memberSync: MemberSync, syncWriteTxn: Transaction, log: ILogItem): Promise; + updateRoomMembers(room: Room, memberChanges: Map): void; } /** diff --git a/src/matrix/room/timeline/persistence/MemberWriter.js b/src/matrix/room/timeline/persistence/MemberWriter.js index 1cdcb7d5..9345324e 100644 --- a/src/matrix/room/timeline/persistence/MemberWriter.js +++ b/src/matrix/room/timeline/persistence/MemberWriter.js @@ -56,7 +56,11 @@ export class MemberWriter { } } -class MemberSync { +/** Represents the member changes in a given sync. + * Used to write the changes to storage and historical member + * information for events in the same sync. + **/ +export class MemberSync { constructor(memberWriter, stateEvents, timelineEvents, hasFetchedMembers) { this._memberWriter = memberWriter; this._timelineEvents = timelineEvents; diff --git a/src/matrix/room/timeline/persistence/SyncWriter.js b/src/matrix/room/timeline/persistence/SyncWriter.js index dc5ae3a8..76c7bec7 100644 --- a/src/matrix/room/timeline/persistence/SyncWriter.js +++ b/src/matrix/room/timeline/persistence/SyncWriter.js @@ -244,7 +244,7 @@ export class SyncWriter { const {currentKey, entries, updatedEntries} = await this._writeTimeline(timelineEvents, timeline, memberSync, this._lastLiveKey, txn, log); const memberChanges = await memberSync.write(txn); - return {entries, updatedEntries, newLiveKey: currentKey, memberChanges}; + return {entries, updatedEntries, newLiveKey: currentKey, memberChanges, memberSync}; } afterSync(newLiveKey) { From 90b6a5ccb625066325b4a45e4ae3fc1c1fb3b026 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 2 Jun 2022 15:56:23 +0200 Subject: [PATCH 073/174] update call member info with room member info --- src/domain/session/room/CallViewModel.ts | 5 +++- src/matrix/calls/CallHandler.ts | 38 ++++++++++++++++-------- src/matrix/calls/group/GroupCall.ts | 19 ++++++++++-- src/matrix/calls/group/Member.ts | 9 +++++- 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/domain/session/room/CallViewModel.ts b/src/domain/session/room/CallViewModel.ts index 08bc3691..a770ddbe 100644 --- a/src/domain/session/room/CallViewModel.ts +++ b/src/domain/session/room/CallViewModel.ts @@ -117,7 +117,10 @@ class OwnMemberViewModel extends ViewModel implements IStreamV } } -type MemberOptions = BaseOptions & {member: Member, mediaRepository: MediaRepository}; +type MemberOptions = BaseOptions & { + member: Member, + mediaRepository: MediaRepository +}; export class CallMemberViewModel extends ViewModel implements IStreamViewModel { get stream(): Stream | undefined { diff --git a/src/matrix/calls/CallHandler.ts b/src/matrix/calls/CallHandler.ts index e585bb40..7524200c 100644 --- a/src/matrix/calls/CallHandler.ts +++ b/src/matrix/calls/CallHandler.ts @@ -22,6 +22,7 @@ import {EventType, CallIntent} from "./callEventTypes"; import {GroupCall} from "./group/GroupCall"; import {makeId} from "../common"; import {CALL_LOG_TYPE} from "./common"; +import {EVENT_TYPE as MEMBER_EVENT_TYPE, RoomMember} from "../room/members/RoomMember"; import type {LocalMedia} from "./LocalMedia"; import type {Room} from "../room/Room"; @@ -36,6 +37,7 @@ import type {Transaction} from "../storage/idb/Transaction"; import type {CallEntry} from "../storage/idb/stores/CallStore"; import type {Clock} from "../../platform/web/dom/Clock"; import type {RoomStateHandler} from "../room/state/types"; +import type {MemberSync} from "../room/timeline/persistence/MemberWriter"; export type Options = Omit & { clock: Clock @@ -77,7 +79,7 @@ export class CallHandler implements RoomStateHandler { const names = this.options.storage.storeNames; const txn = await this.options.storage.readTxn([ names.calls, - names.roomState + names.roomState, ]); return txn; } @@ -97,15 +99,17 @@ export class CallHandler implements RoomStateHandler { })); const roomIds = Array.from(new Set(callEntries.map(e => e.roomId))); await Promise.all(roomIds.map(async roomId => { - // const ownCallsMemberEvent = await txn.roomState.get(roomId, EventType.GroupCallMember, this.options.ownUserId); - // if (ownCallsMemberEvent) { - // this.handleCallMemberEvent(ownCallsMemberEvent.event, log); - // } + // TODO: don't load all members until we need them const callsMemberEvents = await txn.roomState.getAllForType(roomId, EventType.GroupCallMember); - for (const entry of callsMemberEvents) { - this.handleCallMemberEvent(entry.event, roomId, log); - } - // TODO: we should be loading the other members as well at some point + await Promise.all(callsMemberEvents.map(async entry => { + const roomMemberState = await txn.roomState.get(roomId, MEMBER_EVENT_TYPE, entry.event.sender); + if (roomMemberState) { + const roomMember = RoomMember.fromMemberEvent(roomMemberState.event); + if (roomMember) { + this.handleCallMemberEvent(entry.event, roomMember, roomId, log); + } + } + })); })); log.set("newSize", this._calls.size); }); @@ -144,12 +148,15 @@ export class CallHandler implements RoomStateHandler { // TODO: check and poll turn server credentials here /** @internal */ - handleRoomState(room: Room, event: StateEvent, txn: Transaction, log: ILogItem) { + async handleRoomState(room: Room, event: StateEvent, memberSync: MemberSync, txn: Transaction, log: ILogItem) { if (event.type === EventType.GroupCall) { this.handleCallEvent(event, room.id, txn, log); } if (event.type === EventType.GroupCallMember) { - this.handleCallMemberEvent(event, room.id, log); + const member: RoomMember | undefined = await memberSync.lookupMemberAtEvent(event.sender, event, txn); + if (member) { // should always have a member? + this.handleCallMemberEvent(event, member, room.id, log); + } } } @@ -157,6 +164,11 @@ export class CallHandler implements RoomStateHandler { updateRoomMembers(room: Room, memberChanges: Map) { // TODO: also have map for roomId to calls, so we can easily update members // we will also need this to get the call for a room + for (const call of this._calls.values()) { + if (call.roomId === room.id) { + call.updateRoomMembers(memberChanges); + } + } } /** @internal */ @@ -193,7 +205,7 @@ export class CallHandler implements RoomStateHandler { } } - private handleCallMemberEvent(event: StateEvent, roomId: string, log: ILogItem) { + private handleCallMemberEvent(event: StateEvent, member: RoomMember, roomId: string, log: ILogItem) { const userId = event.state_key; const roomMemberKey = getRoomMemberKey(roomId, userId) const calls = event.content["m.calls"] ?? []; @@ -201,7 +213,7 @@ export class CallHandler implements RoomStateHandler { const callId = call["m.call_id"]; const groupCall = this._calls.get(callId); // TODO: also check the member when receiving the m.call event - groupCall?.updateMembership(userId, call, log); + groupCall?.updateMembership(userId, member, call, log); }; const newCallIdsMemberOf = new Set(calls.map(call => call["m.call_id"])); let previousCallIdsMemberOf = this.roomMemberToCallIds.get(roomMemberKey); diff --git a/src/matrix/calls/group/GroupCall.ts b/src/matrix/calls/group/GroupCall.ts index ddc0b517..ebacf844 100644 --- a/src/matrix/calls/group/GroupCall.ts +++ b/src/matrix/calls/group/GroupCall.ts @@ -18,7 +18,7 @@ import {ObservableMap} from "../../../observable/map/ObservableMap"; import {Member} from "./Member"; import {LocalMedia} from "../LocalMedia"; import {MuteSettings, CALL_LOG_TYPE} from "../common"; -import {RoomMember} from "../../room/members/RoomMember"; +import {MemberChange, RoomMember} from "../../room/members/RoomMember"; import {EventEmitter} from "../../../utils/EventEmitter"; import {EventType, CallIntent} from "../callEventTypes"; @@ -258,7 +258,20 @@ export class GroupCall extends EventEmitter<{change: never}> { } /** @internal */ - updateMembership(userId: string, callMembership: CallMembership, syncLog: ILogItem) { + updateRoomMembers(memberChanges: Map) { + for (const change of memberChanges.values()) { + const {member} = change; + for (const callMember of this._members.values()) { + // find all call members for a room member (can be multiple, for every device) + if (callMember.userId === member.userId) { + callMember.updateRoomMember(member); + } + } + } + } + + /** @internal */ + updateMembership(userId: string, roomMember: RoomMember, callMembership: CallMembership, syncLog: ILogItem) { syncLog.wrap({l: "update call membership", t: CALL_LOG_TYPE, id: this.id, userId}, log => { const devices = callMembership["m.devices"]; const previousDeviceIds = this.getDeviceIdsForUserId(userId); @@ -290,7 +303,7 @@ export class GroupCall extends EventEmitter<{change: never}> { } log.set("add", true); member = new Member( - RoomMember.fromUserId(this.roomId, userId, "join"), + roomMember, device, this._memberOptions, ); this._members.add(memberKey, member); diff --git a/src/matrix/calls/group/Member.ts b/src/matrix/calls/group/Member.ts index 541cdf13..2a4eefb7 100644 --- a/src/matrix/calls/group/Member.ts +++ b/src/matrix/calls/group/Member.ts @@ -68,7 +68,7 @@ export class Member { private connection?: MemberConnection; constructor( - public readonly member: RoomMember, + public member: RoomMember, private callDeviceMembership: CallDeviceMembership, private readonly options: Options, ) {} @@ -179,6 +179,13 @@ export class Member { this.connection.logItem.refDetached(causeItem); } } + + /** @internal */ + updateRoomMember(roomMember: RoomMember) { + this.member = roomMember; + // TODO: this emits an update during the writeSync phase, which we usually try to avoid + this.options.emitUpdate(this); + } /** @internal */ emitUpdateFromPeerCall = (peerCall: PeerCall, params: any, log: ILogItem): void => { From 0c20beb1c0a43dff5275d8711c565b718cf7c36d Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 2 Jun 2022 15:58:03 +0200 Subject: [PATCH 074/174] always pass mediaRepo to call vm --- src/domain/session/room/RoomViewModel.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index c60ce649..f8b70508 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -67,8 +67,9 @@ export class RoomViewModel extends ViewModel { this.emitChange("callViewModel"); })); const call = this._callObservable.get(); + // TODO: cleanup this duplication to create CallViewModel if (call) { - this._callViewModel = new CallViewModel(this.childOptions({call})); + this._callViewModel = this.track(new CallViewModel(this.childOptions({call, mediaRepository: this._room.mediaRepository}))); } } From d66d810fe2a4daa3dcbed4d2f97fbce5a4a393e1 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 2 Jun 2022 15:58:26 +0200 Subject: [PATCH 075/174] pass updates to avatar view --- src/platform/web/ui/session/room/CallView.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform/web/ui/session/room/CallView.ts b/src/platform/web/ui/session/room/CallView.ts index 92dfe6b9..ac31973c 100644 --- a/src/platform/web/ui/session/room/CallView.ts +++ b/src/platform/web/ui/session/room/CallView.ts @@ -49,7 +49,7 @@ class StreamView extends TemplateView { t.div({className: { StreamView_avatar: true, hidden: vm => !vm.isCameraMuted - }}, t.view(new AvatarView(vm, 64), {parentProvidesUpdates: true})), + }}, t.view(new AvatarView(vm, 96), {parentProvidesUpdates: true})), t.div({ className: { StreamView_muteStatus: true, @@ -60,4 +60,10 @@ class StreamView extends TemplateView { }) ]); } + + update(value, props) { + super.update(value); + // update the AvatarView as we told it to not subscribe itself with parentProvidesUpdates + this.updateSubViews(value, props); + } } From f452c3ff4c56f1ae4fb42a46107f10ff02b33668 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 2 Jun 2022 15:58:38 +0200 Subject: [PATCH 076/174] enable 96px avatars --- src/platform/web/ui/css/avatar.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/platform/web/ui/css/avatar.css b/src/platform/web/ui/css/avatar.css index 2ee9ca0c..a8f8080d 100644 --- a/src/platform/web/ui/css/avatar.css +++ b/src/platform/web/ui/css/avatar.css @@ -46,6 +46,14 @@ limitations under the License. font-size: calc(var(--avatar-size) * 0.6); } +.hydrogen .avatar.size-96 { + --avatar-size: 96px; + width: var(--avatar-size); + height: var(--avatar-size); + line-height: var(--avatar-size); + font-size: calc(var(--avatar-size) * 0.6); +} + .hydrogen .avatar.size-64 { --avatar-size: 64px; width: var(--avatar-size); From 8ba1d085f6a1ed011f464d9e38ea3d414fe991d7 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 2 Jun 2022 15:58:50 +0200 Subject: [PATCH 077/174] fix refactor mistake in logging --- src/matrix/DeviceMessageHandler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/matrix/DeviceMessageHandler.js b/src/matrix/DeviceMessageHandler.js index 507e07ef..05276549 100644 --- a/src/matrix/DeviceMessageHandler.js +++ b/src/matrix/DeviceMessageHandler.js @@ -91,7 +91,8 @@ export class DeviceMessageHandler { l: "could not verify olm fingerprint key matches, ignoring", ed25519Key: dr.device.ed25519Key, claimedEd25519Key: dr.claimedEd25519Key, - deviceId, userId, + deviceId: device.deviceId, + userId: device.userId, }); } } From c8b5c6dd41e89a20a3a3913a4efbed9046c8f7b8 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 2 Jun 2022 17:30:17 +0200 Subject: [PATCH 078/174] expose own user on BaseRoom so we don't have to pass session around everywhere we need this --- src/matrix/room/BaseRoom.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/matrix/room/BaseRoom.js b/src/matrix/room/BaseRoom.js index b8f172d0..9ca75a24 100644 --- a/src/matrix/room/BaseRoom.js +++ b/src/matrix/room/BaseRoom.js @@ -446,6 +446,10 @@ export class BaseRoom extends EventEmitter { return this._summary.data.membership; } + get user() { + return this._user; + } + isDirectMessageForUserId(userId) { if (this._summary.data.dmUserId === userId) { return true; From 5280467e6612de359b332ad28133cede54f569a3 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 2 Jun 2022 17:30:43 +0200 Subject: [PATCH 079/174] return type is actual subclass options, not the options of ViewModel --- src/domain/ViewModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/ViewModel.ts b/src/domain/ViewModel.ts index 0bc52f6e..debf8e34 100644 --- a/src/domain/ViewModel.ts +++ b/src/domain/ViewModel.ts @@ -47,7 +47,7 @@ export class ViewModel extends EventEmitter<{change this._options = options; } - childOptions(explicitOptions: T): T & Options { + childOptions(explicitOptions: T): T & O { return Object.assign({}, this._options, explicitOptions); } From f8b01ac3ccd09fe3527aa7704360d76565f10cd2 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 2 Jun 2022 17:31:17 +0200 Subject: [PATCH 080/174] show profile info for own call member by observing member on room --- src/domain/session/room/CallViewModel.ts | 55 ++++++++++++++++++------ src/domain/session/room/RoomViewModel.js | 4 +- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/domain/session/room/CallViewModel.ts b/src/domain/session/room/CallViewModel.ts index a770ddbe..265648bf 100644 --- a/src/domain/session/room/CallViewModel.ts +++ b/src/domain/session/room/CallViewModel.ts @@ -20,15 +20,18 @@ import {getStreamVideoTrack, getStreamAudioTrack} from "../../../matrix/calls/co import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar"; import {EventObservableValue} from "../../../observable/value/EventObservableValue"; import {ObservableValueMap} from "../../../observable/map/ObservableValueMap"; +import type {Room} from "../../../matrix/room/Room"; import type {GroupCall} from "../../../matrix/calls/group/GroupCall"; import type {Member} from "../../../matrix/calls/group/Member"; +import type {RoomMember} from "../../../matrix/room/members/RoomMember"; import type {BaseObservableList} from "../../../observable/list/BaseObservableList"; +import type {BaseObservableValue} from "../../../observable/value/BaseObservableValue"; import type {Stream} from "../../../platform/types/MediaDevices"; import type {MediaRepository} from "../../../matrix/net/MediaRepository"; type Options = BaseOptions & { call: GroupCall, - mediaRepository: MediaRepository + room: Room, }; export class CallViewModel extends ViewModel { @@ -37,10 +40,10 @@ export class CallViewModel extends ViewModel { constructor(options: Options) { super(options); const ownMemberViewModelMap = new ObservableValueMap("self", new EventObservableValue(this.call, "change")) - .mapValues(call => new OwnMemberViewModel(this.childOptions({call: this.call, mediaRepository: this.getOption("mediaRepository")})), () => {}); + .mapValues((call, emitChange) => new OwnMemberViewModel(this.childOptions({call, emitChange})), () => {}); this.memberViewModels = this.call.members .filterValues(member => member.isConnected) - .mapValues(member => new CallMemberViewModel(this.childOptions({member, mediaRepository: this.getOption("mediaRepository")}))) + .mapValues(member => new CallMemberViewModel(this.childOptions({member, mediaRepository: this.getOption("room").mediaRepository}))) .join(ownMemberViewModelMap) .sortValues((a, b) => a.compare(b)); } @@ -74,12 +77,22 @@ export class CallViewModel extends ViewModel { } } -type OwnMemberOptions = BaseOptions & { - call: GroupCall, - mediaRepository: MediaRepository -} +class OwnMemberViewModel extends ViewModel implements IStreamViewModel { + private memberObservable: undefined | BaseObservableValue; + + constructor(options: Options) { + super(options); + this.init(); + } + + async init() { + const room = this.getOption("room"); + this.memberObservable = await room.observeMember(room.user.id); + this.track(this.memberObservable!.subscribe(() => { + this.emitChange(undefined); + })); + } -class OwnMemberViewModel extends ViewModel implements IStreamViewModel { get stream(): Stream | undefined { return this.call.localMedia?.userMedia; } @@ -97,19 +110,37 @@ class OwnMemberViewModel extends ViewModel implements IStreamV } get avatarLetter(): string { - return "I"; + const member = this.memberObservable?.get(); + if (member) { + return avatarInitials(member.name); + } else { + return ""; + } } get avatarColorNumber(): number { - return 3; + const member = this.memberObservable?.get(); + if (member) { + return getIdentifierColorNumber(member.userId); + } else { + return 0; + } } avatarUrl(size: number): string | undefined { - return undefined; + const member = this.memberObservable?.get(); + if (member) { + return getAvatarHttpUrl(member.avatarUrl, size, this.platform, this.getOption("room").mediaRepository); + } } get avatarTitle(): string { - return "Me"; + const member = this.memberObservable?.get(); + if (member) { + return member.name; + } else { + return ""; + } } compare(other: OwnMemberViewModel | CallMemberViewModel): number { diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index f8b70508..077a996e 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -62,14 +62,14 @@ export class RoomViewModel extends ViewModel { } this._callViewModel = this.disposeTracked(this._callViewModel); if (call) { - this._callViewModel = this.track(new CallViewModel(this.childOptions({call, mediaRepository: this._room.mediaRepository}))); + this._callViewModel = this.track(new CallViewModel(this.childOptions({call, room: this._room}))); } this.emitChange("callViewModel"); })); const call = this._callObservable.get(); // TODO: cleanup this duplication to create CallViewModel if (call) { - this._callViewModel = this.track(new CallViewModel(this.childOptions({call, mediaRepository: this._room.mediaRepository}))); + this._callViewModel = this.track(new CallViewModel(this.childOptions({call, room: this._room}))); } } From ed5fdb8154513b67d07d8fc3f7494a63e9f8c3e7 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 3 Jun 2022 12:43:51 +0200 Subject: [PATCH 081/174] don't withhold member event for call just because we don't have profile --- src/matrix/calls/CallHandler.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/matrix/calls/CallHandler.ts b/src/matrix/calls/CallHandler.ts index 7524200c..ca58c4a8 100644 --- a/src/matrix/calls/CallHandler.ts +++ b/src/matrix/calls/CallHandler.ts @@ -102,13 +102,18 @@ export class CallHandler implements RoomStateHandler { // TODO: don't load all members until we need them const callsMemberEvents = await txn.roomState.getAllForType(roomId, EventType.GroupCallMember); await Promise.all(callsMemberEvents.map(async entry => { - const roomMemberState = await txn.roomState.get(roomId, MEMBER_EVENT_TYPE, entry.event.sender); + const userId = entry.event.sender; + const roomMemberState = await txn.roomState.get(roomId, MEMBER_EVENT_TYPE, userId); + let roomMember; if (roomMemberState) { - const roomMember = RoomMember.fromMemberEvent(roomMemberState.event); - if (roomMember) { - this.handleCallMemberEvent(entry.event, roomMember, roomId, log); - } + roomMember = RoomMember.fromMemberEvent(roomMemberState.event); } + if (!roomMember) { + // we'll be missing the member here if we received a call and it's members + // as pre-gap state and the members weren't active in the timeline we got. + roomMember = RoomMember.fromUserId(roomId, userId, "join"); + } + this.handleCallMemberEvent(entry.event, roomMember, roomId, log); })); })); log.set("newSize", this._calls.size); @@ -153,10 +158,13 @@ export class CallHandler implements RoomStateHandler { this.handleCallEvent(event, room.id, txn, log); } if (event.type === EventType.GroupCallMember) { - const member: RoomMember | undefined = await memberSync.lookupMemberAtEvent(event.sender, event, txn); - if (member) { // should always have a member? - this.handleCallMemberEvent(event, member, room.id, log); + let member = await memberSync.lookupMemberAtEvent(event.sender, event, txn); + if (!member) { + // we'll be missing the member here if we received a call and it's members + // as pre-gap state and the members weren't active in the timeline we got. + member = RoomMember.fromUserId(room.id, event.sender, "join"); } + this.handleCallMemberEvent(event, member, room.id, log); } } From 1fab314dd51c4b7356d047a07bdd04bd3d35ad44 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 3 Jun 2022 15:49:45 +0200 Subject: [PATCH 082/174] return user id for own avatar in call if member hasn't been found --- src/domain/session/room/CallViewModel.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/domain/session/room/CallViewModel.ts b/src/domain/session/room/CallViewModel.ts index 265648bf..cd974605 100644 --- a/src/domain/session/room/CallViewModel.ts +++ b/src/domain/session/room/CallViewModel.ts @@ -114,17 +114,12 @@ class OwnMemberViewModel extends ViewModel implements IStreamViewModel if (member) { return avatarInitials(member.name); } else { - return ""; + return this.getOption("room").user.id; } } get avatarColorNumber(): number { - const member = this.memberObservable?.get(); - if (member) { - return getIdentifierColorNumber(member.userId); - } else { - return 0; - } + return getIdentifierColorNumber(this.getOption("room").user.id); } avatarUrl(size: number): string | undefined { @@ -139,7 +134,7 @@ class OwnMemberViewModel extends ViewModel implements IStreamViewModel if (member) { return member.name; } else { - return ""; + return this.getOption("room").user.id; } } From bfdea03bbdff2c44fda85bc922146786164739eb Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 3 Jun 2022 15:50:02 +0200 Subject: [PATCH 083/174] start with seq 1, like Element Call does --- src/matrix/calls/group/Member.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/calls/group/Member.ts b/src/matrix/calls/group/Member.ts index 2a4eefb7..cf6bb6f0 100644 --- a/src/matrix/calls/group/Member.ts +++ b/src/matrix/calls/group/Member.ts @@ -222,7 +222,7 @@ export class Member { /** @internal */ sendSignallingMessage = async (message: SignallingMessage, log: ILogItem): Promise => { const groupMessage = message as SignallingMessage; - groupMessage.content.seq = ++this.connection!.outboundSeqCounter; + groupMessage.content.seq = this.connection!.outboundSeqCounter++; groupMessage.content.conf_id = this.options.confId; groupMessage.content.device_id = this.options.ownDeviceId; groupMessage.content.party_id = this.options.ownDeviceId; From 4474458f4bf4d765f360390cf26a882662358fa5 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 23 May 2022 12:45:32 +0530 Subject: [PATCH 084/174] getActiveTheme should never return undefined Instead it should throw an error. This is useful for when we do setTheme(await getActiveTheme()) because setTheme expects a string. --- src/platform/web/ThemeLoader.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index d9aaabb6..2aa79bb5 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -58,7 +58,7 @@ export class ThemeLoader { return Object.keys(this._themeMapping); } - async getActiveTheme(): Promise { + async getActiveTheme(): Promise { // check if theme is set via settings let theme = await this._platform.settingsStorage.getString("theme"); if (theme) { @@ -70,6 +70,6 @@ export class ThemeLoader { } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { return this._platform.config["defaultTheme"].light; } - return undefined; + throw new Error("Cannot find active theme!"); } } From 809c522571baf6a1d4b41221621893a1a89feef3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 26 May 2022 23:13:31 +0530 Subject: [PATCH 085/174] Change the format of built-asset --- .../rollup-plugin-build-themes.js | 47 +++++++++++++++++-- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index da2db73b..4fddb8ed 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -254,13 +254,50 @@ module.exports = function buildThemes(options) { const derivedVariables = compiledVariables["derived-variables"]; const icon = compiledVariables["icon"]; const builtAssets = {}; - /** - * Generate a mapping from theme name to asset hashed location of said theme in build output. - * This can be used to enumerate themes during runtime. - */ + let defaultDarkVariant = {}, defaultLightVariant = {}; + const themeName = manifest.name; for (const chunk of chunkArray) { const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); - builtAssets[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; + const {name: variantName, default: isDefault, dark} = manifest.values.variants[variant]; + const themeId = `${name}-${variant}`; + const themeDisplayName = `${themeName} ${variantName}`; + if (isDefault) { + /** + * This is a default variant! + * We'll add these to the builtAssets keyed with just the + * theme-name (i.e "Element" instead of "Element Dark") + * so that we can distinguish them from other variants! + * + * This allows us to render a radio-button with "dark" and + * "light" options. + */ + const defaultVariant = dark ? defaultDarkVariant : defaultLightVariant; + defaultVariant.themeDisplayName = variantName; + defaultVariant.id = themeId + defaultVariant.cssLocation = assetMap.get(chunk.fileName).fileName; + continue; + } + // Non-default variants are keyed in builtAssets with "theme_name variant_name" + // eg: "Element Dark" + builtAssets[themeDisplayName] = { + cssLocation: assetMap.get(chunk.fileName).fileName, + id: themeId + }; + } + if (defaultDarkVariant.id && defaultLightVariant.id) { + /** + * As mentioned above, if there's both a default dark and a default light variant, + * add them to builtAsset separately. + */ + builtAssets[themeName] = { dark: defaultDarkVariant, light: defaultLightVariant }; + } + else { + /** + * If only one default variant is found (i.e only dark default or light default but not both), + * treat it like any other variant. + */ + const variant = defaultDarkVariant.id ? defaultDarkVariant : defaultLightVariant; + builtAssets[`${themeName} ${variant.themeDisplayName}`] = { id: variant.id, cssLocation: variant.cssLocation }; } manifest.source = { "built-assets": builtAssets, From 3afbe1148e9fffa5837eb26c81a21b8c90047037 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 26 May 2022 23:35:09 +0530 Subject: [PATCH 086/174] Use the new built-asset format in ThemeLoader --- src/platform/web/ThemeLoader.ts | 46 ++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 2aa79bb5..1f010fb2 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -17,15 +17,35 @@ limitations under the License. import type {ILogItem} from "../../logging/types.js"; import type {Platform} from "./Platform.js"; +type NormalVariant = { + id: string; + cssLocation: string; +}; + +type DefaultVariant = { + dark: { + id: string; + cssLocation: string; + themeDisplayName: string; + }; + light: { + id: string; + cssLocation: string; + themeDisplayName: string; + }; +} +type ThemeInformation = NormalVariant | DefaultVariant; + export class ThemeLoader { private _platform: Platform; - private _themeMapping: Record = {}; + private _themeMapping: Record; constructor(platform: Platform) { this._platform = platform; } async init(manifestLocations: string[]): Promise { + this._themeMapping = {}; for (const manifestLocation of manifestLocations) { const { body } = await this._platform .request(manifestLocation, { @@ -36,8 +56,8 @@ export class ThemeLoader { .response(); /* After build has finished, the source section of each theme manifest - contains `built-assets` which is a mapping from the theme-name to the - location of the css file in build. + contains `built-assets` which is a mapping from the theme-name to theme + details which includes the location of the CSS file. */ Object.assign(this._themeMapping, body["source"]["built-assets"]); } @@ -45,7 +65,7 @@ export class ThemeLoader { setTheme(themeName: string, log?: ILogItem) { this._platform.logger.wrapOrRun(log, {l: "change theme", id: themeName}, () => { - const themeLocation = this._themeMapping[themeName]; + const themeLocation = this._findThemeLocationFromId(themeName); if (!themeLocation) { throw new Error( `Cannot find theme location for theme "${themeName}"!`); } @@ -54,8 +74,8 @@ export class ThemeLoader { }); } - get themes(): string[] { - return Object.keys(this._themeMapping); + get themeMapping(): Record { + return this._themeMapping; } async getActiveTheme(): Promise { @@ -72,4 +92,18 @@ export class ThemeLoader { } throw new Error("Cannot find active theme!"); } + + private _findThemeLocationFromId(themeId: string) { + for (const themeData of Object.values(this._themeMapping)) { + if ("id" in themeData && themeData.id === themeId) { + return themeData.cssLocation; + } + else if ("light" in themeData && themeData.light?.id === themeId) { + return themeData.light.cssLocation; + } + else if ("dark" in themeData && themeData.dark?.id === themeId) { + return themeData.dark.cssLocation; + } + } + } } From 0b98473e85ce31bf7a4beb80077d29f2002ee1e6 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 26 May 2022 23:36:37 +0530 Subject: [PATCH 087/174] Render a radio button for default variants --- .../session/settings/SettingsViewModel.js | 14 ++++++-- .../web/ui/session/settings/SettingsView.js | 34 +++++++++++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 5c89236f..bab72d3d 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -131,8 +131,8 @@ export class SettingsViewModel extends ViewModel { return this._formatBytes(this._estimate?.usage); } - get themes() { - return this.platform.themeLoader.themes; + get themeMapping() { + return this.platform.themeLoader.themeMapping; } get activeTheme() { @@ -185,5 +185,15 @@ export class SettingsViewModel extends ViewModel { this.emitChange("pushNotifications.serverError"); } } + + themeOptionChanged(themeId) { + if (themeId) { + this.setTheme(themeId); + } + // if there's no themeId, then the theme is going to be set via radio-buttons + // emit so that radio-buttons become displayed/hidden + this.emitChange("themeOption"); + } + } diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index dd7bbc03..06f98577 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -142,9 +142,37 @@ export class SettingsView extends TemplateView { _themeOptions(t, vm) { const activeTheme = vm.activeTheme; const optionTags = []; - for (const name of vm.themes) { - optionTags.push(t.option({value: name, selected: name === activeTheme}, name)); + let isDarkSelected = null, isLightSelected = null; + for (const [name, details] of Object.entries(vm.themeMapping)) { + let isSelected = null; + if (details.id === activeTheme) { + isSelected = true; + } + else if (details.dark?.id === activeTheme) { + isSelected = true; + isDarkSelected = true; + } + else if (details.light?.id === activeTheme) { + isSelected = true; + isLightSelected = true; + } + optionTags.push( t.option({ value: details.id ?? "", selected: isSelected} , name)); } - return t.select({onChange: (e) => vm.setTheme(e.target.value)}, optionTags); + const select = t.select({ onChange: (e) => vm.themeOptionChanged(e.target.value) }, optionTags); + const radioButtons = t.form({ + className: { hidden: () => select.options[select.selectedIndex].value !== "" }, + onChange: (e) => { + const selectedThemeName = select.options[select.selectedIndex].text; + const themeId = vm.themeMapping[selectedThemeName][e.target.value].id; + vm.themeOptionChanged(themeId); + } + }, + [ + t.input({ type: "radio", name: "radio-chooser", value: "dark", id: "dark", checked: isDarkSelected }), + t.label({for: "dark"}, "dark"), + t.input({ type: "radio", name: "radio-chooser", value: "light", id: "light", checked: isLightSelected }), + t.label({for: "light"}, "light"), + ]); + return t.div({ className: "theme-chooser" }, [select, radioButtons]); } } From 1f00c8f6359d2570b0e5040c62d1b11891269619 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 26 May 2022 23:36:56 +0530 Subject: [PATCH 088/174] Add a temporary theme to test this PR --- .../web/ui/css/themes/element/manifest.json | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/platform/web/ui/css/themes/element/manifest.json b/src/platform/web/ui/css/themes/element/manifest.json index ec1852cb..0858a05b 100644 --- a/src/platform/web/ui/css/themes/element/manifest.json +++ b/src/platform/web/ui/css/themes/element/manifest.json @@ -1,6 +1,7 @@ { "version": 1, - "name": "element", + "name": "Element", + "id": "element", "values": { "font-faces": [ { @@ -9,37 +10,51 @@ } ], "variants": { - "light": { - "base": true, - "default": true, - "name": "Light", - "variables": { - "background-color-primary": "#fff", + "light": { + "base": true, + "default": true, + "name": "Light", + "variables": { + "background-color-primary": "#fff", "background-color-secondary": "#f6f6f6", - "text-color": "#2E2F32", + "text-color": "#2E2F32", "accent-color": "#03b381", "error-color": "#FF4B55", "fixed-white": "#fff", "room-badge": "#61708b", "link-color": "#238cf5" - } - }, - "dark": { - "dark": true, - "default": true, - "name": "Dark", - "variables": { - "background-color-primary": "#21262b", + } + }, + "dark": { + "dark": true, + "default": true, + "name": "Dark", + "variables": { + "background-color-primary": "#21262b", "background-color-secondary": "#2D3239", - "text-color": "#fff", + "text-color": "#fff", "accent-color": "#03B381", "error-color": "#FF4B55", "fixed-white": "#fff", "room-badge": "#61708b", "link-color": "#238cf5" - } - } - } + } + }, + "violet": { + "dark": true, + "name": "Violet", + "variables": { + "background-color-primary": "#21262b", + "background-color-secondary": "#2D3239", + "text-color": "#fff", + "accent-color": "#03B381", + "error-color": "#FF4B55", + "fixed-white": "#fff", + "room-badge": "#61708b", + "link-color": "#238cf5" + } + } + } } } From d4084da2998e0f011317f34d58bf988fbe3882f0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 27 May 2022 14:23:38 +0530 Subject: [PATCH 089/174] Extract code into function --- src/platform/web/ThemeLoader.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 1f010fb2..2cc0c38d 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -79,18 +79,19 @@ export class ThemeLoader { } async getActiveTheme(): Promise { - // check if theme is set via settings - let theme = await this._platform.settingsStorage.getString("theme"); + const theme = await this._platform.settingsStorage.getString("theme") ?? this.getDefaultTheme(); if (theme) { return theme; } - // return default theme + throw new Error("Cannot find active theme!"); + } + + getDefaultTheme(): string | undefined { if (window.matchMedia("(prefers-color-scheme: dark)").matches) { return this._platform.config["defaultTheme"].dark; } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { return this._platform.config["defaultTheme"].light; } - throw new Error("Cannot find active theme!"); } private _findThemeLocationFromId(themeId: string) { From bbec2effe5128470fbea855c73ccff0e3093cdc8 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 27 May 2022 14:24:19 +0530 Subject: [PATCH 090/174] Add typing --- src/platform/web/ThemeLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 2cc0c38d..299c0e1d 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -94,7 +94,7 @@ export class ThemeLoader { } } - private _findThemeLocationFromId(themeId: string) { + private _findThemeLocationFromId(themeId: string): string | undefined { for (const themeData of Object.values(this._themeMapping)) { if ("id" in themeData && themeData.id === themeId) { return themeData.cssLocation; From f6cec938a7a4903d88f431544c8bab820d6b58f0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 27 May 2022 14:24:44 +0530 Subject: [PATCH 091/174] Add default theme to mapping --- src/platform/web/ThemeLoader.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 299c0e1d..e9bdd479 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -60,6 +60,14 @@ export class ThemeLoader { details which includes the location of the CSS file. */ Object.assign(this._themeMapping, body["source"]["built-assets"]); + //Add the default-theme as an additional option to the mapping + const defaultThemeId = this.getDefaultTheme(); + if (defaultThemeId) { + const cssLocation = this._findThemeLocationFromId(defaultThemeId); + if (cssLocation) { + this._themeMapping["Default"] = { id: "default", cssLocation }; + } + } } } From cb03e97e78e308eeec82cfb6d5d1f43f73a51005 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 27 May 2022 16:31:23 +0530 Subject: [PATCH 092/174] Use default theme intially --- src/platform/web/ThemeLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index e9bdd479..2901cdf9 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -87,7 +87,7 @@ export class ThemeLoader { } async getActiveTheme(): Promise { - const theme = await this._platform.settingsStorage.getString("theme") ?? this.getDefaultTheme(); + const theme = await this._platform.settingsStorage.getString("theme") ?? "default"; if (theme) { return theme; } From e8e4c33bae7435c33bb0c158a348452616f57a40 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 30 May 2022 11:39:05 +0530 Subject: [PATCH 093/174] Rephrase comment --- scripts/build-plugins/rollup-plugin-build-themes.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 4fddb8ed..a3fd6c8f 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -264,11 +264,11 @@ module.exports = function buildThemes(options) { if (isDefault) { /** * This is a default variant! - * We'll add these to the builtAssets keyed with just the - * theme-name (i.e "Element" instead of "Element Dark") - * so that we can distinguish them from other variants! + * We'll add these to the builtAssets (separately) keyed with just the + * theme-name (i.e "Element" instead of "Element Dark"). + * We need to be able to distinguish them from other variants! * - * This allows us to render a radio-button with "dark" and + * This allows us to render radio-buttons with "dark" and * "light" options. */ const defaultVariant = dark ? defaultDarkVariant : defaultLightVariant; From 8ad0b8a7264f22de9c2ce382c60bbd077e689828 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 30 May 2022 11:48:02 +0530 Subject: [PATCH 094/174] rename themeName --> variantName --- scripts/build-plugins/rollup-plugin-build-themes.js | 4 ++-- src/platform/web/ThemeLoader.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index a3fd6c8f..53258f16 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -272,7 +272,7 @@ module.exports = function buildThemes(options) { * "light" options. */ const defaultVariant = dark ? defaultDarkVariant : defaultLightVariant; - defaultVariant.themeDisplayName = variantName; + defaultVariant.variantName = variantName; defaultVariant.id = themeId defaultVariant.cssLocation = assetMap.get(chunk.fileName).fileName; continue; @@ -297,7 +297,7 @@ module.exports = function buildThemes(options) { * treat it like any other variant. */ const variant = defaultDarkVariant.id ? defaultDarkVariant : defaultLightVariant; - builtAssets[`${themeName} ${variant.themeDisplayName}`] = { id: variant.id, cssLocation: variant.cssLocation }; + builtAssets[`${themeName} ${variant.variantName}`] = { id: variant.id, cssLocation: variant.cssLocation }; } manifest.source = { "built-assets": builtAssets, diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 2901cdf9..955d3b48 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -26,12 +26,12 @@ type DefaultVariant = { dark: { id: string; cssLocation: string; - themeDisplayName: string; + variantName: string; }; light: { id: string; cssLocation: string; - themeDisplayName: string; + variantName: string; }; } type ThemeInformation = NormalVariant | DefaultVariant; From 46d2792dac6c76425f32fbde2f9ea21c4e238d30 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 30 May 2022 11:49:45 +0530 Subject: [PATCH 095/174] Modify comment --- src/platform/web/ThemeLoader.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 955d3b48..5bc38afa 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -34,6 +34,7 @@ type DefaultVariant = { variantName: string; }; } + type ThemeInformation = NormalVariant | DefaultVariant; export class ThemeLoader { @@ -57,7 +58,8 @@ export class ThemeLoader { /* After build has finished, the source section of each theme manifest contains `built-assets` which is a mapping from the theme-name to theme - details which includes the location of the CSS file. + information which includes the location of the CSS file. + (see type ThemeInformation above) */ Object.assign(this._themeMapping, body["source"]["built-assets"]); //Add the default-theme as an additional option to the mapping From e3235ea3eb79da19b93a74a75511339c3d861b1b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 30 May 2022 11:58:17 +0530 Subject: [PATCH 096/174] Rename themeName --> themeId --- src/platform/web/ThemeLoader.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 5bc38afa..bd2d51b4 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -73,14 +73,14 @@ export class ThemeLoader { } } - setTheme(themeName: string, log?: ILogItem) { - this._platform.logger.wrapOrRun(log, {l: "change theme", id: themeName}, () => { - const themeLocation = this._findThemeLocationFromId(themeName); + setTheme(themeId: string, log?: ILogItem) { + this._platform.logger.wrapOrRun(log, {l: "change theme", id: themeId}, () => { + const themeLocation = this._findThemeLocationFromId(themeId); if (!themeLocation) { - throw new Error( `Cannot find theme location for theme "${themeName}"!`); + throw new Error( `Cannot find theme location for theme "${themeId}"!`); } this._platform.replaceStylesheet(themeLocation); - this._platform.settingsStorage.setString("theme", themeName); + this._platform.settingsStorage.setString("theme", themeId); }); } From efb1a674706973b9bbd93f9263d83f14e52eaddb Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 30 May 2022 12:02:35 +0530 Subject: [PATCH 097/174] Make method name a verb --- src/domain/session/settings/SettingsViewModel.js | 2 +- src/platform/web/ui/session/settings/SettingsView.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index bab72d3d..caaa2579 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -186,7 +186,7 @@ export class SettingsViewModel extends ViewModel { } } - themeOptionChanged(themeId) { + changeThemeOption(themeId) { if (themeId) { this.setTheme(themeId); } diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 06f98577..e77da911 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -158,13 +158,13 @@ export class SettingsView extends TemplateView { } optionTags.push( t.option({ value: details.id ?? "", selected: isSelected} , name)); } - const select = t.select({ onChange: (e) => vm.themeOptionChanged(e.target.value) }, optionTags); + const select = t.select({ onChange: (e) => vm.changeThemeOption(e.target.value) }, optionTags); const radioButtons = t.form({ className: { hidden: () => select.options[select.selectedIndex].value !== "" }, onChange: (e) => { const selectedThemeName = select.options[select.selectedIndex].text; const themeId = vm.themeMapping[selectedThemeName][e.target.value].id; - vm.themeOptionChanged(themeId); + vm.changeThemeOption(themeId); } }, [ From 9e79b632a8e405857f80d9e6a6a040659bae5288 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 30 May 2022 12:06:23 +0530 Subject: [PATCH 098/174] Extract variable --- src/platform/web/ui/session/settings/SettingsView.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index e77da911..14893aba 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -158,12 +158,18 @@ export class SettingsView extends TemplateView { } optionTags.push( t.option({ value: details.id ?? "", selected: isSelected} , name)); } - const select = t.select({ onChange: (e) => vm.changeThemeOption(e.target.value) }, optionTags); + const select = t.select({ + onChange: (e) => { + const themeId = e.target.value; + vm.changeThemeOption(themeId) + } + }, optionTags); const radioButtons = t.form({ className: { hidden: () => select.options[select.selectedIndex].value !== "" }, onChange: (e) => { const selectedThemeName = select.options[select.selectedIndex].text; - const themeId = vm.themeMapping[selectedThemeName][e.target.value].id; + const colorScheme = e.target.value; + const themeId = vm.themeMapping[selectedThemeName][colorScheme].id; vm.changeThemeOption(themeId); } }, From 12a8e9424325ced7568ad7be21b3e50d3be6e340 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 31 May 2022 20:21:18 +0530 Subject: [PATCH 099/174] Move code into ThemeLoader --- .../rollup-plugin-build-themes.js | 43 +--------------- src/platform/web/ThemeLoader.ts | 51 ++++++++++++++++++- 2 files changed, 51 insertions(+), 43 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 53258f16..8d05f58a 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -254,50 +254,9 @@ module.exports = function buildThemes(options) { const derivedVariables = compiledVariables["derived-variables"]; const icon = compiledVariables["icon"]; const builtAssets = {}; - let defaultDarkVariant = {}, defaultLightVariant = {}; - const themeName = manifest.name; for (const chunk of chunkArray) { const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); - const {name: variantName, default: isDefault, dark} = manifest.values.variants[variant]; - const themeId = `${name}-${variant}`; - const themeDisplayName = `${themeName} ${variantName}`; - if (isDefault) { - /** - * This is a default variant! - * We'll add these to the builtAssets (separately) keyed with just the - * theme-name (i.e "Element" instead of "Element Dark"). - * We need to be able to distinguish them from other variants! - * - * This allows us to render radio-buttons with "dark" and - * "light" options. - */ - const defaultVariant = dark ? defaultDarkVariant : defaultLightVariant; - defaultVariant.variantName = variantName; - defaultVariant.id = themeId - defaultVariant.cssLocation = assetMap.get(chunk.fileName).fileName; - continue; - } - // Non-default variants are keyed in builtAssets with "theme_name variant_name" - // eg: "Element Dark" - builtAssets[themeDisplayName] = { - cssLocation: assetMap.get(chunk.fileName).fileName, - id: themeId - }; - } - if (defaultDarkVariant.id && defaultLightVariant.id) { - /** - * As mentioned above, if there's both a default dark and a default light variant, - * add them to builtAsset separately. - */ - builtAssets[themeName] = { dark: defaultDarkVariant, light: defaultLightVariant }; - } - else { - /** - * If only one default variant is found (i.e only dark default or light default but not both), - * treat it like any other variant. - */ - const variant = defaultDarkVariant.id ? defaultDarkVariant : defaultLightVariant; - builtAssets[`${themeName} ${variant.variantName}`] = { id: variant.id, cssLocation: variant.cssLocation }; + builtAssets[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; } manifest.source = { "built-assets": builtAssets, diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index bd2d51b4..ca7ef641 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -55,13 +55,14 @@ export class ThemeLoader { cache: true, }) .response(); + this._populateThemeMap(body); /* After build has finished, the source section of each theme manifest contains `built-assets` which is a mapping from the theme-name to theme information which includes the location of the CSS file. (see type ThemeInformation above) */ - Object.assign(this._themeMapping, body["source"]["built-assets"]); + // Object.assign(this._themeMapping, body["source"]["built-assets"]); //Add the default-theme as an additional option to the mapping const defaultThemeId = this.getDefaultTheme(); if (defaultThemeId) { @@ -73,6 +74,54 @@ export class ThemeLoader { } } + private _populateThemeMap(manifest) { + const builtAssets: Record = manifest.source?.["built-assets"]; + const themeName = manifest.name; + let defaultDarkVariant: any = {}, defaultLightVariant: any = {}; + for (const [themeId, cssLocation] of Object.entries(builtAssets)) { + const variant = themeId.match(/.+-(.+)/)?.[1]; + const { name: variantName, default: isDefault, dark } = manifest.values.variants[variant!]; + const themeDisplayName = `${themeName} ${variantName}`; + if (isDefault) { + /** + * This is a default variant! + * We'll add these to the themeMapping (separately) keyed with just the + * theme-name (i.e "Element" instead of "Element Dark"). + * We need to be able to distinguish them from other variants! + * + * This allows us to render radio-buttons with "dark" and + * "light" options. + */ + const defaultVariant = dark ? defaultDarkVariant : defaultLightVariant; + defaultVariant.variantName = variantName; + defaultVariant.id = themeId + defaultVariant.cssLocation = cssLocation; + continue; + } + // Non-default variants are keyed in themeMapping with "theme_name variant_name" + // eg: "Element Dark" + this._themeMapping[themeDisplayName] = { + cssLocation, + id: themeId + }; + } + if (defaultDarkVariant.id && defaultLightVariant.id) { + /** + * As mentioned above, if there's both a default dark and a default light variant, + * add them to themeMapping separately. + */ + this._themeMapping[themeName] = { dark: defaultDarkVariant, light: defaultLightVariant }; + } + else { + /** + * If only one default variant is found (i.e only dark default or light default but not both), + * treat it like any other variant. + */ + const variant = defaultDarkVariant.id ? defaultDarkVariant : defaultLightVariant; + this._themeMapping[`${themeName} ${variant.variantName}`] = { id: variant.id, cssLocation: variant.cssLocation }; + } + } + setTheme(themeId: string, log?: ILogItem) { this._platform.logger.wrapOrRun(log, {l: "change theme", id: themeId}, () => { const themeLocation = this._findThemeLocationFromId(themeId); From dc2d1ce700de79d1eb89fa7293d88b232bf95d45 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 31 May 2022 20:21:36 +0530 Subject: [PATCH 100/174] Remove id --- src/platform/web/ui/css/themes/element/manifest.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/web/ui/css/themes/element/manifest.json b/src/platform/web/ui/css/themes/element/manifest.json index 0858a05b..6c99e404 100644 --- a/src/platform/web/ui/css/themes/element/manifest.json +++ b/src/platform/web/ui/css/themes/element/manifest.json @@ -1,7 +1,6 @@ { "version": 1, "name": "Element", - "id": "element", "values": { "font-faces": [ { From 8de91291ddbeb42098f56ccef38f3ddca66fe448 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 2 Jun 2022 15:05:43 +0530 Subject: [PATCH 101/174] Add more methods to ThemeLoader --- src/platform/web/ThemeLoader.ts | 44 +++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index ca7ef641..f302bc0e 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -37,6 +37,11 @@ type DefaultVariant = { type ThemeInformation = NormalVariant | DefaultVariant; +export enum ColorSchemePreference { + Dark, + Light +}; + export class ThemeLoader { private _platform: Platform; private _themeMapping: Record; @@ -104,7 +109,7 @@ export class ThemeLoader { cssLocation, id: themeId }; - } + } if (defaultDarkVariant.id && defaultLightVariant.id) { /** * As mentioned above, if there's both a default dark and a default light variant, @@ -123,14 +128,14 @@ export class ThemeLoader { } setTheme(themeId: string, log?: ILogItem) { - this._platform.logger.wrapOrRun(log, {l: "change theme", id: themeId}, () => { + this._platform.logger.wrapOrRun(log, { l: "change theme", id: themeId }, () => { const themeLocation = this._findThemeLocationFromId(themeId); if (!themeLocation) { - throw new Error( `Cannot find theme location for theme "${themeId}"!`); + throw new Error(`Cannot find theme location for theme "${themeId}"!`); } this._platform.replaceStylesheet(themeLocation); this._platform.settingsStorage.setString("theme", themeId); - }); + }); } get themeMapping(): Record { @@ -146,10 +151,11 @@ export class ThemeLoader { } getDefaultTheme(): string | undefined { - if (window.matchMedia("(prefers-color-scheme: dark)").matches) { - return this._platform.config["defaultTheme"].dark; - } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { - return this._platform.config["defaultTheme"].light; + switch (this.preferredColorScheme) { + case ColorSchemePreference.Dark: + return this._platform.config["defaultTheme"].dark; + case ColorSchemePreference.Light: + return this._platform.config["defaultTheme"].light; } } @@ -166,4 +172,26 @@ export class ThemeLoader { } } } + + get preferredColorScheme(): ColorSchemePreference { + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + return ColorSchemePreference.Dark; + } + else if (window.matchMedia("(prefers-color-scheme: light)").matches) { + return ColorSchemePreference.Light; + } + throw new Error("Cannot find preferred colorscheme!"); + } + + async persistVariantToStorage(variant: string) { + await this._platform.settingsStorage.setString("theme-variant", variant); + } + + async getCurrentVariant(): Promise { + return await this._platform.settingsStorage.getString("theme-variant"); + } + + async removeVariantFromStorage(): Promise { + await this._platform.settingsStorage.remove("theme-variant"); + } } From b74f4b612bc8197b377c107149052cbd8bdffaa9 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 2 Jun 2022 15:06:20 +0530 Subject: [PATCH 102/174] Change UI --- .../session/settings/SettingsViewModel.js | 17 ++++++ .../web/ui/session/settings/SettingsView.js | 56 +++++++++++-------- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index caaa2579..aae83fb6 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -51,6 +51,7 @@ export class SettingsViewModel extends ViewModel { this.maxSentImageSizeLimit = 4000; this.pushNotifications = new PushNotificationStatus(); this._activeTheme = undefined; + this._activeVariant = undefined; } get _session() { @@ -79,6 +80,7 @@ export class SettingsViewModel extends ViewModel { this.pushNotifications.enabled = await this._session.arePushNotificationsEnabled(); if (!import.meta.env.DEV) { this._activeTheme = await this.platform.themeLoader.getActiveTheme(); + this._activeVariant = await this.platform.themeLoader.getCurrentVariant(); } this.emitChange(""); } @@ -139,6 +141,10 @@ export class SettingsViewModel extends ViewModel { return this._activeTheme; } + get activeVariant() { + return this._activeVariant; + } + setTheme(name) { this.platform.themeLoader.setTheme(name); } @@ -195,5 +201,16 @@ export class SettingsViewModel extends ViewModel { this.emitChange("themeOption"); } + get preferredColorScheme() { + return this.platform.themeLoader.preferredColorScheme; + } + + persistVariantToStorage(variant) { + this.platform.themeLoader.persistVariantToStorage(variant); + } + + removeVariantFromStorage() { + this.platform.themeLoader.removeVariantFromStorage(); + } } diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 14893aba..45178990 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -16,6 +16,7 @@ limitations under the License. import {TemplateView} from "../../general/TemplateView"; import {KeyBackupSettingsView} from "./KeyBackupSettingsView.js" +import {ColorSchemePreference} from "../../../ThemeLoader"; export class SettingsView extends TemplateView { render(t, vm) { @@ -142,38 +143,49 @@ export class SettingsView extends TemplateView { _themeOptions(t, vm) { const activeTheme = vm.activeTheme; const optionTags = []; - let isDarkSelected = null, isLightSelected = null; + const isDarkSelected = vm.activeVariant === "dark"; + const isLightSelected = vm.activeVariant === "light"; + // 1. render the dropdown containing the themes for (const [name, details] of Object.entries(vm.themeMapping)) { - let isSelected = null; - if (details.id === activeTheme) { - isSelected = true; - } - else if (details.dark?.id === activeTheme) { - isSelected = true; - isDarkSelected = true; - } - else if (details.light?.id === activeTheme) { - isSelected = true; - isLightSelected = true; - } + const isSelected = (details.id ?? details.dark?.id ?? details.light?.id) === activeTheme; optionTags.push( t.option({ value: details.id ?? "", selected: isSelected} , name)); } const select = t.select({ onChange: (e) => { const themeId = e.target.value; - vm.changeThemeOption(themeId) - } - }, optionTags); - const radioButtons = t.form({ - className: { hidden: () => select.options[select.selectedIndex].value !== "" }, - onChange: (e) => { - const selectedThemeName = select.options[select.selectedIndex].text; - const colorScheme = e.target.value; - const themeId = vm.themeMapping[selectedThemeName][colorScheme].id; + if (themeId) { + /* if the