mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-19 14:01:21 +00:00
Compare commits
14 Commits
feat_tweak
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67da3964e5 | ||
|
|
45ebc7eb6b | ||
|
|
cc5ead68bc | ||
|
|
9b9e4f22f3 | ||
|
|
7823f46053 | ||
|
|
d6d8e548b3 | ||
|
|
44b1ed7610 | ||
|
|
5786da5534 | ||
|
|
042a80dd44 | ||
|
|
ee30f6cd6d | ||
|
|
977a300808 | ||
|
|
a392ccab6a | ||
|
|
dfdfa5383b | ||
|
|
a08294ab16 |
31
.github/ISSUE_TEMPLATE/issue-report.md
vendored
31
.github/ISSUE_TEMPLATE/issue-report.md
vendored
@@ -53,11 +53,12 @@ The hatch report (below) includes version information. If you cannot provide the
|
||||
|
||||
- Self-hosted LiveSync version: <!-- e.g. 0.23.0 — find it in Obsidian Settings → Community Plugins -->
|
||||
|
||||
### 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.
|
||||
|
||||
<details>
|
||||
<summary>Report from hatch (primary)</summary>
|
||||
<summary>Report and Logs (primary)</summary>
|
||||
|
||||
```
|
||||
<!-- paste here or link to Gist -->
|
||||
@@ -65,29 +66,7 @@ Open the `Hatch` pane in LiveSync settings and press `Make report`. Paste here o
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Report from hatch (if applicable)</summary>
|
||||
|
||||
```
|
||||
<!-- paste here or link to Gist -->
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
<details>
|
||||
<summary>Plug-in log (primary)</summary>
|
||||
|
||||
```
|
||||
<!-- paste here or link to Gist -->
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Plug-in log (if applicable)</summary>
|
||||
<summary>Report and Logs (if applicable)</summary>
|
||||
|
||||
```
|
||||
<!-- paste here or link to Gist -->
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -28,4 +28,6 @@ data.json
|
||||
cov_profile/**
|
||||
|
||||
coverage
|
||||
src/apps/cli/dist/*
|
||||
src/apps/cli/dist/*
|
||||
_testdata/**
|
||||
utils/bench/splitResults.csv
|
||||
@@ -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. 
|
||||
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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
258
package-lock.json
generated
258
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
142
src/common/reportTool.ts
Normal file
142
src/common/reportTool.ts
Normal file
@@ -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<string, any>, 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<string, any>;
|
||||
}
|
||||
// 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<string, any> = {};
|
||||
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<string, any>;
|
||||
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;
|
||||
}
|
||||
@@ -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<string | null>(null);
|
||||
let communicatingUntil = $state<Record<string, number>>({});
|
||||
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 @@
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<button class="icon-button" onclick={() => createAndSelectP2PRemote()} title="Create P2P remote" aria-label="Create P2P remote">
|
||||
<button
|
||||
class="icon-button"
|
||||
onclick={() => createAndSelectP2PRemote()}
|
||||
title="Create P2P remote"
|
||||
aria-label="Create P2P remote"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
@@ -442,7 +449,8 @@
|
||||
<div class="peer-item">
|
||||
<div class="peer-info">
|
||||
<div class="peer-name">
|
||||
{peer.name} : <span class="peer-id-mini" title={peer.peerId}>({peer.peerId.slice(0, 8)})</span>
|
||||
{peer.name} :
|
||||
<span class="peer-id-mini" title={peer.peerId}>({peer.peerId.slice(0, 8)})</span>
|
||||
{#if isCommunicating(peer.peerId)}
|
||||
<span class="comm-icon" title="Communicating" aria-label="Communicating">📡</span>
|
||||
{/if}
|
||||
@@ -460,11 +468,11 @@
|
||||
<button
|
||||
class="emoji-button"
|
||||
disabled={replicatingPeerId !== null}
|
||||
title={replicatingPeerId === peer.peerId ? 'Replicating...' : 'Replicate now'}
|
||||
aria-label={replicatingPeerId === peer.peerId ? 'Replicating' : 'Replicate now'}
|
||||
title={replicatingPeerId === peer.peerId ? "Replicating..." : "Replicate now"}
|
||||
aria-label={replicatingPeerId === peer.peerId ? "Replicating" : "Replicate now"}
|
||||
onclick={() => startReplication(peer)}
|
||||
>
|
||||
{replicatingPeerId === peer.peerId ? '⏳' : '🔄'}
|
||||
{replicatingPeerId === peer.peerId ? "⏳" : "🔄"}
|
||||
</button>
|
||||
<button
|
||||
class="action-button"
|
||||
@@ -478,25 +486,31 @@
|
||||
<span class="decision-label">WATCH</span>
|
||||
<button
|
||||
class="emoji-button {isWatching(peer.peerId) ? 'is-watching' : ''}"
|
||||
title={isWatching(peer.peerId) ? 'Watching this peer \u2014 click to stop' : 'Watch this peer\'s changes'}
|
||||
aria-label={isWatching(peer.peerId) ? 'Stop watching' : 'Watch peer'}
|
||||
title={isWatching(peer.peerId)
|
||||
? "Watching this peer \u2014 click to stop"
|
||||
: "Watch this peer's changes"}
|
||||
aria-label={isWatching(peer.peerId) ? "Stop watching" : "Watch peer"}
|
||||
disabled={!canEditP2PSettings()}
|
||||
onclick={() => toggleWatch(peer.peerId)}
|
||||
>
|
||||
{isWatching(peer.peerId) ? '🔔' : '🔕'}
|
||||
{isWatching(peer.peerId) ? "🔔" : "🔕"}
|
||||
</button>
|
||||
</div> <div class="decision-row watch-row">
|
||||
</div>
|
||||
<div class="decision-row watch-row">
|
||||
<span class="decision-label">SYNC</span>
|
||||
<button
|
||||
class="emoji-button {isSyncTarget(peer.name) ? 'is-watching' : ''}"
|
||||
title={isSyncTarget(peer.name) ? 'Sync target \u2014 click to remove' : 'Set as sync target'}
|
||||
aria-label={isSyncTarget(peer.name) ? 'Remove sync target' : 'Set sync target'}
|
||||
title={isSyncTarget(peer.name)
|
||||
? "Sync target \u2014 click to remove"
|
||||
: "Set as sync target"}
|
||||
aria-label={isSyncTarget(peer.name) ? "Remove sync target" : "Set sync target"}
|
||||
disabled={!canEditP2PSettings()}
|
||||
onclick={() => toggleSyncTarget(peer)}
|
||||
>
|
||||
{isSyncTarget(peer.name) ? '🔗' : '⛓️💥'}
|
||||
{isSyncTarget(peer.name) ? "🔗" : "⛓️💥"}
|
||||
</button>
|
||||
</div> {:else}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="decision-status">
|
||||
<span class="badge status-chip {getAcceptanceStatusClass(peer)}">
|
||||
{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;
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: 37c3d78c6c...6abcea69eb
@@ -2,11 +2,9 @@ import { Logger, LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
|
||||
import { extractObject } from "octagonal-wheels/object";
|
||||
import {
|
||||
TweakValuesShouldMatchedTemplate,
|
||||
TweakValuesTemplate,
|
||||
IncompatibleChanges,
|
||||
confName,
|
||||
type TweakValues,
|
||||
type ObsidianLiveSyncSettings,
|
||||
type RemoteDBSettings,
|
||||
IncompatibleChangesInSpecificPattern,
|
||||
CompatibleButLossyChanges,
|
||||
@@ -18,105 +16,7 @@ import type { InjectableServiceHub } from "../../lib/src/services/InjectableServ
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
import { REMOTE_P2P } from "@lib/common/models/setting.const.ts";
|
||||
|
||||
function valueToString(value: any) {
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
return `${value}`;
|
||||
}
|
||||
|
||||
export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||
private _hasNotifiedAutoAcceptCompatibleUndefined = false;
|
||||
|
||||
private _collectMismatchedTweakKeys(current: TweakValues, preferred: Partial<TweakValues>) {
|
||||
const items = Object.keys(
|
||||
TweakValuesShouldMatchedTemplate
|
||||
) as (keyof typeof TweakValuesShouldMatchedTemplate)[];
|
||||
return items.filter((key) => current[key] !== preferred[key]);
|
||||
}
|
||||
|
||||
private _selectNewerTweakSide(current: TweakValues, preferred: Partial<TweakValues>): "REMOTE" | "CURRENT" {
|
||||
Logger(`Modified: ${current.tweakModified} (current) vs ${preferred.tweakModified} (preferred)`);
|
||||
const currentModified = current.tweakModified;
|
||||
const preferredModified = preferred.tweakModified;
|
||||
// debugger;
|
||||
const hasCurrentModified = typeof currentModified === "number" && currentModified > 0;
|
||||
const hasPreferredModified = typeof preferredModified === "number" && preferredModified > 0;
|
||||
|
||||
if (!hasCurrentModified && !hasPreferredModified) return "REMOTE";
|
||||
if (!hasCurrentModified) return "REMOTE";
|
||||
if (!hasPreferredModified) return "CURRENT";
|
||||
if (preferredModified >= currentModified) return "REMOTE";
|
||||
return "CURRENT";
|
||||
}
|
||||
|
||||
private async _shouldAutoAcceptCompatibleLossy(
|
||||
current: TweakValues,
|
||||
preferred: Partial<TweakValues>,
|
||||
mismatchedKeys: (keyof typeof TweakValuesShouldMatchedTemplate)[]
|
||||
): Promise<"REMOTE" | "CURRENT" | undefined> {
|
||||
if (mismatchedKeys.length === 0) return undefined;
|
||||
const hasOnlyCompatibleLossyMismatches = mismatchedKeys.every(
|
||||
(key) => CompatibleButLossyChanges.indexOf(key) !== -1
|
||||
);
|
||||
if (!hasOnlyCompatibleLossyMismatches) return undefined;
|
||||
|
||||
if (this.settings.autoAcceptCompatibleTweak === undefined) {
|
||||
if (this._hasNotifiedAutoAcceptCompatibleUndefined) {
|
||||
return undefined;
|
||||
}
|
||||
this._hasNotifiedAutoAcceptCompatibleUndefined = true;
|
||||
const CHOICE_ENABLE = $msg("TweakMismatchResolve.Action.EnableAutoAcceptCompatible");
|
||||
const CHOICE_DISABLE = $msg("TweakMismatchResolve.Action.DisableAutoAcceptCompatible");
|
||||
const CHOICES = [CHOICE_ENABLE, CHOICE_DISABLE] as const;
|
||||
const message = $msg("TweakMismatchResolve.Message.AutoAcceptCompatibleUndefined");
|
||||
const ret = await this.core.confirm.askSelectStringDialogue(message, CHOICES, {
|
||||
title: $msg("TweakMismatchResolve.Title.AutoAcceptCompatible"),
|
||||
timeout: 0,
|
||||
defaultAction: CHOICE_ENABLE,
|
||||
});
|
||||
if (ret !== CHOICE_ENABLE) {
|
||||
return undefined;
|
||||
}
|
||||
await this.services.setting.applyPartial(
|
||||
{
|
||||
autoAcceptCompatibleTweak: true,
|
||||
},
|
||||
true
|
||||
);
|
||||
Logger("Auto-accept for compatible tweak mismatch has been enabled.");
|
||||
}
|
||||
|
||||
if (this.settings.autoAcceptCompatibleTweak !== true) return undefined;
|
||||
return this._selectNewerTweakSide(current, preferred);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook before saving settings, to check if there are changes in tweak values, and if so,
|
||||
* update the tweakModified timestamp to current time.
|
||||
* This allows other devices to know that the tweak values have been changed and decide whether to accept the new values based on the modification time.
|
||||
* @param next
|
||||
* @param previous
|
||||
* @returns
|
||||
*/
|
||||
async _onBeforeSaveSettingData(next: ObsidianLiveSyncSettings, previous: ObsidianLiveSyncSettings) {
|
||||
const tweakKeys = Object.keys(TweakValuesTemplate) as (keyof TweakValues)[];
|
||||
const tweakKeysForUpdate = tweakKeys.filter((key) => key !== "tweakModified");
|
||||
const hasChangedTweak = tweakKeysForUpdate.some((key) => next[key] !== previous[key]);
|
||||
if (!hasChangedTweak) return;
|
||||
Logger(
|
||||
`Some tweak values have been changed. ${tweakKeysForUpdate.filter((key) => next[key] !== previous[key]).join(", ")}`
|
||||
);
|
||||
const modified = Date.now();
|
||||
Logger(`Modified: ${modified}`);
|
||||
return await Promise.resolve({
|
||||
tweakModified: modified,
|
||||
});
|
||||
}
|
||||
|
||||
async _anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
|
||||
if (!this.core.replicator.tweakSettingsMismatched && !this.core.replicator.preferredTweakValue) return false;
|
||||
const preferred = this.core.replicator.preferredTweakValue;
|
||||
@@ -127,16 +27,10 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||
if (ret == "IGNORE") return true;
|
||||
}
|
||||
|
||||
async _checkAndAskResolvingMismatchedTweaks(preferred: TweakValues): Promise<[TweakValues | boolean, boolean]> {
|
||||
const mine = extractObject(TweakValuesShouldMatchedTemplate, this.settings) as TweakValues;
|
||||
const mismatchedKeys = this._collectMismatchedTweakKeys(mine, preferred);
|
||||
const autoAcceptSide = await this._shouldAutoAcceptCompatibleLossy(mine, preferred, mismatchedKeys);
|
||||
if (autoAcceptSide === "REMOTE") {
|
||||
return [{ ...mine, ...preferred }, false];
|
||||
}
|
||||
if (autoAcceptSide === "CURRENT") {
|
||||
return [true, false];
|
||||
}
|
||||
async _checkAndAskResolvingMismatchedTweaks(
|
||||
preferred: Partial<TweakValues>
|
||||
): Promise<[TweakValues | boolean, boolean]> {
|
||||
const mine = extractObject(TweakValuesShouldMatchedTemplate, this.settings);
|
||||
const items = Object.entries(TweakValuesShouldMatchedTemplate);
|
||||
let rebuildRequired = false;
|
||||
let rebuildRecommended = false;
|
||||
@@ -175,8 +69,8 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||
tableRows.push(
|
||||
$msg("TweakMismatchResolve.Table.Row", {
|
||||
name: confName(key),
|
||||
self: valueToString(valueMine),
|
||||
remote: valueToString(valuePreferred),
|
||||
self: valueMine,
|
||||
remote: valuePreferred,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -243,7 +137,9 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||
if (!tweaks) {
|
||||
return "IGNORE";
|
||||
}
|
||||
const [conf, rebuildRequired] = await this.services.tweakValue.checkAndAskResolvingMismatched(tweaks);
|
||||
const preferred = extractObject(TweakValuesShouldMatchedTemplate, tweaks);
|
||||
|
||||
const [conf, rebuildRequired] = await this.services.tweakValue.checkAndAskResolvingMismatched(preferred);
|
||||
if (!conf) return "IGNORE";
|
||||
|
||||
if (conf === true) {
|
||||
@@ -251,7 +147,10 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||
if (rebuildRequired) {
|
||||
await this.core.rebuilder.$rebuildRemote();
|
||||
}
|
||||
Logger($msg("TweakMismatchResolve.Message.remoteUpdated"), LOG_LEVEL_NOTICE);
|
||||
Logger(
|
||||
`Tweak values on the remote server have been updated. Your other device will see this message.`,
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
return "CHECKAGAIN";
|
||||
}
|
||||
if (conf) {
|
||||
@@ -261,7 +160,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||
if (rebuildRequired) {
|
||||
await this.core.rebuilder.$fetchLocal();
|
||||
}
|
||||
Logger($msg("TweakMismatchResolve.Message.mineUpdated"), LOG_LEVEL_NOTICE);
|
||||
Logger(`Configuration has been updated as configured by the other device.`, LOG_LEVEL_NOTICE);
|
||||
return "CHECKAGAIN";
|
||||
}
|
||||
return "IGNORE";
|
||||
@@ -302,16 +201,6 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||
trialSetting: RemoteDBSettings,
|
||||
preferred: TweakValues
|
||||
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
||||
const localTweaks = extractObject(TweakValuesTemplate, this.settings) as TweakValues;
|
||||
const mismatchedKeys = this._collectMismatchedTweakKeys(localTweaks, preferred);
|
||||
const autoAcceptSide = await this._shouldAutoAcceptCompatibleLossy(localTweaks, preferred, mismatchedKeys);
|
||||
if (autoAcceptSide === "REMOTE") {
|
||||
return { result: { ...trialSetting, ...preferred }, requireFetch: false };
|
||||
}
|
||||
if (autoAcceptSide === "CURRENT") {
|
||||
return { result: false, requireFetch: false };
|
||||
}
|
||||
|
||||
const items = Object.entries(TweakValuesShouldMatchedTemplate);
|
||||
let rebuildRequired = false;
|
||||
let rebuildRecommended = false;
|
||||
@@ -322,8 +211,8 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||
// const items = [mine,preferred]
|
||||
for (const v of items) {
|
||||
const key = v[0] as keyof typeof TweakValuesShouldMatchedTemplate;
|
||||
const remoteValueForDisplay = escapeMarkdownValue(valueToString(preferred[key]));
|
||||
const currentValueForDisplay = escapeMarkdownValue(valueToString((trialSetting as TweakValues)?.[key]));
|
||||
const remoteValueForDisplay = escapeMarkdownValue(preferred[key]);
|
||||
const currentValueForDisplay = `${escapeMarkdownValue((trialSetting as TweakValues)?.[key])}`;
|
||||
if ((trialSetting as TweakValues)?.[key] !== preferred[key]) {
|
||||
if (IncompatibleChanges.indexOf(key) !== -1) {
|
||||
rebuildRequired = true;
|
||||
@@ -400,7 +289,6 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||
}
|
||||
|
||||
override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||
services.setting.onBeforeSaveSettingData.addHandler(this._onBeforeSaveSettingData.bind(this));
|
||||
services.tweakValue.fetchRemotePreferred.setHandler(this._fetchRemotePreferredTweakValues.bind(this));
|
||||
services.tweakValue.checkAndAskResolvingMismatched.setHandler(
|
||||
this._checkAndAskResolvingMismatchedTweaks.bind(this)
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_SETTINGS, REMOTE_COUCHDB, type RemoteDBSettings, type TweakValues } from "@lib/common/types";
|
||||
import { ModuleResolvingMismatchedTweaks } from "./ModuleResolveMismatchedTweaks";
|
||||
|
||||
function createModule(settingsOverride: Partial<typeof DEFAULT_SETTINGS> = {}) {
|
||||
const askSelectStringDialogue = vi.fn(async () => undefined);
|
||||
const core = {
|
||||
_services: {
|
||||
API: {
|
||||
addLog: vi.fn(),
|
||||
addCommand: vi.fn(),
|
||||
registerWindow: vi.fn(),
|
||||
addRibbonIcon: vi.fn(),
|
||||
registerProtocolHandler: vi.fn(),
|
||||
},
|
||||
setting: {
|
||||
saveSettingData: vi.fn(async () => undefined),
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
...DEFAULT_SETTINGS,
|
||||
remoteType: REMOTE_COUCHDB,
|
||||
...settingsOverride,
|
||||
},
|
||||
confirm: {
|
||||
askSelectStringDialogue,
|
||||
},
|
||||
} as any;
|
||||
|
||||
Object.defineProperty(core, "services", {
|
||||
get() {
|
||||
return core._services;
|
||||
},
|
||||
});
|
||||
|
||||
const module = new ModuleResolvingMismatchedTweaks(core);
|
||||
return { module, core, askSelectStringDialogue };
|
||||
}
|
||||
|
||||
describe("ModuleResolvingMismatchedTweaks", () => {
|
||||
it("should auto-accept compatible mismatches on connect check using newer remote tweakModified", async () => {
|
||||
const { module, askSelectStringDialogue } = createModule({
|
||||
autoAcceptCompatibleTweak: true,
|
||||
hashAlg: "xxhash64",
|
||||
tweakModified: 100,
|
||||
});
|
||||
|
||||
const preferred = {
|
||||
...(DEFAULT_SETTINGS as unknown as TweakValues),
|
||||
hashAlg: "xxhash32",
|
||||
tweakModified: 200,
|
||||
} as Partial<TweakValues>;
|
||||
|
||||
const [conf, rebuild] = await module._checkAndAskResolvingMismatchedTweaks(preferred);
|
||||
|
||||
expect(conf).toEqual(preferred);
|
||||
expect(rebuild).toBe(false);
|
||||
expect(askSelectStringDialogue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should fallback to manual confirmation when mismatches are mixed on connect check", async () => {
|
||||
const { module, askSelectStringDialogue } = createModule({
|
||||
autoAcceptCompatibleTweak: true,
|
||||
hashAlg: "xxhash64",
|
||||
encrypt: false,
|
||||
tweakModified: 100,
|
||||
});
|
||||
|
||||
const preferred = {
|
||||
...(DEFAULT_SETTINGS as unknown as TweakValues),
|
||||
hashAlg: "xxhash32",
|
||||
encrypt: true,
|
||||
tweakModified: 200,
|
||||
} as Partial<TweakValues>;
|
||||
|
||||
const [conf, rebuild] = await module._checkAndAskResolvingMismatchedTweaks(preferred);
|
||||
|
||||
expect(conf).toBe(false);
|
||||
expect(rebuild).toBe(false);
|
||||
expect(askSelectStringDialogue).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should auto-accept compatible mismatches on remote-config check using newer local tweakModified", async () => {
|
||||
const { module, askSelectStringDialogue } = createModule({
|
||||
autoAcceptCompatibleTweak: true,
|
||||
hashAlg: "xxhash64",
|
||||
tweakModified: 300,
|
||||
});
|
||||
|
||||
const trialSetting = {
|
||||
...DEFAULT_SETTINGS,
|
||||
remoteType: REMOTE_COUCHDB,
|
||||
hashAlg: "xxhash64",
|
||||
tweakModified: 300,
|
||||
} as RemoteDBSettings;
|
||||
|
||||
const preferred = {
|
||||
...(trialSetting as unknown as TweakValues),
|
||||
hashAlg: "xxhash32",
|
||||
tweakModified: 200,
|
||||
} as TweakValues;
|
||||
|
||||
const result = await module._askUseRemoteConfiguration(trialSetting, preferred);
|
||||
|
||||
expect(result).toEqual({ result: false, requireFetch: false });
|
||||
expect(askSelectStringDialogue).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<number>, mark: string) {
|
||||
const formatted = reactiveSource("");
|
||||
let timer: ReturnType<typeof setTimeout> | 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<boolean> {
|
||||
@@ -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<boolean> {
|
||||
@@ -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) {
|
||||
|
||||
@@ -35,7 +35,6 @@ export function paneAdvanced(this: ObsidianLiveSyncSettingTab, paneEl: HTMLEleme
|
||||
clampMin: 10,
|
||||
onUpdate: this.onlyOnCouchDB,
|
||||
});
|
||||
new Setting(paneEl).setClass("wizardHidden").autoWireToggle("autoAcceptCompatibleTweak");
|
||||
// new Setting(paneEl)
|
||||
// .setClass("wizardHidden")
|
||||
// .autoWireToggle("sendChunksBulk", { onUpdate: onlyOnCouchDB })
|
||||
|
||||
@@ -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)
|
||||
|
||||
18
updates.md
18
updates.md
@@ -3,13 +3,23 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||
|
||||
The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope.
|
||||
|
||||
## Unreleased
|
||||
## 0.25.65
|
||||
|
||||
### New features
|
||||
- Implement auto-accept compatible tweak setting and enhance mismatch resolution logic.
|
||||
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
|
||||
- Many messages related to tweak mismatch resolution have been updated for clarity.
|
||||
- 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
|
||||
|
||||
|
||||
197
utils/bench/splitPiecesRabinKarp.ts
Normal file
197
utils/bench/splitPiecesRabinKarp.ts
Normal file
@@ -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<Blob> {
|
||||
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<ObsidianLiveSyncSettings>) {
|
||||
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<ObsidianLiveSyncSettings>
|
||||
) {
|
||||
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<Blob> {
|
||||
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();
|
||||
Reference in New Issue
Block a user