diff --git a/.github/ISSUE_TEMPLATE/issue-report.md b/.github/ISSUE_TEMPLATE/issue-report.md index d12287f..0184d93 100644 --- a/.github/ISSUE_TEMPLATE/issue-report.md +++ b/.github/ISSUE_TEMPLATE/issue-report.md @@ -53,11 +53,12 @@ The hatch report (below) includes version information. If you cannot provide the - Self-hosted LiveSync version: -### Report from LiveSync -Open the `Hatch` pane in LiveSync settings and press `Make report`. Paste here or upload to [Gist](https://gist.github.com/) and share the link. +### Report and Logs from LiveSync +Perform a `Generate full report for opening the issue with debug info` command and provide the generated report. This contains detailed information and recent 1000 log lines, which is very helpful for debugging. **PLEASE AMEND THE REPORT TO REMOVE ANY SENSITIVE INFORMATION BEFORE PASTING.** +If too large to paste here, upload to [Gist](https://gist.github.com/) and share the link.
-Report from hatch (primary) +Report and Logs (primary) ``` @@ -65,29 +66,7 @@ Open the `Hatch` pane in LiveSync settings and press `Make report`. Paste here o
-Report from hatch (if applicable) - -``` - -``` -
- - -### Plug-in log -Enable `Verbose Log` in General Settings first, then reproduce the issue and copy the log (tap the document box icon in the ribbon). -Paste here or upload to [Gist](https://gist.github.com/) and share the link. - -
-Plug-in log (primary) - -``` - -``` -
- - -
-Plug-in log (if applicable) +Report and Logs (if applicable) ``` diff --git a/.gitignore b/.gitignore index 43e267e..a6bced9 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,6 @@ data.json cov_profile/** coverage -src/apps/cli/dist/* \ No newline at end of file +src/apps/cli/dist/* +_testdata/** +utils/bench/splitResults.csv \ No newline at end of file diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index fe22a00..f463e36 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -255,14 +255,20 @@ It depends on Obsidian detects. May toggling `Detect all extensions` of ### I hope to report the issue, but you said you needs `Report`. How to make it? -We can copy the report to the clipboard, by pressing the `Make report` button on -the `Hatch` pane. ![Screenshot](../images/hatch.png) +We can copy the report to the clipboard, by performing +`Generate full report for opening the issue with debug info` command! ### Where can I check the log? We can launch the log pane by `Show log` on the command palette. And if you have troubled something, please enable the `Verbose Log` on the `General Setting` pane. +`Generate full report for opening the issue with debug info` command also contains +the recent 1000 log lines, which is very helpful for debugging. Full-report is +already set to the verbose level, so it contains all the logs without enabling the +`Verbose Log` toggle. + +Let me note that please be sure to remove any sensitive information before sharing the report. However, the logs would not be kept so long and cleared when restarted. If you want to check the logs, please enable `Write logs into the file` temporarily. diff --git a/manifest.json b/manifest.json index 70f2561..604f917 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.64", + "version": "0.25.65", "minAppVersion": "1.7.2", "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "author": "vorotamoroz", diff --git a/package-lock.json b/package-lock.json index cf13e0d..43bf109 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.64", + "version": "0.25.65", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.64", + "version": "0.25.65", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", @@ -1851,9 +1851,9 @@ "license": "MIT" }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -1887,7 +1887,7 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-helpers/node_modules/@eslint/core": { + "node_modules/@eslint/core": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", @@ -1932,9 +1932,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -1984,19 +1984,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/json/node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@eslint/object-schema": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", @@ -2021,19 +2008,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@fidm/asn1": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@fidm/asn1/-/asn1-1.0.4.tgz", @@ -3572,20 +3546,13 @@ } }, "node_modules/@smithy/core": { - "version": "3.23.12", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.12.tgz", - "integrity": "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w==", + "version": "3.24.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz", + "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-middleware": "^4.2.12", - "@smithy/util-stream": "^4.5.20", - "@smithy/util-utf8": "^4.2.2", - "@smithy/uuid": "^1.1.2", + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, "engines": { @@ -3752,11 +3719,12 @@ } }, "node_modules/@smithy/is-array-buffer": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", - "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.3.3.tgz", + "integrity": "sha512-RRxYqjUa/n8dRVkbhyuiRarppLzt4H/AtMUEFmiHlDy8o4wrgqAdzxsk9naemzu6iX67ZV375fNmX7Q8dynGKw==", "license": "Apache-2.0", "dependencies": { + "@smithy/core": "^3.24.3", "tslib": "^2.6.2" }, "engines": { @@ -4020,9 +3988,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.1.tgz", - "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", + "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4084,12 +4052,12 @@ } }, "node_modules/@smithy/util-buffer-from": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", - "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.3.3.tgz", + "integrity": "sha512-5xlgilVaX96HdVlLZymKUa7vOTZtisOTxBJloM2J4PeRqyAWBeFIq0DnIxQISvwxT4rgJAvk7rHhB+GlCCKe8g==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.2", + "@smithy/core": "^3.24.3", "tslib": "^2.6.2" }, "engines": { @@ -4226,12 +4194,12 @@ } }, "node_modules/@smithy/util-utf8": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", - "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.3.3.tgz", + "integrity": "sha512-c1QpRBn3aMsoqE64dd4Imgjy8Pynfw+eR7GkjElquxUFSnezwYVaOFm8JcYa+Bo/5ssbEyPKcT3+4bmrWYh6eQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.2", + "@smithy/core": "^3.24.3", "tslib": "^2.6.2" }, "engines": { @@ -4521,9 +4489,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.12.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", - "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "version": "24.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", + "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", "dev": true, "license": "MIT", "dependencies": { @@ -5255,9 +5223,9 @@ "license": "MIT" }, "node_modules/@wdio/config/node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "license": "MIT", "dependencies": { @@ -5377,9 +5345,9 @@ } }, "node_modules/@wdio/repl/node_modules/@types/node": { - "version": "20.19.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", - "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5407,9 +5375,9 @@ } }, "node_modules/@wdio/types/node_modules/@types/node": { - "version": "20.19.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", - "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5532,9 +5500,9 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -5623,9 +5591,9 @@ "license": "MIT" }, "node_modules/archiver-utils/node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "license": "MIT", "dependencies": { @@ -6234,9 +6202,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -7150,9 +7118,9 @@ } }, "node_modules/dotenv": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.0.tgz", - "integrity": "sha512-kCKF62fwtzwYm0IGBNjRUjtJgMfGapII+FslMHIjMR5KTnwEmBmWLDRSnc3XSNP8bNy34tekgQyDT0hr7pERRQ==", + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -7914,9 +7882,9 @@ "license": "MIT" }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -8262,14 +8230,14 @@ } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", - "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", + "is-core-module": "^2.16.2", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", @@ -8432,19 +8400,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/eslint/node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -8453,9 +8408,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -9289,9 +9244,9 @@ "license": "MIT" }, "node_modules/globby/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -9443,9 +9398,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "dev": true, "license": "MIT", "dependencies": { @@ -9786,13 +9741,13 @@ } }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -10973,9 +10928,9 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz", + "integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -12695,9 +12650,9 @@ "license": "MIT" }, "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "license": "MIT", "dependencies": { @@ -12817,12 +12772,13 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "dev": true, "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" @@ -13119,9 +13075,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -15293,9 +15249,9 @@ } }, "node_modules/undici": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", - "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", "dev": true, "license": "MIT", "engines": { @@ -16157,9 +16113,9 @@ } }, "node_modules/vitest/node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true, "license": "MIT" }, @@ -16240,9 +16196,9 @@ } }, "node_modules/webdriver/node_modules/@types/node": { - "version": "20.19.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", - "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16250,9 +16206,9 @@ } }, "node_modules/webdriver/node_modules/undici": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", - "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", + "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", "dev": true, "license": "MIT", "engines": { @@ -16312,9 +16268,9 @@ } }, "node_modules/webdriverio/node_modules/@types/node": { - "version": "20.19.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", - "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16778,9 +16734,9 @@ "license": "BSD" }, "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "dev": true, "license": "MIT", "engines": { @@ -16848,9 +16804,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", "bin": { diff --git a/package.json b/package.json index e38acd9..61ca87a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.64", + "version": "0.25.65", "description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "main": "main.js", "type": "module", diff --git a/src/common/reportTool.ts b/src/common/reportTool.ts new file mode 100644 index 0000000..8eef539 --- /dev/null +++ b/src/common/reportTool.ts @@ -0,0 +1,142 @@ +import { REMOTE_COUCHDB, REMOTE_MINIO } from "@lib/common/models/setting.const"; +import type { ObsidianLiveSyncSettings } from "@lib/common/models/setting.type"; +import { generateCredentialObject } from "@lib/replication/httplib"; +import { parseHeaderValues } from "@lib/common/utils"; +import { requestToCouchDBWithCredentials } from "./utils"; +import { LOG_LEVEL_VERBOSE, Logger } from "@lib/common/logger"; +import { DEFAULT_SETTINGS } from "@lib/common/models/setting.const.defaults"; +import { isCloudantURI } from "@lib/pouchdb/utils_couchdb"; +import { compatGlobal } from "@lib/common/coreEnvFunctions"; +import { manifestVersion, packageVersion } from "@lib/common/coreEnvVars"; +import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; +function redactObject(obj: Record, dotted: string, redactedValue = "REDACTED") { + const keys = dotted.split("."); + let current = obj; + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + if (!(key in current)) { + current[key] = {} as Record; + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + current = current[key]; + } + const lastKey = keys[keys.length - 1]; + if (lastKey in current) { + current[lastKey] = redactedValue; + } + return obj; +} +export async function generateReport(settings: ObsidianLiveSyncSettings, core: LiveSyncBaseCore) { + let responseConfig: Record = {}; + const REDACTED = "𝑅𝐸𝐷𝐴𝐢𝑇𝐸𝐷"; + if (settings.remoteType == REMOTE_COUCHDB) { + try { + const credential = generateCredentialObject(settings); + const customHeaders = parseHeaderValues(settings.couchDB_CustomHeaders); + const r = await requestToCouchDBWithCredentials( + settings.couchDB_URI, + credential, + window.origin, + undefined, + undefined, + undefined, + customHeaders + ); + responseConfig = r.json as Record; + redactObject(responseConfig, "couch_httpd_auth.secret"); + redactObject(responseConfig, "couch_httpd_auth.authentication_db"); + redactObject(responseConfig, "couch_httpd_auth.authentication_redirect"); + redactObject(responseConfig, "couchdb.uuid"); + redactObject(responseConfig, "admins"); + redactObject(responseConfig, "users"); + redactObject(responseConfig, "chttpd_auth.secret"); + delete responseConfig["jwt_keys"]; + } catch (ex) { + Logger(ex, LOG_LEVEL_VERBOSE); + responseConfig = { + error: "Requesting information from the remote CouchDB has failed. If you are using IBM Cloudant, this is normal behaviour.", + }; + } + } else if (settings.remoteType == REMOTE_MINIO) { + responseConfig = { error: "Object Storage Synchronisation" }; + // + } + const defaultKeys = Object.keys(DEFAULT_SETTINGS) as (keyof ObsidianLiveSyncSettings)[]; + const pluginConfig = JSON.parse(JSON.stringify(settings)) as ObsidianLiveSyncSettings; + const pluginKeys = Object.keys(pluginConfig); + for (const key of pluginKeys) { + if (defaultKeys.includes(key as keyof ObsidianLiveSyncSettings)) continue; + delete pluginConfig[key as keyof ObsidianLiveSyncSettings]; + } + + pluginConfig.couchDB_DBNAME = REDACTED; + pluginConfig.couchDB_PASSWORD = REDACTED; + const scheme = pluginConfig.couchDB_URI.startsWith("http:") + ? "(HTTP)" + : pluginConfig.couchDB_URI.startsWith("https:") + ? "(HTTPS)" + : ""; + pluginConfig.couchDB_URI = isCloudantURI(pluginConfig.couchDB_URI) ? "cloudant" : `self-hosted${scheme}`; + pluginConfig.couchDB_USER = REDACTED; + pluginConfig.passphrase = REDACTED; + pluginConfig.encryptedPassphrase = REDACTED; + pluginConfig.encryptedCouchDBConnection = REDACTED; + pluginConfig.accessKey = REDACTED; + pluginConfig.secretKey = REDACTED; + const redact = (source: string) => `${REDACTED}(${source.length} letters)`; + const toSchemeOnly = (uri: string) => { + try { + return `${new URL(uri).protocol}//`; + } catch { + const matched = uri.match(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//); + return matched?.[0] ?? REDACTED; + } + }; + pluginConfig.remoteConfigurations = Object.fromEntries( + Object.entries(pluginConfig.remoteConfigurations || {}).map(([id, config]) => [ + id, + { + ...config, + uri: toSchemeOnly(config.uri), + }, + ]) + ); + pluginConfig.region = redact(pluginConfig.region); + pluginConfig.bucket = redact(pluginConfig.bucket); + pluginConfig.pluginSyncExtendedSetting = {}; + pluginConfig.P2P_AppID = redact(pluginConfig.P2P_AppID); + pluginConfig.P2P_passphrase = redact(pluginConfig.P2P_passphrase); + pluginConfig.P2P_roomID = redact(pluginConfig.P2P_roomID); + pluginConfig.P2P_relays = redact(pluginConfig.P2P_relays); + pluginConfig.jwtKey = redact(pluginConfig.jwtKey); + pluginConfig.jwtSub = redact(pluginConfig.jwtSub); + pluginConfig.jwtKid = redact(pluginConfig.jwtKid); + pluginConfig.bucketCustomHeaders = redact(pluginConfig.bucketCustomHeaders); + pluginConfig.couchDB_CustomHeaders = redact(pluginConfig.couchDB_CustomHeaders); + pluginConfig.P2P_turnCredential = redact(pluginConfig.P2P_turnCredential); + pluginConfig.P2P_turnUsername = redact(pluginConfig.P2P_turnUsername); + pluginConfig.P2P_turnServers = `(${pluginConfig.P2P_turnServers.split(",").length} servers configured)`; + const endpoint = pluginConfig.endpoint; + if (endpoint == "") { + pluginConfig.endpoint = "Not configured or AWS"; + } else { + const endpointScheme = pluginConfig.endpoint.startsWith("http:") + ? "(HTTP)" + : pluginConfig.endpoint.startsWith("https:") + ? "(HTTPS)" + : ""; + pluginConfig.endpoint = `${endpoint.indexOf(".r2.cloudflarestorage.") !== -1 ? "R2" : "self-hosted?"}(${endpointScheme})`; + } + const obsidianInfo = { + navigator: compatGlobal.navigator.userAgent, + fileSystem: core.services.vault.isStorageInsensitive() ? "insensitive" : "sensitive", + }; + const result = { + obsidianInfo, + responseConfig, + pluginConfig, + manifestVersion, + packageVersion, + }; + return result; +} diff --git a/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte b/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte index aeeee48..2ed575c 100644 --- a/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte @@ -16,10 +16,7 @@ import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; import { ConnectionStringParser } from "@lib/common/ConnectionString"; import type { P2PSyncSetting, RemoteConfiguration } from "@lib/common/models/setting.type"; - import { - activateP2PRemoteConfiguration, - createRemoteConfigurationId, - } from "@lib/serviceFeatures/remoteConfig"; + import { activateP2PRemoteConfiguration, createRemoteConfigurationId } from "@lib/serviceFeatures/remoteConfig"; import { extractP2PRoomSuffix } from "@lib/common/utils"; import { SetupManager } from "@/modules/features/SetupManager"; import SetupRemoteP2P from "@/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte"; @@ -36,9 +33,7 @@ let replicatingPeerId = $state(null); let communicatingUntil = $state>({}); const COMMUNICATION_HOLD_MS = 2500; - let syncOnReplicationSetting = $state( - core.services.setting.currentSettings()?.P2P_SyncOnReplication ?? "" - ); + let syncOnReplicationSetting = $state(core.services.setting.currentSettings()?.P2P_SyncOnReplication ?? ""); type P2PRemoteOption = { id: string; name: string; @@ -51,12 +46,19 @@ let selectingP2PRemote = $state(false); function addToList(item: string, list: string): string { - const items = list.split(",").map((e) => e.trim()).filter((e) => e); + const items = list + .split(",") + .map((e) => e.trim()) + .filter((e) => e); if (!items.includes(item)) items.push(item); return items.join(","); } function removeFromList(item: string, list: string): string { - return list.split(",").map((e) => e.trim()).filter((e) => e && e !== item).join(","); + return list + .split(",") + .map((e) => e.trim()) + .filter((e) => e && e !== item) + .join(","); } function markCommunicating(peerId: string) { @@ -409,7 +411,12 @@ {/each} - @@ -442,7 +449,8 @@
- {peer.name} : ({peer.peerId.slice(0, 8)}) + {peer.name} : + ({peer.peerId.slice(0, 8)}) {#if isCommunicating(peer.peerId)} πŸ“‘ {/if} @@ -460,11 +468,11 @@ -
+
+
SYNC -
{:else} +
+ {:else}
{getAcceptanceStatus(peer)} @@ -571,7 +585,6 @@ display: flex; flex-direction: column; gap: 1rem; - padding: 0.75rem; } .peers-section { @@ -584,7 +597,7 @@ display: flex; justify-content: space-between; align-items: center; - flex-wrap: nowrap; + flex-wrap: wrap; gap: 0.5rem; } @@ -603,8 +616,9 @@ } .remote-picker { - max-width: 14rem; - min-width: 8rem; + max-width: 10rem; + min-width: 1em; + flex-shrink: 1; height: 1.9rem; border: 1px solid var(--divider-color); border-radius: 0.4rem; @@ -648,6 +662,7 @@ .peers-header { display: flex; + flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 0.5rem; @@ -873,5 +888,4 @@ font-size: 0.9rem; padding: 1rem; } - - \ No newline at end of file + diff --git a/src/modules/essentialObsidian/ModuleObsidianEvents.ts b/src/modules/essentialObsidian/ModuleObsidianEvents.ts index 7445e11..33f0485 100644 --- a/src/modules/essentialObsidian/ModuleObsidianEvents.ts +++ b/src/modules/essentialObsidian/ModuleObsidianEvents.ts @@ -121,7 +121,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { return; } - const isHidden = document.hidden; + const isHidden = activeWindow.document.hidden; if (this.isLastHidden === isHidden) { return; } @@ -134,7 +134,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { } else { // suspend all temporary. if (this.services.appLifecycle.isSuspended()) return; - if (!this.hasFocus) return; + // Do not block resume by focus state here; visibility recovery should be enough. await this.services.appLifecycle.onResuming(); await this.services.appLifecycle.onResumed(); } diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index 16de2d9..1eb7945 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -25,7 +25,7 @@ import { EVENT_ON_UNRESOLVED_ERROR, } from "../../common/events.ts"; import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; -import { addIcon, normalizePath, Notice } from "../../deps.ts"; +import { addIcon, debounce, normalizePath, Notice, stringifyYaml, type WorkspaceLeaf } from "../../deps.ts"; import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger"; import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts"; import { serialized } from "octagonal-wheels/concurrency/lock"; @@ -41,6 +41,8 @@ import { } from "@lib/string_and_binary/path.ts"; import { MARK_LOG_NETWORK_ERROR, MARK_LOG_SEPARATOR } from "@lib/services/lib/logUtils.ts"; import { NetworkWarningStyles } from "@lib/common/models/setting.const.ts"; +import { compatGlobal } from "@lib/common/coreEnvFunctions.ts"; +import { generateReport } from "@/common/reportTool.ts"; // This module cannot be a core module because it depends on the Obsidian UI. @@ -50,18 +52,51 @@ const globalLogFunction = (message: any, level?: number, key?: string) => { const messageX = message instanceof Error ? new LiveSyncError("[Error Logged]: " + message.message, { cause: message }) - : message; + : typeof message === "string" + ? message + : JSON.stringify(message); const entry = { message: messageX, level, key } as LogEntry; recentLogEntries.value = [...recentLogEntries.value, entry]; }; setGlobalLogFunction(globalLogFunction); -let recentLogs = [] as string[]; +// Keep the recent logs in memory for display, but also keep a longer history in logForDump for when the user wants to see more logs. +// logForDump is not reactive and is only used for dumping logs when requested, while recentLogs is reactive and is used for displaying logs in the UI. +const logForDump = [] as string[]; function addLog(log: string) { - recentLogs = [...recentLogs, log].splice(-200); - logMessages.value = recentLogs; + logForDump.push(log); + while (logForDump.length > 1000) { + logForDump.shift(); + } } + +// Display log is kept separate from the full log history to optimize performance and memory usage. +// And debounce the updates to the display log to avoid excessive UI updates when there are many log entries in a short time. +const logForDisplay = [] as string[]; + +const updateLogMessage = debounce(() => { + logMessages.value = [...logForDisplay]; +}, 25); +function addDisplayLog(log: string) { + logForDisplay.push(log); + while (logForDisplay.length > 200) { + logForDisplay.shift(); + } + updateLogMessage(); +} + +const redactPatterns = [/PBKDF2 salt \(Security Seed\):.*$/]; +function redactLog(log: string) { + let redactedLog = log; + for (const pattern of redactPatterns) { + redactedLog = redactedLog.replace(pattern, (match) => { + return match.split(":")[0] + ": [REDACTED]"; + }); + } + return redactedLog; +} + // logStore.intercept(e => e.slice(Math.min(e.length - 200, 0))); const showDebugLog = false; @@ -86,15 +121,15 @@ export class ModuleLog extends AbstractObsidianModule { // const emptyMark = `\u{2003}`; function padLeftSpComputed(numI: ReactiveValue, mark: string) { const formatted = reactiveSource(""); - let timer: ReturnType | undefined = undefined; + let timer: number | undefined = undefined; let maxLen = 1; numI.onChanged((numX) => { const num = numX.value; const numLen = `${Math.abs(num)}`.length + 1; maxLen = maxLen < numLen ? numLen : maxLen; - if (timer) clearTimeout(timer); + if (timer) compatGlobal.clearTimeout(timer); if (num == 0) { - timer = setTimeout(() => { + timer = compatGlobal.setTimeout(() => { formatted.value = ""; maxLen = 1; }, 3000); @@ -323,7 +358,7 @@ export class ModuleLog extends AbstractObsidianModule { if (this.nextFrameQueue) { return; } - this.nextFrameQueue = requestAnimationFrame(() => { + this.nextFrameQueue = compatGlobal.requestAnimationFrame(() => { this.nextFrameQueue = undefined; const { message, status } = this.statusBarLabels.value; // const recent = logMessages.value; @@ -346,7 +381,8 @@ export class ModuleLog extends AbstractObsidianModule { (a, b) => (a < b.ttl ? a : b.ttl), Number.MAX_SAFE_INTEGER ); - if (this.logLines.length > 0) setTimeout(() => this.applyStatusBarText(), minimumNext - now); + if (this.logLines.length > 0) + compatGlobal.setTimeout(() => this.applyStatusBarText(), minimumNext - now); const recent = this.logLines.map((e) => e.message); const recentLogs = recent.reverse().join("\n"); if (isDirty("recentLogs", recentLogs)) this.logHistory!.innerText = recentLogs; @@ -368,7 +404,7 @@ export class ModuleLog extends AbstractObsidianModule { if (this.statusDiv) { this.statusDiv.remove(); } - document.querySelectorAll(`.livesync-status`)?.forEach((e) => e.remove()); + compatGlobal.document.querySelectorAll(`.livesync-status`)?.forEach((e) => e.remove()); return Promise.resolve(true); } _everyOnloadStart(): Promise { @@ -390,7 +426,28 @@ export class ModuleLog extends AbstractObsidianModule { void this.services.API.showWindow(VIEW_TYPE_LOG); }, }); - this.registerView(VIEW_TYPE_LOG, (leaf) => new LogPaneView(leaf, this.plugin)); + this.addCommand({ + id: "dump-debug-info", + name: "Generate full report for opening the issue with debug info", + callback: async () => { + const recentLog = [...logForDump]; + const report = await generateReport(this.services.setting.currentSettings(), this.core); + const info = { + ...report, + recentLog: recentLog.map(redactLog), + }; + const yaml = `\`\`\`\` +# ---- Debug Info Dump ---- +${stringifyYaml(info)} +\`\`\`\``; + if (await this.services.UI.promptCopyToClipboard("Debug info", yaml)) { + new Notice( + "Debug info copied to clipboard. You can paste it in the issue. Be careful as it may contain sensitive information, review it before sharing." + ); + } + }, + }); + this.registerView(VIEW_TYPE_LOG, (leaf: WorkspaceLeaf) => new LogPaneView(leaf, this.plugin)); return Promise.resolve(true); } private _everyOnloadAfterLoadSettings(): Promise { @@ -404,7 +461,7 @@ export class ModuleLog extends AbstractObsidianModule { void this.setFileStatus(); }); - const w = document.querySelectorAll(`.livesync-status`); + const w = compatGlobal.document.querySelectorAll(`.livesync-status`); w.forEach((e) => e.remove()); this.observeForLogs(); @@ -421,6 +478,8 @@ export class ModuleLog extends AbstractObsidianModule { this.statusBar?.addClass("syncstatusbar"); } this.adjustStatusDivPosition(); + this._log("Log module loaded", LOG_LEVEL_INFO); + this._log("Verbose log", LOG_LEVEL_VERBOSE); return Promise.resolve(true); } @@ -444,11 +503,12 @@ export class ModuleLog extends AbstractObsidianModule { if (level == LOG_LEVEL_DEBUG && !showDebugLog) { return; } + let memoOnly = false; if (level <= LOG_LEVEL_INFO && this.settings && this.settings.lessInformationInLog) { - return; + memoOnly = true; } if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL_VERBOSE) { - return; + memoOnly = true; } const vaultName = this.services.vault.getVaultName(); const now = new Date(); @@ -469,6 +529,15 @@ export class ModuleLog extends AbstractObsidianModule { ? `${errorInfo}` : JSON.stringify(message, null, 2); const newMessage = timestamp + "->" + messageContent; + + if (this.settings?.writeLogToTheFile) { + this.writeLogToTheFile(now, vaultName, newMessage); + } + addLog(newMessage); + if (memoOnly) { + return; + } + addDisplayLog(newMessage); if (message instanceof Error) { console.error(vaultName + ":" + newMessage); } else if (level >= LOG_LEVEL_INFO) { @@ -479,10 +548,6 @@ export class ModuleLog extends AbstractObsidianModule { if (!this.settings?.showOnlyIconsOnEditor) { this.statusLog.value = messageContent; } - if (this.settings?.writeLogToTheFile) { - this.writeLogToTheFile(now, vaultName, newMessage); - } - addLog(newMessage); this.logLines.push({ ttl: now.getTime() + 3000, message: newMessage }); if (level >= LOG_LEVEL_NOTICE) { diff --git a/src/modules/features/SettingDialogue/PaneHatch.ts b/src/modules/features/SettingDialogue/PaneHatch.ts index ebfb377..d9ca27c 100644 --- a/src/modules/features/SettingDialogue/PaneHatch.ts +++ b/src/modules/features/SettingDialogue/PaneHatch.ts @@ -39,6 +39,7 @@ import { EVENT_REQUEST_SHOW_HISTORY } from "../../../common/obsidianEvents.ts"; import { generateCredentialObject } from "../../../lib/src/replication/httplib.ts"; import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts"; import type { PageFunctions } from "./SettingPane.ts"; +import { generateReport } from "@/common/reportTool.ts"; export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, { addPanel }: PageFunctions): void { // const hatchWarn = this.createEl(paneEl, "div", { text: `To stop the boot up sequence for fixing problems on databases, you can put redflag.md on top of your vault (Rebooting obsidian is required).` }); // hatchWarn.addClass("op-warn-info"); @@ -69,140 +70,14 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, eventHub.emitEvent(EVENT_REQUEST_RUN_FIX_INCOMPLETE); }) ); + new Setting(paneEl).setName($msg("Prepare the 'report' to create an issue")).addButton((button) => button .setButtonText($msg("Copy Report to clipboard")) .setCta() .setDisabled(false) .onClick(async () => { - let responseConfig: any = {}; - const REDACTED = "𝑅𝐸𝐷𝐴𝐢𝑇𝐸𝐷"; - if (this.editingSettings.remoteType == REMOTE_COUCHDB) { - try { - const credential = generateCredentialObject(this.editingSettings); - const customHeaders = parseHeaderValues(this.editingSettings.couchDB_CustomHeaders); - const r = await requestToCouchDBWithCredentials( - this.editingSettings.couchDB_URI, - credential, - window.origin, - undefined, - undefined, - undefined, - customHeaders - ); - - Logger(JSON.stringify(r.json, null, 2)); - - responseConfig = r.json; - responseConfig["couch_httpd_auth"].secret = REDACTED; - responseConfig["couch_httpd_auth"].authentication_db = REDACTED; - responseConfig["couch_httpd_auth"].authentication_redirect = REDACTED; - responseConfig["couchdb"].uuid = REDACTED; - responseConfig["admins"] = REDACTED; - delete responseConfig["jwt_keys"]; - if ("secret" in responseConfig["chttpd_auth"]) - responseConfig["chttpd_auth"].secret = REDACTED; - } catch (ex) { - Logger(ex, LOG_LEVEL_VERBOSE); - responseConfig = { - error: "Requesting information from the remote CouchDB has failed. If you are using IBM Cloudant, this is normal behaviour.", - }; - } - } else if (this.editingSettings.remoteType == REMOTE_MINIO) { - responseConfig = { error: "Object Storage Synchronisation" }; - // - } - const defaultKeys = Object.keys(DEFAULT_SETTINGS) as (keyof ObsidianLiveSyncSettings)[]; - const pluginConfig = JSON.parse(JSON.stringify(this.editingSettings)) as ObsidianLiveSyncSettings; - const pluginKeys = Object.keys(pluginConfig); - for (const key of pluginKeys) { - if (defaultKeys.includes(key as any)) continue; - delete pluginConfig[key as keyof ObsidianLiveSyncSettings]; - } - - pluginConfig.couchDB_DBNAME = REDACTED; - pluginConfig.couchDB_PASSWORD = REDACTED; - const scheme = pluginConfig.couchDB_URI.startsWith("http:") - ? "(HTTP)" - : pluginConfig.couchDB_URI.startsWith("https:") - ? "(HTTPS)" - : ""; - pluginConfig.couchDB_URI = isCloudantURI(pluginConfig.couchDB_URI) - ? "cloudant" - : `self-hosted${scheme}`; - pluginConfig.couchDB_USER = REDACTED; - pluginConfig.passphrase = REDACTED; - pluginConfig.encryptedPassphrase = REDACTED; - pluginConfig.encryptedCouchDBConnection = REDACTED; - pluginConfig.accessKey = REDACTED; - pluginConfig.secretKey = REDACTED; - const redact = (source: string) => `${REDACTED}(${source.length} letters)`; - const toSchemeOnly = (uri: string) => { - try { - return `${new URL(uri).protocol}//`; - } catch { - const matched = uri.match(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//); - return matched?.[0] ?? REDACTED; - } - }; - pluginConfig.remoteConfigurations = Object.fromEntries( - Object.entries(pluginConfig.remoteConfigurations || {}).map(([id, config]) => [ - id, - { - ...config, - uri: toSchemeOnly(config.uri), - }, - ]) - ); - pluginConfig.region = redact(pluginConfig.region); - pluginConfig.bucket = redact(pluginConfig.bucket); - pluginConfig.pluginSyncExtendedSetting = {}; - pluginConfig.P2P_AppID = redact(pluginConfig.P2P_AppID); - pluginConfig.P2P_passphrase = redact(pluginConfig.P2P_passphrase); - pluginConfig.P2P_roomID = redact(pluginConfig.P2P_roomID); - pluginConfig.P2P_relays = redact(pluginConfig.P2P_relays); - pluginConfig.jwtKey = redact(pluginConfig.jwtKey); - pluginConfig.jwtSub = redact(pluginConfig.jwtSub); - pluginConfig.jwtKid = redact(pluginConfig.jwtKid); - pluginConfig.bucketCustomHeaders = redact(pluginConfig.bucketCustomHeaders); - pluginConfig.couchDB_CustomHeaders = redact(pluginConfig.couchDB_CustomHeaders); - pluginConfig.P2P_turnCredential = redact(pluginConfig.P2P_turnCredential); - pluginConfig.P2P_turnUsername = redact(pluginConfig.P2P_turnUsername); - pluginConfig.P2P_turnServers = `(${pluginConfig.P2P_turnServers.split(",").length} servers configured)`; - const endpoint = pluginConfig.endpoint; - if (endpoint == "") { - pluginConfig.endpoint = "Not configured or AWS"; - } else { - const endpointScheme = pluginConfig.endpoint.startsWith("http:") - ? "(HTTP)" - : pluginConfig.endpoint.startsWith("https:") - ? "(HTTPS)" - : ""; - pluginConfig.endpoint = `${endpoint.indexOf(".r2.cloudflarestorage.") !== -1 ? "R2" : "self-hosted?"}(${endpointScheme})`; - } - const obsidianInfo = { - navigator: navigator.userAgent, - fileSystem: this.core.services.vault.isStorageInsensitive() ? "insensitive" : "sensitive", - }; - const msgConfig = `# ---- Obsidian info ---- -${stringifyYaml(obsidianInfo)} ---- -# ---- remote config ---- -${stringifyYaml(responseConfig)} ---- -# ---- Plug-in config ---- -${stringifyYaml({ - version: this.manifestVersion, - ...pluginConfig, -})}`; - console.log(msgConfig); - if ((await this.services.UI.promptCopyToClipboard("Generated report", msgConfig)) == true) { - // await navigator.clipboard.writeText(msgConfig); - // Logger( - // `Generated report has been copied to clipboard. Please report the issue with this! Thank you for your cooperation!`, - // LOG_LEVEL_NOTICE - // ); - } + await this.app.commands.executeCommandById("obsidian-livesync:dump-debug-info"); }) ); new Setting(paneEl) diff --git a/updates.md b/updates.md index 4eedc41..7ee19e1 100644 --- a/updates.md +++ b/updates.md @@ -11,6 +11,24 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid ### Improved - Many messages related to tweak mismatch resolution have been updated for clarity. +## 0.25.65 + +19th May, 2026 + +### Fixed +- Fix an issue about resuming from background on iOS (#888). +- Now Chunk Splitter: `V3: Fine Deduplication` is working fine again (#866). + - It has some drawbacks, such as fewer chunks are generated. However, it makes less transfer and storage when the files are modified but not completely changed. +- Unsynchronised local changes (which means changes that have not been sent) are now correctly preserved as a conflict (Thank you so much for @SeleiXi!). +- Avoid creating a new revision when the current and conflicted revisions have identical content (Thank you so much for @daichi-629). + +### Improved +- Improved the error verbosity on concurrent processing during the start-up process. +- Now the `report` includes recent logs (of verbosity `verbose` even settings is not set to `verbose`). +- Updating logs is now debounced to avoid excessive updates during rapid log generation. +- Added a `Generate full report for opening the issue with debug info` command to the command palette, which generates a report without opening the settings dialogue. + + ## 0.25.64 17th May, 2026 diff --git a/utils/bench/splitPiecesRabinKarp.ts b/utils/bench/splitPiecesRabinKarp.ts new file mode 100644 index 0000000..1c4642c --- /dev/null +++ b/utils/bench/splitPiecesRabinKarp.ts @@ -0,0 +1,197 @@ +import { glob } from "glob"; +import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { promises as fs } from "node:fs"; +import { isPlainText, shouldSplitAsPlainText } from "../../src/lib/src/string_and_binary/path"; +import { splitPiecesRabinKarp } from "../../src/lib/src/string_and_binary/chunks"; +import { + PREFERRED_BASE, + PREFERRED_JOURNAL_SYNC, + PREFERRED_SETTING_CLOUDANT, + PREFERRED_SETTING_SELF_HOSTED, +} from "../../src/lib/src/common/models/setting.const.preferred"; +import { type ObsidianLiveSyncSettings, DEFAULT_SETTINGS, MAX_DOC_SIZE_BIN } from "../../src/lib/src/common/types"; + +async function blobFromString(content: string): Promise { + return new Blob([content], { type: "text/plain" }); +} + +const preferred = PREFERRED_BASE; +const preferredJournal = PREFERRED_JOURNAL_SYNC; +const preferredCouchDB = PREFERRED_SETTING_SELF_HOSTED; +const preferredIBM = PREFERRED_SETTING_CLOUDANT; + +function computeChunkSize(overlay: Partial) { + const settings = { ...DEFAULT_SETTINGS, ...overlay }; + const maxChunkSize = Math.floor(MAX_DOC_SIZE_BIN * ((settings.customChunkSize || 0) * 1 + 1)); + const pieceSize = maxChunkSize; + + const minimumChunkSize = settings.minimumChunkSize; + return { pieceSize, minimumChunkSize }; +} + +async function testSplit( + splitPiecesRabinKarpFn: typeof splitPiecesRabinKarp, + content: Blob, + settingsOverlay: Partial +) { + const { pieceSize, minimumChunkSize } = computeChunkSize(settingsOverlay); + const isPlain = content.type === "text/plain"; + const chunkGenerator = await splitPiecesRabinKarpFn(content, pieceSize, isPlain, minimumChunkSize); + const chunks = [] as string[]; + for await (const chunk of chunkGenerator()) { + chunks.push(chunk); + } + // if there are few chunks, calculate average chunk size except the last chunk which can be smaller due to the way the algorithm works, especially for small files. + const averageChunkSize = + chunks.length > 1 + ? chunks.slice(0, -1).reduce((acc, chunk) => acc + chunk.length, 0) / (chunks.length - 1) + : chunks.reduce((acc, chunk) => acc + chunk.length, 0) / chunks.length; + const lastChunk = chunks[chunks.length - 1]; + // compute minimum chunk size if the last chunk is not the smallest. + const nonLastChunkSizes = chunks.slice(0, -1).map((c) => c.length); + const minChunkSize = nonLastChunkSizes.length > 0 ? Math.min(...nonLastChunkSizes) : lastChunk.length; + const result = { + isPlain, + originalSize: content.size, + chunkCount: chunks.length, + totalLength: chunks.reduce((acc, chunk) => acc + chunk.length, 0), + averageChunkSize: averageChunkSize, + maxChunkSize: Math.max(...chunks.map((c) => c.length)), + minChunkSize: minChunkSize, + uniqueChunks: new Set(chunks).size, + chunks: chunks, + }; + return result; +} +const __filename = fileURLToPath(import.meta.url); +const __dirname = resolve(__filename, ".."); +async function loadFileAsBlob(filePath: string): Promise { + if (shouldSplitAsPlainText(filePath)) { + const content = await fs.readFile(filePath, "utf-8"); + return blobFromString(content); + } else { + const buffer = await fs.readFile(filePath); + return new Blob([buffer]); + } +} +const testProfiles = [ + { name: "CouchDB", settings: preferredCouchDB }, + { name: "IBM Cloudant", settings: preferredIBM }, + { name: "Journal Sync", settings: preferredJournal }, + // { name: "Base", settings: preferred }, +]; +function modifyBlob(blob: Blob, position: number, insertText: string): Blob { + const before = blob.slice(0, position); + const after = blob.slice(position); + const insert = new Blob([insertText], { type: blob.type }); + return new Blob([before, insert, after], { type: blob.type }); +} +async function main() { + const results = [] as string[][]; + console.log("directory:", __dirname); + const findPath = resolve(__dirname, "../../"); + console.warn("CWD:", findPath); + let testFiles = await glob("**/*.*", { + cwd: findPath, + maxDepth: 20, + ignore: ["**/node_modules/**", "**/.obsidian/**", "**/dist/**", "**/build/**", "**/out/**"], + }); + testFiles = testFiles.filter((file) => { + const ext = file.split(".").pop()?.toLowerCase() || ""; + return ["md", "txt", "json", "csv", "png"].includes(ext); + }); + const header = [ + "Profile", + "Implementation", + "Edition", + "File", + "Mode", + "Original Size (bytes)", + "Chunk Count", + "Average Chunk Size", + "Max Chunk Size", + "Min Chunk Size", + "Unique Chunks", + "Shared Chunks", + "Savings", + "Newly added (count)", + "Newly consumed (bytes)", + ]; + for (const profile of testProfiles) { + console.log(`Testing profile: ${profile.name}`); + for (const fn of [splitPiecesRabinKarp]) { + const funcProfile = fn !== splitPiecesRabinKarp ? "Old" : "New"; + console.log(`Testing function: ${funcProfile}`); + for (const file of testFiles) { + const filePath = resolve(findPath, file); + const isPlain = shouldSplitAsPlainText(filePath); + const content = await loadFileAsBlob(filePath); + console.log(`Testing file: ${file} (size: ${content.size} bytes)`); + const result = await testSplit(fn, content, profile.settings); + const chunkSizes = result.chunks.map((c) => c.length); + const savings = result.originalSize - chunkSizes.reduce((acc, size) => acc + size, 0); + // console.log(`Result for ${file}:`, result); + results.push([ + `${profile.name}`, + funcProfile, + "original", + file, + isPlain ? "plain" : "binary", + content.size.toString(), + result.chunkCount.toString(), + result.averageChunkSize.toFixed(2), + result.maxChunkSize.toString(), + result.minChunkSize.toString(), + result.uniqueChunks.toString(), + "", + savings.toString(), + "", + "", + ]); + // add editions (inserting "*") to content on head, 5%, middle, 95%, tail to see if it affects the chunking + const editions = [ + { name: "head", content: modifyBlob(content, 0, "*") }, + { name: "5%", content: modifyBlob(content, Math.floor(content.size * 0.05), "*") }, + { name: "middle", content: modifyBlob(content, Math.floor(content.size * 0.5), "*") }, + { name: "95%", content: modifyBlob(content, Math.floor(content.size * 0.95), "*") }, + { name: "tail", content: modifyBlob(content, content.size, "*") }, + ]; + const baseChunks = result.chunks; + for (const edition of editions) { + console.log(`Testing edition: ${edition.name}`); + const editionResult = await testSplit(fn, edition.content, profile.settings); + const sharedChunks = editionResult.chunks.filter((chunk) => baseChunks.includes(chunk)).length; + const newChunks = editionResult.chunks.filter((chunk) => !baseChunks.includes(chunk)); + const editionResultChunkLength = editionResult.chunks.map((c) => c.length); + // console.log(`Result for edition ${edition.name} of ${file}:`, editionResult); + const editionSavings = + editionResult.originalSize - editionResultChunkLength.reduce((acc, size) => acc + size, 0); + // newly added chunks size : + const newChunksSize = newChunks.reduce((acc, chunk) => acc + chunk.length, 0); + results.push([ + `${profile.name}`, + funcProfile, + `${edition.name}`, + file, + isPlain ? "plain" : "binary", + edition.content.size.toString(), + editionResult.chunkCount.toString(), + editionResult.averageChunkSize.toFixed(2), + editionResult.maxChunkSize.toString(), + editionResult.minChunkSize.toString(), + editionResult.uniqueChunks.toString(), + sharedChunks.toString(), + editionSavings.toString(), + newChunks.length.toString(), + newChunksSize.toString(), + ]); + } + } + } + } + + results.unshift(header); + await fs.writeFile(resolve(__dirname, "splitResults.csv"), results.map((r) => r.join(",")).join("\n")); +} +main();