Compare commits

...

10 Commits

Author SHA1 Message Date
vorotamoroz
236f2293ce Remove notice. 2022-06-10 01:28:41 +09:00
vorotamoroz
4cb908cf62 Fixed migration problem. 2022-06-10 01:26:55 +09:00
vorotamoroz
fab2327937 fix a typo. 2022-06-09 18:22:38 +09:00
vorotamoroz
0837648aa6 Add a note 2022-06-09 18:20:51 +09:00
vorotamoroz
58dcc13b50 Bumped 2022-06-09 17:45:32 +09:00
vorotamoroz
e2da4ec454 # Fixed
- Illegible coloring of the Diff dialog.

# Implemented
- On-the-fly encryption and decryption in replication.
- Text splitting algorithms updated
(use a bit more memory (which is saved by On-the-fly enc-dec), but faster than old algorithms.)
- Garbage collector is now decent and memory saving.

# Internal things
- Refactored so much.
2022-06-09 17:44:08 +09:00
vorotamoroz
f613f1b887 New feature:
- Add database configuration check & fixing tool
2022-05-10 13:43:50 +09:00
vorotamoroz
88ef7c316a Fixed:
- Do not show error message when synchronization run automatically .
2022-05-09 11:08:10 +09:00
vorotamoroz
3fbecdf567 Fixed:
- Newly created files could not be synchronized.
2022-05-08 00:02:34 +09:00
vorotamoroz
5db3a374a9 Fixed:
- Freezing LiveSync on mobile devices.
2022-05-06 18:14:45 +09:00
17 changed files with 1225 additions and 699 deletions

View File

@@ -1,3 +1,5 @@
NOTE: This document surely became outdated. I'll improve this doc in a while. but your contributions are always welcome.
# Settings of this plugin
## Remote Database Configurations
@@ -19,14 +21,51 @@ The Database name to synchronize.
If not exist, created automatically.
### Use the old connecting method
Since v0.8.0, Self-hosted LiveSync uses Obsidian's API to connect to the CouchDB instead of the browser API.
This method will increase the performance and avoid troubles with the CORS.
But it doesn't been well tested yet. If you are troubled, please disable this option once.
This option has been removed at v0.10.0
### End to End Encryption
Encrypt your database. It affects only the database, your files are left as plain.
The encryption algorithm is AES-GCM.
Note: If you want to use "Plugins and their settings", you have to enable this.
### Passphrase
The passphrase to used as the key of encryption. Please use the long text.
### Apply
To enable End-to-End encryption, there must be no items of the same content encrypted with different passphrases to avoid attackers guessing passphrases. Self-hosted LiveSync uses crc32 of the chunks, It is really a must.
So, this plugin completely deletes everything from both local and remote databases before enabling it and then synchronizing again.
To enable, "Apply and send" from the most powerful device.
If you want to synchronize an existing database, set passphrase and press "Just apply".
- Apply and send
1. Initialize the Local Database and set (or clear) passphrase, put all files into the database again.
2. Initialize the Remote Database.
3. Lock the Remote Database.
4. Send it all.
This process is simply heavy. Using a PC or Mac is preferred.
- Apply and receive
1. Initialize the Local Database and set (or clear) the passphrase.
2. Unlock the Remote Database.
3. Retrieve all and decrypt to file.
When running these operations, every synchronization settings is disabled.
### Test Database connection
You can check the connection by clicking this button.
### Check database configuration
You can check and modify your CouchDB's configuration from here directly.
## Local Database Configurations
"Local Database" is created inside your obsidian.
@@ -56,39 +95,6 @@ Note: If you want to use "Use history", this vault must be set to 0.
### Manual Garbage Collect
Run "Garbage Collection" manually.
### End to End Encryption
Encrypt your database. It affects only the database, your files are left as plain.
The encryption algorithm is AES-GCM.
Note: If you want to use "Plugins and their settings", you have to enable this.
### Passphrase
The passphrase to used as the key of encryption. Please use the long text.
### Apply
To enable End-to-End encryption, there must be no items of the same content encrypted with different passphrases to avoid attackers guessing passphrases. Self-hosted LiveSync uses crc32 of the chunks, It is really a must.
So, this plugin completely deletes everything from both local and remote databases before enabling it and then synchronizing again.
To enable, "Apply and send" from the most powerful device, and "Apply and receive" from every other device.
- Apply and send
1. Initialize the Local Database and set (or clear) passphrase, put all files into the database again.
2. Initialize the Remote Database.
3. Lock the Remote Database.
4. Send it all.
This process is simply heavy. Using a PC or Mac is preferred.
- Apply and receive
1. Initialize the Local Database and set (or clear) the passphrase.
2. Unlock the Remote Database.
3. Retrieve all and decrypt to file.
When running these operations, every synchronization settings is disabled.
**And even your passphrase is wrong, It doesn't be checked before the plugin really decrypts. So If you set the wrong passphrase and run "Apply and Receive", you will get an amount of decryption error. But, this is the specification.**
### minimum chunk size and LongLine threshold
The configuration of chunk splitting.

View File

@@ -1,3 +1,5 @@
注意:少し内容が古くなっています。
# このプラグインの設定項目
## Remote Database Configurations
@@ -19,9 +21,44 @@ CouchDBのURIを入力します。Cloudantの場合は「External Endpoint(prefe
⚠️存在しない場合は、テストや接続を行った際、自動的に作成されます[^1]。
[^1]:権限がない場合は自動作成には失敗します。
### End to End Encryption
データベースを暗号化します。この効果はデータベースに格納されるデータに限られ、ディスク上のファイルは平文のままです。
暗号化はAES-GCMを使用して行っています。
### Passphrase
暗号化を行う際に使用するパスフレーズです。充分に長いものを使用してください。
### Apply
End to End 暗号化を行うに当たって、異なるパスフレーズで暗号化された同一の内容を入手されることは避けるべきです。また、Self-hosted LiveSyncはコンテンツのcrc32を重複回避に使用しているため、その点でも攻撃が有効になってしまいます。
そのため、End to End 暗号化を有効にする際には、ローカル、リモートすべてのデータベースをいったん破棄し、新しいパスフレーズで暗号化された内容のみを、改めて同期し直します。
有効化するには、一番体力のある端末からApply and sendを行います。
既に存在するリモートと同期する場合は、設定してJust applyを行ってください。
- Apply and send
1. ローカルのデータベースを初期化しパスフレーズを設定(またはクリア)します。その後、すべてのファイルをもう一度データベースに登録します。
2. リモートのデータベースを初期化します。
3. リモートのデータベースをロックし、他の端末を締め出します。
4. すべて再送信します。
負荷と時間がかかるため、デスクトップから行う方が好ましいです。
- Apply and receive
1. ローカルのデータベースを初期化し、パスフレーズを設定(またはクリア)します。
2. リモートのデータベースにかかっているロックを解除します。
3. すべて受信して、復号します。
どちらのオペレーションも、実行するとすべての同期設定が無効化されます。
### Test Database connection
上記の設定でデータベースに接続できるか確認します。
### Check database configuration
ここから直接CouchDBの設定を確認・変更できます。
## Local Database Configurations
端末内に作成されるデータベースの設定です。
@@ -49,35 +86,6 @@ Obsidianでのファイル操作が終わってから指定秒数が経過した
### Manual Garbage Collect
上記のGarbage Collectionを手動で行います。
### End to End Encryption
データベースを暗号化します。この効果はデータベースに格納されるデータに限られ、ディスク上のファイルは平文のままです。
暗号化はAES-GCMを使用して行っています。
### Passphrase
暗号化を行う際に使用するパスフレーズです。充分に長いものを使用してください。
### Apply
End to End 暗号化を行うに当たって、異なるパスフレーズで暗号化された同一の内容を入手されることは避けるべきです。また、Self-hosted LiveSyncはコンテンツのcrc32を重複回避に使用しているため、その点でも攻撃が有効になってしまいます。
そのため、End to End 暗号化を有効にする際には、ローカル、リモートすべてのデータベースをいったん破棄し、新しいパスフレーズで暗号化された内容のみを、改めて同期し直します。
有効化するには、一番体力のある端末からApply and sendを行い、他の端末でApply and receiveを行います。
- Apply and send
1. ローカルのデータベースを初期化しパスフレーズを設定(またはクリア)します。その後、すべてのファイルをもう一度データベースに登録します。
2. リモートのデータベースを初期化します。
3. リモートのデータベースをロックし、他の端末を締め出します。
4. すべて再送信します。
負荷と時間がかかるため、デスクトップから行う方が好ましいです。
- Apply and receive
1. ローカルのデータベースを初期化し、パスフレーズを設定(またはクリア)します。
2. リモートのデータベースにかかっているロックを解除します。
3. すべて受信して、復号します。
どちらのオペレーションも、実行するとすべての同期設定が無効化されます。
**また、パスフレーズのチェックは、実際に復号するまで行いません。そのため、パスフレーズを間違えて設定し、Apply and receiveで同期を行うと、大量のエラーが発生します。これは仕様です。**
### minimum chunk size と LongLine threshold
チャンクの分割についての設定です。
Self-hosted LiveSyncは一つのチャンクのサイズを最低minimum chunk size文字確保した上で、できるだけ効率的に同期できるよう、ートを分割してチャンクを作成します。

1
lib Submodule

Submodule lib added at 315ef99845

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "0.8.5",
"version": "0.10.1",
"minAppVersion": "0.9.12",
"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",

15
package-lock.json generated
View File

@@ -1,17 +1,18 @@
{
"name": "obsidian-livesync",
"version": "0.8.5",
"version": "0.10.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "obsidian-livesync",
"version": "0.8.5",
"version": "0.10.1",
"license": "MIT",
"dependencies": {
"diff-match-patch": "^1.0.5",
"esbuild": "0.13.12",
"esbuild-svelte": "^0.6.0",
"idb": "^7.0.1",
"svelte-preprocess": "^4.10.2",
"xxhash-wasm": "^0.4.2"
},
@@ -2125,6 +2126,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/idb": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz",
"integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg=="
},
"node_modules/ignore": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz",
@@ -5077,6 +5083,11 @@
"has-symbols": "^1.0.2"
}
},
"idb": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz",
"integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg=="
},
"ignore": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.8.5",
"version": "0.10.1",
"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",
@@ -37,6 +37,7 @@
"diff-match-patch": "^1.0.5",
"esbuild": "0.13.12",
"esbuild-svelte": "^0.6.0",
"idb": "^7.0.1",
"svelte-preprocess": "^4.10.2",
"xxhash-wasm": "^0.4.2"
}

File diff suppressed because one or more lines are too long

View File

@@ -1,23 +1,25 @@
{
"name": "pouchdb-browser-webpack",
"version": "1.0.0",
"description": "pouchdb-browser webpack",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode=production --node-env=production",
"build:dev": "webpack --mode=development",
"build:prod": "webpack --mode=production --node-env=production",
"watch": "webpack --watch"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"pouchdb-browser": "^7.3.0"
},
"devDependencies": {
"webpack": "^5.58.1",
"webpack-cli": "^4.9.0"
}
"name": "pouchdb-browser-webpack",
"version": "1.0.0",
"description": "pouchdb-browser webpack",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode=production --node-env=production",
"build:dev": "webpack --mode=development",
"build:prod": "webpack --mode=production --node-env=production",
"watch": "webpack --watch"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"pouchdb-browser": "^7.3.0",
"transform-pouch": "^2.0.0",
"pouchdb-find": "^7.3.0"
},
"devDependencies": {
"webpack": "^5.58.1",
"webpack-cli": "^4.9.0"
}
}

View File

@@ -1,4 +1,10 @@
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-var-requires */
// This module just webpacks pouchdb-browser
import * as PouchDB_src from "pouchdb-browser";
const PouchDB = PouchDB_src.default;
// import * as PouchDB_src from "pouchdb-browser";
const pouch = require("pouchdb-browser").default;
const find = require("pouchdb-find").default;
const transform = require("transform-pouch");
const PouchDB = pouch.plugin(find).plugin(transform);
export { PouchDB };

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom } from "obsidian";
import { EntryDoc, LOG_LEVEL } from "./lib/src/types";
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl } from "obsidian";
import { EntryDoc, LOG_LEVEL, RemoteDBSettings } from "./lib/src/types";
import { path2id, id2path } from "./utils";
import { NewNotice, runWithLock } from "./lib/src/utils";
import { Logger } from "./lib/src/logger";
import { connectRemoteCouchDB } from "./utils_couchdb";
import { checkSyncInfo, connectRemoteCouchDBWithSetting } from "./utils_couchdb";
import { testCrypt } from "./lib/src/e2ee";
import ObsidianLiveSyncPlugin from "./main";
@@ -15,14 +15,16 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin = plugin;
}
async testConnection(): Promise<void> {
const db = await connectRemoteCouchDB(
this.plugin.settings.couchDB_URI + (this.plugin.settings.couchDB_DBNAME == "" ? "" : "/" + this.plugin.settings.couchDB_DBNAME),
{
username: this.plugin.settings.couchDB_USER,
password: this.plugin.settings.couchDB_PASSWORD,
},
this.plugin.settings.disableRequestURI
);
// const db = await connectRemoteCouchDB(
// this.plugin.settings.couchDB_URI + (this.plugin.settings.couchDB_DBNAME == "" ? "" : "/" + this.plugin.settings.couchDB_DBNAME),
// {
// username: this.plugin.settings.couchDB_USER,
// password: this.plugin.settings.couchDB_PASSWORD,
// },
// this.plugin.settings.disableRequestURI,
// this.plugin.settings.encrypt ? this.plugin.settings.passphrase : this.plugin.settings.encrypt
// );
const db = await connectRemoteCouchDBWithSetting(this.plugin.settings, this.plugin.localDatabase.isMobile);
if (typeof db === "string") {
this.plugin.addLog(`could not connect to ${this.plugin.settings.couchDB_URI} : ${this.plugin.settings.couchDB_DBNAME} \n(${db})`, LOG_LEVEL.NOTICE);
return;
@@ -78,7 +80,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
const containerRemoteDatabaseEl = containerEl.createDiv();
containerRemoteDatabaseEl.createEl("h3", { text: "Remote Database configuration" });
const syncWarn = containerRemoteDatabaseEl.createEl("div", { text: `These settings are kept locked while automatic synchronization options are enabled. Disable these options in the "Sync Settings" tab to unlock.` });
syncWarn.addClass("op-warn");
syncWarn.addClass("op-warn-info");
syncWarn.addClass("sls-hidden");
const isAnySyncEnabled = (): boolean => {
@@ -170,14 +172,126 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.couchDB_DBNAME = value;
await this.plugin.saveSettings();
})
),
new Setting(containerRemoteDatabaseEl).setName("Use the old connecting method").addToggle((toggle) =>
toggle.setValue(this.plugin.settings.disableRequestURI).onChange(async (value) => {
this.plugin.settings.disableRequestURI = value;
)
// new Setting(containerRemoteDatabaseEl)
// .setDesc("This feature is locked in mobile")
// .setName("Use the old connecting method")
// .addToggle((toggle) => {
// toggle.setValue(this.plugin.settings.disableRequestURI).onChange(async (value) => {
// this.plugin.settings.disableRequestURI = value;
// await this.plugin.saveSettings();
// });
// toggle.setDisabled(this.plugin.isMobile);
// return toggle;
// })
);
new Setting(containerRemoteDatabaseEl)
.setName("End to End Encryption")
.setDesc("Encrypting contents on the database.")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.workingEncrypt).onChange(async (value) => {
this.plugin.settings.workingEncrypt = value;
phasspharase.setDisabled(!value);
await this.plugin.saveSettings();
})
);
const phasspharase = new Setting(containerRemoteDatabaseEl)
.setName("Passphrase")
.setDesc("Encrypting passphrase")
.addText((text) => {
text.setPlaceholder("")
.setValue(this.plugin.settings.workingPassphrase)
.onChange(async (value) => {
this.plugin.settings.workingPassphrase = value;
await this.plugin.saveSettings();
});
text.inputEl.setAttribute("type", "password");
});
phasspharase.setDisabled(!this.plugin.settings.workingEncrypt);
containerRemoteDatabaseEl.createEl("div", {
text: "If you change the passphrase, rebuilding the remote database is required. Please press 'Apply and send'. Or, If you have configured it to connect to an existing database, click 'Just apply'.",
});
const checkWorkingPassphrase = async (): Promise<boolean> => {
const settingForCheck: RemoteDBSettings = {
...this.plugin.settings,
encrypt: this.plugin.settings.workingEncrypt,
passphrase: this.plugin.settings.workingPassphrase,
};
console.dir(settingForCheck);
const db = await connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.localDatabase.isMobile);
if (typeof db === "string") {
Logger("Could not connect to the database.", LOG_LEVEL.NOTICE);
return false;
} else {
if (await checkSyncInfo(db.db)) {
// Logger("Database connected", LOG_LEVEL.NOTICE);
return true;
} else {
Logger("Failed to read remote database", LOG_LEVEL.NOTICE);
return false;
}
}
};
const applyEncryption = async (sendToServer: boolean) => {
if (this.plugin.settings.workingEncrypt && this.plugin.settings.workingPassphrase == "") {
Logger("If you enable encryption, you have to set the passphrase", LOG_LEVEL.NOTICE);
return;
}
if (this.plugin.settings.workingEncrypt && !(await testCrypt())) {
Logger("WARNING! Your device would not support encryption.", LOG_LEVEL.NOTICE);
return;
}
if (!(await checkWorkingPassphrase())) {
return;
}
if (!this.plugin.settings.workingEncrypt) {
this.plugin.settings.workingPassphrase = "";
}
this.plugin.settings.liveSync = false;
this.plugin.settings.periodicReplication = false;
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.encrypt = this.plugin.settings.workingEncrypt;
this.plugin.settings.passphrase = this.plugin.settings.workingPassphrase;
await this.plugin.saveSettings();
// await this.plugin.resetLocalDatabase();
if (sendToServer) {
await this.plugin.initializeDatabase(true);
await this.plugin.markRemoteLocked();
await this.plugin.tryResetRemoteDatabase();
await this.plugin.markRemoteLocked();
await this.plugin.replicateAllToServer(true);
} else {
await this.plugin.markRemoteResolved();
await this.plugin.replicate(true);
}
};
new Setting(containerRemoteDatabaseEl)
.setName("Apply")
.setDesc("apply encryption settinngs, and re-initialize remote database")
.addButton((button) =>
button
.setButtonText("Apply and send")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-left")
.onClick(async () => {
await applyEncryption(true);
})
)
);
.addButton((button) =>
button
.setButtonText("Just apply")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-right")
.onClick(async () => {
await applyEncryption(false);
})
);
new Setting(containerRemoteDatabaseEl)
.setName("Test Database Connection")
@@ -191,6 +305,174 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
})
);
new Setting(containerRemoteDatabaseEl)
.setName("Check database configuration")
// .setDesc("Open database connection. If the remote database is not found and you have the privilege to create a database, the database will be created.")
.addButton((button) =>
button
.setButtonText("Check")
.setDisabled(false)
.onClick(async () => {
const checkConfig = async () => {
try {
const requestToCouchDB = async (baseUri: string, username: string, password: string, origin: string, key?: string, body?: string) => {
const utf8str = String.fromCharCode.apply(null, new TextEncoder().encode(`${username}:${password}`));
const encoded = window.btoa(utf8str);
const authHeader = "Basic " + encoded;
// const origin = "capacitor://localhost";
const transformedHeaders: Record<string, string> = { authorization: authHeader, origin: origin };
const uri = `${baseUri}/_node/_local/_config${key ? "/" + key : ""}`;
const requestParam: RequestUrlParam = {
url: uri,
method: body ? "PUT" : "GET",
headers: transformedHeaders,
contentType: "application/json",
body: body ? JSON.stringify(body) : undefined,
};
return await requestUrl(requestParam);
};
const r = await requestToCouchDB(this.plugin.settings.couchDB_URI, this.plugin.settings.couchDB_USER, this.plugin.settings.couchDB_PASSWORD, window.origin);
Logger(JSON.stringify(r.json, null, 2));
const responseConfig = r.json;
const emptyDiv = createDiv();
emptyDiv.innerHTML = "<span></span>";
checkResultDiv.replaceChildren(...[emptyDiv]);
const addResult = (msg: string, classes?: string[]) => {
const tmpDiv = createDiv();
tmpDiv.addClass("ob-btn-config-fix");
if (classes) {
tmpDiv.addClasses(classes);
}
tmpDiv.innerHTML = `${msg}`;
checkResultDiv.appendChild(tmpDiv);
};
const addConfigFixButton = (title: string, key: string, value: string) => {
const tmpDiv = createDiv();
tmpDiv.addClass("ob-btn-config-fix");
tmpDiv.innerHTML = `<label>${title}</label><button>Fix</button>`;
const x = checkResultDiv.appendChild(tmpDiv);
x.querySelector("button").addEventListener("click", async () => {
console.dir({ key, value });
const res = await requestToCouchDB(this.plugin.settings.couchDB_URI, this.plugin.settings.couchDB_USER, this.plugin.settings.couchDB_PASSWORD, undefined, key, value);
console.dir(res);
if (res.status == 200) {
Logger(`${title} successfly updated`, LOG_LEVEL.NOTICE);
checkResultDiv.removeChild(x);
checkConfig();
} else {
Logger(`${title} failed`, LOG_LEVEL.NOTICE);
Logger(res.text);
}
});
};
addResult("---Notice---", ["ob-btn-config-head"]);
addResult(
"If the server configuration is not persistent (e.g., running on docker), the values set from here will also be volatile. Once you are able to connect, please reflect the settings in the server's local.ini.",
["ob-btn-config-info"]
);
addResult("Your configuration is dumped to Log", ["ob-btn-config-info"]);
addResult("--Config check--", ["ob-btn-config-head"]);
// Admin check
// for database creation and deletion
if (!(this.plugin.settings.couchDB_USER in responseConfig.admins)) {
addResult(`⚠ You do not have administrative privileges.`);
} else {
addResult("✔ You have administrative privileges.");
}
// HTTP user-authorization check
if (responseConfig?.chttpd?.require_valid_user != "true") {
addResult("❗ chttpd.require_valid_user looks like wrong.");
addConfigFixButton("Set chttpd.require_valid_user = true", "chttpd/require_valid_user", "true");
} else {
addResult("✔ chttpd.require_valid_user is ok.");
}
if (responseConfig?.chttpd_auth?.require_valid_user != "true") {
addResult("❗ chttpd_auth.require_valid_user looks like wrong.");
addConfigFixButton("Set chttpd_auth.require_valid_user = true", "chttpd_auth/require_valid_user", "true");
} else {
addResult("✔ chttpd_auth.require_valid_user is ok.");
}
// HTTPD check
// Check Authentication header
if (!responseConfig?.httpd["WWW-Authenticate"]) {
addResult("❗ httpd.WWW-Authenticate is missing");
addConfigFixButton("Set httpd.WWW-Authenticate", "httpd/WWW-Authenticate", 'Basic realm="couchdb"');
} else {
addResult("✔ httpd.WWW-Authenticate is ok.");
}
if (responseConfig?.httpd?.enable_cors != "true") {
addResult("❗ httpd.enable_cors is wrong");
addConfigFixButton("Set httpd.enable_cors", "httpd/enable_cors", "true");
} else {
addResult("✔ httpd.enable_cors is ok.");
}
// CORS check
// checking connectivity for mobile
if (responseConfig?.cors?.credentials != "true") {
addResult("❗ cors.credentials is wrong");
addConfigFixButton("Set cors.credentials", "cors/credentials", "true");
} else {
addResult("✔ cors.credentials is ok.");
}
const ConfiguredOrigins = ((responseConfig?.cors?.origins ?? "") + "").split(",");
if (
responseConfig?.cors?.origins == "*" ||
(ConfiguredOrigins.indexOf("app://obsidian.md") !== -1 && ConfiguredOrigins.indexOf("capacitor://localhost") !== -1 && ConfiguredOrigins.indexOf("http://localhost") !== -1)
) {
addResult("✔ cors.origins is ok.");
} else {
addResult("❗ cors.origins is wrong");
addConfigFixButton("Set cors.origins", "cors/origins", "app://obsidian.md,capacitor://localhost,http://localhost");
}
addResult("--Connection check--", ["ob-btn-config-head"]);
addResult(`Current origin:${window.location.origin}`);
// Request header check
const origins = ["app://obsidian.md", "capacitor://localhost", "http://localhost"];
for (const org of origins) {
const rr = await requestToCouchDB(this.plugin.settings.couchDB_URI, this.plugin.settings.couchDB_USER, this.plugin.settings.couchDB_PASSWORD, org);
const responseHeaders = Object.entries(rr.headers)
.map((e) => {
e[0] = (e[0] + "").toLowerCase();
return e;
})
.reduce((obj, [key, val]) => {
obj[key] = val;
return obj;
}, {});
addResult(`Origin check:${org}`);
if (responseHeaders["access-control-allow-credentials"] != "true") {
addResult("❗ CORS is not allowing credential");
} else {
addResult("✔ CORS credential OK");
}
if (responseHeaders["access-control-allow-origin"] != org) {
addResult(`❗ CORS Origin is unmatched:${origin}->${responseHeaders["access-control-allow-origin"]}`);
} else {
addResult("✔ CORS origin OK");
}
}
addResult("--Done--", ["ob-btn-config-haed"]);
addResult("If you have some trouble with Connection-check even though all Config-check has been passed, Please check your reverse proxy's configuration.", ["ob-btn-config-info"]);
} catch (ex) {
Logger(`Checking configration failed`);
Logger(ex);
}
};
await checkConfig();
})
);
const checkResultDiv = containerRemoteDatabaseEl.createEl("div", {
text: "",
});
addScreenElement("0", containerRemoteDatabaseEl);
const containerLocalDatabaseEl = containerEl.createDiv();
containerLocalDatabaseEl.createEl("h3", { text: "Local Database configuration" });
@@ -234,88 +516,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.plugin.garbageCollect();
})
);
new Setting(containerLocalDatabaseEl)
.setName("End to End Encryption")
.setDesc("Encrypting contents on the database.")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.workingEncrypt).onChange(async (value) => {
this.plugin.settings.workingEncrypt = value;
phasspharase.setDisabled(!value);
await this.plugin.saveSettings();
})
);
const phasspharase = new Setting(containerLocalDatabaseEl)
.setName("Passphrase")
.setDesc("Encrypting passphrase")
.addText((text) => {
text.setPlaceholder("")
.setValue(this.plugin.settings.workingPassphrase)
.onChange(async (value) => {
this.plugin.settings.workingPassphrase = value;
await this.plugin.saveSettings();
});
text.inputEl.setAttribute("type", "password");
});
phasspharase.setDisabled(!this.plugin.settings.workingEncrypt);
containerLocalDatabaseEl.createEl("div", {
text: "When you change any encryption enabled or passphrase, you have to reset all databases to make sure that the last password is unused and erase encrypted data from anywhere. This operation will not lost your vault if you are fully synced.",
});
const applyEncryption = async (sendToServer: boolean) => {
if (this.plugin.settings.workingEncrypt && this.plugin.settings.workingPassphrase == "") {
Logger("If you enable encryption, you have to set the passphrase", LOG_LEVEL.NOTICE);
return;
}
if (this.plugin.settings.workingEncrypt && !(await testCrypt())) {
Logger("WARNING! Your device would not support encryption.", LOG_LEVEL.NOTICE);
return;
}
if (!this.plugin.settings.workingEncrypt) {
this.plugin.settings.workingPassphrase = "";
}
this.plugin.settings.liveSync = false;
this.plugin.settings.periodicReplication = false;
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.encrypt = this.plugin.settings.workingEncrypt;
this.plugin.settings.passphrase = this.plugin.settings.workingPassphrase;
await this.plugin.saveSettings();
await this.plugin.resetLocalDatabase();
if (sendToServer) {
await this.plugin.initializeDatabase(true);
await this.plugin.markRemoteLocked();
await this.plugin.tryResetRemoteDatabase();
await this.plugin.markRemoteLocked();
await this.plugin.replicateAllToServer(true);
} else {
await this.plugin.markRemoteResolved();
await this.plugin.replicate(true);
}
};
new Setting(containerLocalDatabaseEl)
.setName("Apply")
.setDesc("apply encryption settinngs, and re-initialize database")
.addButton((button) =>
button
.setButtonText("Apply and send")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-left")
.onClick(async () => {
await applyEncryption(true);
})
)
.addButton((button) =>
button
.setButtonText("Apply and receive")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-right")
.onClick(async () => {
await applyEncryption(false);
})
);
containerLocalDatabaseEl.createEl("div", {
text: sanitizeHTMLToDom(`Advanced settings<br>
@@ -656,7 +856,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}
}
const hatchWarn = containerHatchEl.createEl("div", { text: `To stop the bootup sequence for fixing problems on databases, you can put redflag.md on top of your vault (Rebooting obsidian is required).` });
hatchWarn.addClass("op-warn");
hatchWarn.addClass("op-warn-info");
const dropHistory = async (sendToServer: boolean) => {
this.plugin.settings.liveSync = false;
this.plugin.settings.periodicReplication = false;
@@ -827,6 +1027,20 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
})
);
new Setting(containerHatchEl)
.setName("Drop old encrypted database")
.setDesc("WARNING: Please use this button only when you have failed on converting old-style localdatabase at v0.10.0.")
.addButton((button) =>
button
.setButtonText("Drop")
.setWarning()
.setDisabled(false)
.onClick(async () => {
await this.plugin.resetLocalOldDatabase();
await this.plugin.initializeDatabase();
})
);
addScreenElement("50", containerHatchEl);
// With great respect, thank you TfTHacker!
// refered: https://github.com/TfTHacker/obsidian42-brat/blob/main/src/features/BetaPlugins.ts

Submodule src/lib updated: d495f4577a...b031e4e69d

View File

@@ -1,7 +1,7 @@
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest, Modal, App } from "obsidian";
import { diff_match_patch } from "diff-match-patch";
import { EntryDoc, LoadedEntry, ObsidianLiveSyncSettings, diff_check_result, diff_result_leaf, EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, diff_result, FLAGMD_REDFLAG } from "./lib/src/types";
import { EntryDoc, LoadedEntry, ObsidianLiveSyncSettings, diff_check_result, diff_result_leaf, EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, diff_result, FLAGMD_REDFLAG, SYNCINFO_ID } from "./lib/src/types";
import { PluginDataEntry, PERIODIC_PLUGIN_SWEEP, PluginList, DevicePluginList } from "./types";
import {
base64ToString,
@@ -17,6 +17,7 @@ import {
setNoticeClass,
NewNotice,
allSettledWithConcurrencyLimit,
getLocks,
} from "./lib/src/utils";
import { Logger, setLogger } from "./lib/src/logger";
import { LocalPouchDB } from "./LocalPouchDB";
@@ -27,6 +28,7 @@ import { DocumentHistoryModal } from "./DocumentHistoryModal";
import PluginPane from "./PluginPane.svelte";
import { id2path, path2id } from "./utils";
const isDebug = false;
setNoticeClass(Notice);
class PluginDialogModal extends Modal {
plugin: ObsidianLiveSyncPlugin;
@@ -64,6 +66,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
statusBar2: HTMLElement;
suspended: boolean;
deviceAndVaultName: string;
isMobile = false;
setInterval(handler: () => any, timeout?: number): number {
const timer = window.setInterval(handler, timeout);
@@ -93,6 +96,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
const lsname = "obsidian-live-sync-ver" + this.app.vault.getName();
const last_version = localStorage.getItem(lsname);
await this.loadSettings();
//@ts-ignore
if (this.app.isMobile) {
this.isMobile = true;
this.settings.disableRequestURI = true;
}
if (last_version && Number(last_version) < VER) {
this.settings.liveSync = false;
this.settings.syncOnSave = false;
@@ -155,50 +163,54 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this));
this.app.workspace.onLayoutReady(async () => {
try {
if (this.isRedFlagRaised()) {
this.settings.batchSave = false;
this.settings.liveSync = false;
this.settings.periodicReplication = false;
this.settings.syncOnSave = false;
this.settings.syncOnStart = false;
this.settings.syncOnFileOpen = false;
this.settings.autoSweepPlugins = false;
this.settings.usePluginSync = false;
this.settings.suspendFileWatching = true;
await this.saveSettings();
await this.openDatabase();
const warningMessage = "The red flag is raised! The whole initialize steps are skipped, and any file changes are not captured.";
Logger(warningMessage, LOG_LEVEL.NOTICE);
this.setStatusBarText(warningMessage);
} else {
if (this.settings.suspendFileWatching) {
Logger("'Suspend file watching' turned on. Are you sure this is what you intended? Every modification on the vault will be ignored.", LOG_LEVEL.NOTICE);
if (this.localDatabase.isReady)
try {
if (this.isRedFlagRaised()) {
this.settings.batchSave = false;
this.settings.liveSync = false;
this.settings.periodicReplication = false;
this.settings.syncOnSave = false;
this.settings.syncOnStart = false;
this.settings.syncOnFileOpen = false;
this.settings.autoSweepPlugins = false;
this.settings.usePluginSync = false;
this.settings.suspendFileWatching = true;
await this.saveSettings();
await this.openDatabase();
const warningMessage = "The red flag is raised! The whole initialize steps are skipped, and any file changes are not captured.";
Logger(warningMessage, LOG_LEVEL.NOTICE);
this.setStatusBarText(warningMessage);
} else {
if (this.settings.suspendFileWatching) {
Logger("'Suspend file watching' turned on. Are you sure this is what you intended? Every modification on the vault will be ignored.", LOG_LEVEL.NOTICE);
}
const isInitalized = await this.initializeDatabase();
if (!isInitalized) {
//TODO:stop all sync.
return false;
}
}
await this.initializeDatabase();
await this.realizeSettingSyncMode();
this.registerWatchEvents();
if (this.settings.syncOnStart) {
this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult);
}
} catch (ex) {
Logger("Error while loading Self-hosted LiveSync", LOG_LEVEL.NOTICE);
Logger(ex, LOG_LEVEL.VERBOSE);
}
await this.realizeSettingSyncMode();
this.registerWatchEvents();
if (this.settings.syncOnStart) {
await this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult);
}
} catch (ex) {
Logger("Error while loading Self-hosted LiveSync", LOG_LEVEL.NOTICE);
Logger(ex, LOG_LEVEL.VERBOSE);
}
});
this.addCommand({
id: "livesync-replicate",
name: "Replicate now",
callback: () => {
this.replicate();
callback: async () => {
await this.replicate();
},
});
this.addCommand({
id: "livesync-dump",
name: "Dump informations of this doc ",
editorCallback: (editor: Editor, view: MarkdownView) => {
this.localDatabase.disposeHashCache();
this.localDatabase.getDBEntry(view.file.path, {}, true, false);
},
});
@@ -306,11 +318,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
const vaultName = this.app.vault.getName();
Logger("Open Database...");
this.localDatabase = new LocalPouchDB(this.settings, vaultName);
//@ts-ignore
const isMobile = this.app.isMobile;
this.localDatabase = new LocalPouchDB(this.settings, vaultName, isMobile);
this.localDatabase.updateInfo = () => {
this.refreshStatusText();
};
await this.localDatabase.initializeDatabase();
return await this.localDatabase.initializeDatabase();
}
async garbageCollect() {
@@ -321,6 +335,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
this.settings.workingEncrypt = this.settings.encrypt;
this.settings.workingPassphrase = this.settings.passphrase;
// Delete this feature to avoid problems on mobile.
this.settings.disableRequestURI = true;
const lsname = "obsidian-live-sync-vaultanddevicename-" + this.app.vault.getName();
if (this.settings.deviceAndVaultName != "") {
if (!localStorage.getItem(lsname)) {
@@ -365,7 +381,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.registerEvent(this.app.vault.on("modify", this.watchVaultChange));
this.registerEvent(this.app.vault.on("delete", this.watchVaultDelete));
this.registerEvent(this.app.vault.on("rename", this.watchVaultRename));
this.registerEvent(this.app.vault.on("create", this.watchVaultChange));
this.registerEvent(this.app.vault.on("create", this.watchVaultCreate));
this.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen));
window.addEventListener("visibilitychange", this.watchWindowVisiblity);
}
@@ -389,10 +405,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
await this.sweepPlugin(false);
}
if (this.settings.liveSync) {
await this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
}
if (this.settings.syncOnStart) {
await this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult);
this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult);
}
if (this.settings.periodicReplication) {
this.setPeriodicSync();
@@ -408,11 +424,12 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
async watchWorkspaceOpenAsync(file: TFile) {
await this.applyBatchChange();
if (file == null) return;
if (file == null) {
return;
}
if (this.settings.syncOnFileOpen && !this.suspended) {
await this.replicate();
}
this.localDatabase.disposeHashCache();
await this.showIfConflicted(file);
this.gcHook();
}
@@ -449,7 +466,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
async applyBatchChange() {
if (!this.settings.batchSave || this.batchFileChange.length == 0) {
return [];
return;
}
return await runWithLock("batchSave", false, async () => {
const batchItems = JSON.parse(JSON.stringify(this.batchFileChange)) as string[];
@@ -467,7 +484,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
});
this.refreshStatusText();
return await Promise.all(promises);
await allSettledWithConcurrencyLimit(promises, 3);
return;
});
}
@@ -570,7 +588,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
notifies: { [key: string]: { notice: Notice; timer: NodeJS.Timeout; count: number } } = {};
// eslint-disable-next-line require-await
lastLog = "";
async addLog(message: any, level: LOG_LEVEL = LOG_LEVEL.INFO) {
if (level == LOG_LEVEL.DEBUG && !isDebug) {
return;
}
if (level < LOG_LEVEL.INFO && this.settings && this.settings.lessInformationInLog) {
return;
}
@@ -584,6 +606,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.logMessage = [].concat(this.logMessage).concat([newmessage]).slice(-100);
console.log(valutName + ":" + newmessage);
this.setStatusBarText(null, messagecontent.substring(0, 30));
// if (message instanceof Error) {
// console.trace(message);
// }
if (level >= LOG_LEVEL.NOTICE) {
if (messagecontent in this.notifies) {
@@ -803,6 +829,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (change._id.startsWith("h:")) {
continue;
}
if (change._id == SYNCINFO_ID) {
continue;
}
if (change.type != "leaf" && change.type != "versioninfo" && change.type != "milestoneinfo" && change.type != "nodeinfo") {
Logger("replication change arrived", LOG_LEVEL.VERBOSE);
await this.handleDBChanged(change);
@@ -902,7 +931,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
await this.sweepPlugin(false);
}
if (this.settings.liveSync) {
await this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
this.refreshStatusText();
}
this.setPeriodicSync();
@@ -945,22 +974,36 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
const procs = getProcessingCounts();
const procsDisp = procs == 0 ? "" : `${procs}`;
const message = `Sync:${w}${sent}${arrived}${waiting}${procsDisp}`;
this.setStatusBarText(message);
const locks = getLocks();
const pendingTask = locks.pending.length ? `\nPending:${locks.pending.join(", ")}` : "";
const runningTask = locks.running.length ? `\nRunning:${locks.running.join(", ")}` : "";
this.setStatusBarText(message + pendingTask + runningTask);
}
setStatusBarText(message: string) {
if (this.lastMessage != message) {
this.statusBar.setText(message);
logHideTimer: NodeJS.Timeout = null;
setStatusBarText(message: string = null, log: string = null) {
if (!this.statusBar) return;
const newMsg = typeof message == "string" ? message : this.lastMessage;
const newLog = typeof log == "string" ? log : this.lastLog;
if (`${this.lastMessage}-${this.lastLog}` != `${newMsg}-${newLog}`) {
this.statusBar.setText(newMsg.split("\n")[0]);
if (this.settings.showStatusOnEditor) {
const root = document.documentElement;
root.style.setProperty("--slsmessage", '"' + message + '"');
root.style.setProperty("--slsmessage", '"' + (newMsg + "\n" + newLog).split("\n").join("\\a ") + '"');
} else {
const root = document.documentElement;
root.style.setProperty("--slsmessage", '""');
}
this.lastMessage = message;
if (this.logHideTimer != null) {
clearTimeout(this.logHideTimer);
}
this.logHideTimer = setTimeout(() => this.setStatusBarText(null, ""), 3000);
this.lastMessage = newMsg;
this.lastLog = newLog;
}
}
updateStatusBarText() {}
async replicate(showMessage?: boolean) {
if (this.settings.versionUpFlash != "") {
@@ -971,12 +1014,18 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (this.settings.autoSweepPlugins) {
await this.sweepPlugin(false);
}
await this.localDatabase.openReplication(this.settings, false, showMessage, this.parseReplicationResult);
this.localDatabase.openReplication(this.settings, false, showMessage, this.parseReplicationResult);
}
async initializeDatabase(showingNotice?: boolean) {
await this.openDatabase();
await this.syncAllFiles(showingNotice);
if (await this.openDatabase()) {
if (this.localDatabase.isReady) {
await this.syncAllFiles(showingNotice);
}
return true;
} else {
return false;
}
}
async replicateAllToServer(showingNotice?: boolean) {
@@ -1417,6 +1466,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
async resetLocalDatabase() {
await this.localDatabase.resetDatabase();
}
async resetLocalOldDatabase() {
await this.localDatabase.resetLocalOldDatabase();
}
async tryResetRemoteDatabase() {
await this.localDatabase.tryResetRemoteDatabase(this.settings);

View File

@@ -1,3 +1,4 @@
import { PouchDB } from "../pouchdb-browser-webpack/dist/pouchdb-browser.js";
console.dir(PouchDB)
export { PouchDB };
import { PouchDB as PouchDB_ } from "../pouchdb-browser-webpack/dist/pouchdb-browser.js";
const Pouch: PouchDB.Static = PouchDB_;
export { Pouch as PouchDB };

View File

@@ -1,6 +1,6 @@
import { Logger } from "./lib/src/logger";
import { LOG_LEVEL, VER, VERSIONINFO_DOCID, EntryVersionInfo, EntryDoc } from "./lib/src/types";
import { resolveWithIgnoreKnownError } from "./lib/src/utils";
import { LOG_LEVEL, VER, VERSIONINFO_DOCID, EntryVersionInfo, EntryDoc, RemoteDBSettings, SYNCINFO_ID, SyncInfo } from "./lib/src/types";
import { enableEncryption, resolveWithIgnoreKnownError } from "./lib/src/utils";
import { PouchDB } from "./pouchdb-browser";
import { requestUrl, RequestUrlParam, RequestUrlResponse } from "obsidian";
@@ -27,7 +27,18 @@ const fetchByAPI = async (request: RequestUrlParam): Promise<RequestUrlResponse>
return ret;
};
export const connectRemoteCouchDB = async (uri: string, auth: { username: string; password: string }, disableRequestURI: boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> => {
export const connectRemoteCouchDBWithSetting = (settings: RemoteDBSettings, isMobile: boolean) =>
connectRemoteCouchDB(
settings.couchDB_URI + (settings.couchDB_DBNAME == "" ? "" : "/" + settings.couchDB_DBNAME),
{
username: settings.couchDB_USER,
password: settings.couchDB_PASSWORD,
},
settings.disableRequestURI || isMobile,
settings.encrypt ? settings.passphrase : settings.encrypt
);
const connectRemoteCouchDB = async (uri: string, auth: { username: string; password: string }, disableRequestURI: boolean, passphrase: string | boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> => {
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
let authHeader = "";
if (auth.username && auth.password) {
@@ -41,7 +52,6 @@ export const connectRemoteCouchDB = async (uri: string, auth: { username: string
adapter: "http",
auth,
fetch: async function (url: string | Request, opts: RequestInit) {
let size_ok = true;
let size = "";
const localURL = url.toString().substring(uri.length);
const method = opts.method ?? "GET";
@@ -49,7 +59,6 @@ export const connectRemoteCouchDB = async (uri: string, auth: { username: string
const opts_length = opts.body.toString().length;
if (opts_length > 1024 * 1024 * 10) {
// over 10MB
size_ok = false;
if (uri.contains(".cloudantnosqldb.")) {
last_post_successed = false;
Logger("This request should fail on IBM Cloudant.", LOG_LEVEL.VERBOSE);
@@ -84,7 +93,7 @@ export const connectRemoteCouchDB = async (uri: string, auth: { username: string
} else {
last_post_successed = true;
}
Logger(`HTTP:${method}${size} to:${localURL} -> ${r.status}`, LOG_LEVEL.VERBOSE);
Logger(`HTTP:${method}${size} to:${localURL} -> ${r.status}`, LOG_LEVEL.DEBUG);
return new Response(r.arrayBuffer, {
headers: r.headers,
@@ -93,7 +102,8 @@ export const connectRemoteCouchDB = async (uri: string, auth: { username: string
});
} catch (ex) {
Logger(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL.VERBOSE);
if (!size_ok && (method == "POST" || method == "PUT")) {
// limit only in bulk_docs.
if (url.toString().indexOf("_bulk_docs") !== -1) {
last_post_successed = false;
}
Logger(ex);
@@ -110,11 +120,12 @@ export const connectRemoteCouchDB = async (uri: string, auth: { username: string
} else {
last_post_successed = true;
}
Logger(`HTTP:${method}${size} to:${localURL} -> ${responce.status}`, LOG_LEVEL.VERBOSE);
Logger(`HTTP:${method}${size} to:${localURL} -> ${responce.status}`, LOG_LEVEL.DEBUG);
return responce;
} catch (ex) {
Logger(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL.VERBOSE);
if (!size_ok && (method == "POST" || method == "PUT")) {
// limit only in bulk_docs.
if (url.toString().indexOf("_bulk_docs") !== -1) {
last_post_successed = false;
}
Logger(ex);
@@ -124,6 +135,9 @@ export const connectRemoteCouchDB = async (uri: string, auth: { username: string
},
};
const db: PouchDB.Database<EntryDoc> = new PouchDB<EntryDoc>(uri, conf);
if (passphrase && typeof passphrase === "string") {
enableEncryption(db, passphrase);
}
try {
const info = await db.info();
return { db: db, info: info };
@@ -179,3 +193,32 @@ export const bumpRemoteVersion = async (db: PouchDB.Database, barrier: number =
await db.put(vi);
return true;
};
export const checkSyncInfo = async (db: PouchDB.Database): Promise<boolean> => {
try {
const syncinfo = (await db.get(SYNCINFO_ID)) as SyncInfo;
console.log(syncinfo);
// if we could decrypt the doc, it must be ok.
return true;
} catch (ex) {
if (ex.status && ex.status == 404) {
const randomStrSrc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const temp = [...Array(30)]
.map((e) => Math.floor(Math.random() * randomStrSrc.length))
.map((e) => randomStrSrc[e])
.join("");
const newSyncInfo: SyncInfo = {
_id: SYNCINFO_ID,
type: "syncinfo",
data: temp,
};
if (await db.put(newSyncInfo)) {
return true;
}
return false;
} else {
console.dir(ex);
return false;
}
}
};

View File

@@ -1,14 +1,14 @@
.added {
color: black;
background-color: white;
color: var(--text-on-accent);
background-color: var(--text-accent);
}
.normal {
color: lightgray;
color: var(--text-normal);
}
.deleted {
color: white;
background-color: black;
/* text-decoration: line-through; */
color: var(--text-on-accent);
background-color: var(--text-muted);
text-decoration: line-through;
}
.op-scrollable {
overflow-y: scroll;
@@ -24,6 +24,25 @@
padding: 2px;
border-radius: 4px;
}
.op-warn::before {
content: "Warning";
font-weight: bold;
color: salmon;
position: relative;
display: block;
}
.op-warn-info {
border: 1px solid rgb(255, 209, 81);
padding: 2px;
border-radius: 4px;
}
.op-warn-info::before {
content: "Notice";
font-weight: bold;
color: rgb(255, 209, 81);
position: relative;
display: block;
}
.syncstatusbar {
-webkit-filter: grayscale(100%);
filter: grayscale(100%);
@@ -69,6 +88,8 @@
.CodeMirror-wrap::before,
.cm-s-obsidian > .cm-editor::before {
content: var(--slsmessage);
text-align: right;
white-space: pre-wrap;
position: absolute;
border-radius: 4px;
/* border:1px solid --background-modifier-border; */
@@ -172,3 +193,19 @@ div.sls-setting-menu-btn {
background-color: var(--text-muted);
text-decoration: line-through;
}
.ob-btn-config-fix label {
margin-right: 40px;
}
.ob-btn-config-info {
border: 1px solid salmon;
padding: 2px;
margin: 1px;
border-radius: 4px;
}
.ob-btn-config-head {
padding: 2px;
margin: 1px;
border-radius: 4px;
}

View File

@@ -6,7 +6,6 @@
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"types": ["svelte", "node"],
// "importsNotUsedAsValues": "error",
"importHelpers": true,
"alwaysStrict": true,