Compare commits

...

9 Commits
0.7.1 ... 0.8.3

Author SHA1 Message Date
vorotamoroz
9d3aa35b0b Fixed:
- Problems around new request API's
2022-04-20 15:02:06 +09:00
vorotamoroz
b4b9684a55 Fixed:
- Failure on the first sync
2022-04-07 16:17:20 +09:00
vorotamoroz
221cccb845 bumped 2022-04-04 20:01:50 +09:00
vorotamoroz
801500f924 Fixed:
- Fixed merging issue (Concat both)
- Overdetection of file change after the replication
2022-04-04 19:58:44 +09:00
vorotamoroz
3545ae9690 Implemented:
- using Obsidian API to synchronize.
- Copy button on history dialog.

Documented:
- Document improved.
2022-04-01 17:57:14 +09:00
vorotamoroz
255e7bf828 bumped 2022-03-08 10:40:11 +09:00
vorotamoroz
6f9e7bbcf4 Merge pull request #49 from banool/main
Print exception on failure in certain cases
2022-03-08 10:31:25 +09:00
Daniel Porteous
ce1c94a814 Print exception on failure in certain cases 2022-03-06 16:13:57 -08:00
vorotamoroz
caf7934f28 Create FUNDING.yml 2022-02-25 13:14:24 +09:00
11 changed files with 204 additions and 67 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: vrtmrz

View File

@@ -18,7 +18,14 @@ Note: This password is saved into your Obsidian's vault in plain text.
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.
### Test Database connection
You can check the connection by clicking this button.
## Local Database Configurations
"Local Database" is created inside your obsidian.
@@ -44,6 +51,8 @@ As a result, Obsidian's behavior is temporarily slowed down.
Default is 300 seconds.
If you are an early adopter, maybe this value is left as 30 seconds. Please change this value to larger values.
Note: If you want to use "Use history", this vault must be set to 0.
### Manual Garbage Collect
Run "Garbage Collection" manually.
@@ -52,6 +61,8 @@ Encrypt your database. It affects only the database, your files are left as plai
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.
@@ -195,6 +206,29 @@ You can set synchronization method at once as these pattern:
- Sync on File Open : disabled
- Sync on Start : disabled
### Use history
If you enable this option, you can keep document histories in your database.
(Not all intermediate changes are synchronized.)
You can check the changes caused by your edit and/or replication.
### Enable plugin synchronization
If you want to use this feature, you have to activate this feature by this switch.
### Sweep plugins automatically
Plugin sweep will run before replication automatically.
### Sweep plugins periodically
Plugin sweep will run each 1 minute.
### Notify updates
When replication is complete, a message will be notified if a newer version of the plugin applied to this device is configured on another device.
### Device and Vault name
To save the plugins, you have to set a unique name every each device.
### Open
Open the "Plugins and their settings" dialog.
## Hatch
From here, everything is under the hood. Please handle it with care.

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "0.7.1",
"version": "0.8.3",
"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",

46
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "obsidian-livesync",
"version": "0.7.1",
"version": "0.8.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "obsidian-livesync",
"version": "0.7.1",
"version": "0.8.3",
"license": "MIT",
"dependencies": {
"diff-match-patch": "^1.0.5",
@@ -27,7 +27,7 @@
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.25.2",
"obsidian": "^0.13.11",
"obsidian": "^0.14.6",
"rollup": "^2.32.1",
"svelte-preprocess": "^4.10.2",
"tslib": "^2.2.0",
@@ -2553,9 +2553,9 @@
}
},
"node_modules/minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
},
"node_modules/mkdirp": {
@@ -2571,9 +2571,9 @@
}
},
"node_modules/moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
"version": "2.29.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
"integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==",
"dev": true,
"engines": {
"node": "*"
@@ -2659,15 +2659,15 @@
}
},
"node_modules/obsidian": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.13.11.tgz",
"integrity": "sha512-KxOvAh4CG5vzcukmHvyuK9hUIr6ZFlM9FQfGZEwrrEV8VG2/W2Tk5cWrg0VM7EkGE3QBmjX6owjIDIO8QDXVUQ==",
"version": "0.14.6",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.14.6.tgz",
"integrity": "sha512-oXPJ8Zt10WhN19bk5l4mZuXRZbbdT1QoMgxGGJ0bB7UcJa0bozDzugS5L/QiV9gDoujpUPxDWNVahEel6r0Fpw==",
"dev": true,
"dependencies": {
"@codemirror/state": "^0.19.6",
"@codemirror/view": "^0.19.31",
"@types/codemirror": "0.0.108",
"moment": "2.29.1"
"moment": "2.29.2"
}
},
"node_modules/once": {
@@ -5391,9 +5391,9 @@
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
},
"mkdirp": {
@@ -5406,9 +5406,9 @@
}
},
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
"version": "2.29.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
"integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==",
"dev": true
},
"ms": {
@@ -5470,15 +5470,15 @@
}
},
"obsidian": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.13.11.tgz",
"integrity": "sha512-KxOvAh4CG5vzcukmHvyuK9hUIr6ZFlM9FQfGZEwrrEV8VG2/W2Tk5cWrg0VM7EkGE3QBmjX6owjIDIO8QDXVUQ==",
"version": "0.14.6",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.14.6.tgz",
"integrity": "sha512-oXPJ8Zt10WhN19bk5l4mZuXRZbbdT1QoMgxGGJ0bB7UcJa0bozDzugS5L/QiV9gDoujpUPxDWNVahEel6r0Fpw==",
"dev": true,
"requires": {
"@codemirror/state": "^0.19.6",
"@codemirror/view": "^0.19.31",
"@types/codemirror": "0.0.108",
"moment": "2.29.1"
"moment": "2.29.2"
}
},
"once": {

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.7.1",
"version": "0.8.3",
"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",
@@ -21,17 +21,17 @@
"@types/pouchdb-browser": "^6.1.3",
"@typescript-eslint/eslint-plugin": "^5.7.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.25.2",
"obsidian": "^0.13.11",
"rollup": "^2.32.1",
"tslib": "^2.2.0",
"typescript": "^4.2.4",
"builtin-modules": "^3.2.0",
"esbuild": "0.13.12",
"esbuild-svelte": "^0.6.0",
"svelte-preprocess": "^4.10.2"
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.25.2",
"obsidian": "^0.14.6",
"rollup": "^2.32.1",
"svelte-preprocess": "^4.10.2",
"tslib": "^2.2.0",
"typescript": "^4.2.4"
},
"dependencies": {
"diff-match-patch": "^1.0.5",

View File

@@ -2,6 +2,8 @@ import { TFile, Modal, App } from "obsidian";
import { path2id, escapeStringToHTML } from "./utils";
import ObsidianLiveSyncPlugin from "./main";
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
import { LOG_LEVEL } from "./types";
import { Logger } from "./logger";
export class DocumentHistoryModal extends Modal {
plugin: ObsidianLiveSyncPlugin;
@@ -14,6 +16,7 @@ export class DocumentHistoryModal extends Modal {
file: string;
revs_info: PouchDB.Core.RevisionInfo[] = [];
currentText = "";
constructor(app: App, plugin: ObsidianLiveSyncPlugin, file: TFile) {
super(app);
@@ -37,6 +40,7 @@ export class DocumentHistoryModal extends Modal {
const index = this.revs_info.length - 1 - (this.range.value as any) / 1;
const rev = this.revs_info[index];
const w = await db.getDBEntry(path2id(this.file), { rev: rev.rev }, false, false);
this.currentText = "";
if (w === false) {
this.info.innerHTML = "";
@@ -44,6 +48,7 @@ export class DocumentHistoryModal extends Modal {
} else {
this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`;
let result = "";
this.currentText = w.data;
if (this.showDiff) {
const prevRevIdx = this.revs_info.length - 1 - ((this.range.value as any) / 1 - 1);
if (prevRevIdx >= 0 && prevRevIdx < this.revs_info.length) {
@@ -124,6 +129,14 @@ export class DocumentHistoryModal extends Modal {
this.contentView = div;
div.addClass("op-scrollable");
div.addClass("op-pre");
const buttons = contentEl.createDiv("");
buttons.createEl("button", { text: "Copy to clipboard" }, (e) => {
e.addClass("mod-cta");
e.addEventListener("click", async () => {
await navigator.clipboard.writeText(this.currentText);
Logger(`Old content copied to clipboard`, LOG_LEVEL.NOTICE);
});
});
}
onClose() {
const { contentEl } = this;

View File

@@ -357,7 +357,7 @@ export class LocalPouchDB {
Logger(childrens);
}
} catch (ex) {
Logger(`Something went wrong on reading elements of ${obj._id} from database.`, LOG_LEVEL.NOTICE);
Logger(`Something went wrong on reading elements of ${obj._id} from database:`, LOG_LEVEL.NOTICE);
Logger(ex, LOG_LEVEL.VERBOSE);
this.corruptedEntries[obj._id] = obj;
return false;
@@ -388,7 +388,7 @@ export class LocalPouchDB {
Logger(`Missing document content!, could not read ${obj._id} from database.`, LOG_LEVEL.NOTICE);
return false;
}
Logger(`Something went wrong on reading ${obj._id} from database.`, LOG_LEVEL.NOTICE);
Logger(`Something went wrong on reading ${obj._id} from database:`, LOG_LEVEL.NOTICE);
Logger(ex);
}
}
@@ -594,7 +594,7 @@ export class LocalPouchDB {
try {
pieceData.data = await decrypt(pieceData.data, this.settings.passphrase);
} catch (e) {
Logger("Decode failed !");
Logger("Decode failed!");
throw e;
}
}
@@ -667,8 +667,8 @@ export class LocalPouchDB {
}
}
} catch (ex) {
Logger("ERROR ON SAVING LEAVES ");
Logger(ex);
Logger("ERROR ON SAVING LEAVES:", LOG_LEVEL.NOTICE);
Logger(ex, LOG_LEVEL.NOTICE);
saved = false;
}
}
@@ -743,7 +743,7 @@ export class LocalPouchDB {
username: setting.couchDB_USER,
password: setting.couchDB_PASSWORD,
};
const dbret = await connectRemoteCouchDB(uri, auth);
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
if (typeof dbret === "string") {
Logger(`could not connect to ${uri}:${dbret}`, LOG_LEVEL.NOTICE);
if (notice != null) notice.hide();
@@ -820,9 +820,9 @@ export class LocalPouchDB {
Logger("Another replication running.");
return false;
}
const dbret = await connectRemoteCouchDB(uri, auth);
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
if (typeof dbret === "string") {
Logger(`could not connect to ${uri}:${dbret}`, LOG_LEVEL.NOTICE);
Logger(`could not connect to ${uri}: ${dbret}`, LOG_LEVEL.NOTICE);
return false;
}
@@ -932,8 +932,8 @@ export class LocalPouchDB {
}
this.updateInfo();
} catch (ex) {
Logger("Replication callback error");
Logger(ex);
Logger("Replication callback error", LOG_LEVEL.NOTICE);
Logger(ex, LOG_LEVEL.NOTICE);
}
// re-connect to retry with original setting
if (retrying) {
@@ -1032,8 +1032,8 @@ export class LocalPouchDB {
notice.setMessage(`Replication pulled:${e.docs_read}`);
}
} catch (ex) {
Logger("Replication callback error");
Logger(ex);
Logger("Replication callback error", LOG_LEVEL.NOTICE);
Logger(ex, LOG_LEVEL.NOTICE);
}
});
this.syncStatus = "COMPLETED";
@@ -1046,7 +1046,8 @@ export class LocalPouchDB {
} catch (ex) {
this.syncStatus = "ERRORED";
this.updateInfo();
Logger("Pulling Replication error", LOG_LEVEL.NOTICE);
Logger("Pulling Replication error:", LOG_LEVEL.NOTICE);
Logger(ex, LOG_LEVEL.NOTICE);
this.cancelHandler(replicate);
this.syncHandler = this.cancelHandler(this.syncHandler);
if (notice != null) notice.hide();
@@ -1080,14 +1081,15 @@ export class LocalPouchDB {
username: setting.couchDB_USER,
password: setting.couchDB_PASSWORD,
};
const con = await connectRemoteCouchDB(uri, auth);
const con = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
if (typeof con == "string") return;
try {
await con.db.destroy();
Logger("Remote Database Destroyed", LOG_LEVEL.NOTICE);
await this.tryCreateRemoteDatabase(setting);
} catch (ex) {
Logger("something happend on Remote Database Destory", LOG_LEVEL.NOTICE);
Logger("Something happened on Remote Database Destory:", LOG_LEVEL.NOTICE);
Logger(ex, LOG_LEVEL.NOTICE);
}
}
async tryCreateRemoteDatabase(setting: ObsidianLiveSyncSettings) {
@@ -1097,7 +1099,7 @@ export class LocalPouchDB {
username: setting.couchDB_USER,
password: setting.couchDB_PASSWORD,
};
const con2 = await connectRemoteCouchDB(uri, auth);
const con2 = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
if (typeof con2 === "string") return;
Logger("Remote Database Created or Connected", LOG_LEVEL.NOTICE);
}
@@ -1107,7 +1109,7 @@ export class LocalPouchDB {
username: setting.couchDB_USER,
password: setting.couchDB_PASSWORD,
};
const dbret = await connectRemoteCouchDB(uri, auth);
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
if (typeof dbret === "string") {
Logger(`could not connect to ${uri}:${dbret}`, LOG_LEVEL.NOTICE);
return;
@@ -1141,7 +1143,7 @@ export class LocalPouchDB {
username: setting.couchDB_USER,
password: setting.couchDB_PASSWORD,
};
const dbret = await connectRemoteCouchDB(uri, auth);
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
if (typeof dbret === "string") {
Logger(`could not connect to ${uri}:${dbret}`, LOG_LEVEL.NOTICE);
return;

View File

@@ -14,10 +14,14 @@ 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,
});
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
);
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;
@@ -165,6 +169,12 @@ 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;
await this.plugin.saveSettings();
})
)
);
@@ -603,7 +613,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
);
new Setting(containerMiscellaneousEl)
.setName("Use history (beta)")
.setName("Use history")
.setDesc("Use history dialog (Restart required, auto compaction would be disabled, and more storage will be consumed)")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.useHistory).onChange(async (value) => {
@@ -832,12 +842,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
})
);
new Setting(containerPluginSettings).setName("Show own plugins and settings").addToggle((toggle) =>
toggle.setValue(this.plugin.settings.showOwnPlugins).onChange(async (value) => {
this.plugin.settings.showOwnPlugins = value;
await this.plugin.saveSettings();
})
);
new Setting(containerPluginSettings)
.setName("Sweep plugins automatically")

View File

@@ -427,10 +427,21 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
return;
}
if (this.settings.suspendFileWatching) return;
// If batchsave is enabled, queue all changes and do nothing.
if (this.settings.batchSave) {
this.batchFileChange = Array.from(new Set([...this.batchFileChange, file.path]));
this.refreshStatusText();
~(async () => {
const meta = await this.localDatabase.getDBEntryMeta(file.path);
if (meta != false) {
const localMtime = ~~(file.stat.mtime / 1000);
const docMtime = ~~(meta.mtime / 1000);
if (localMtime !== docMtime) {
// Perhaps we have to modify (to using newer doc), but we don't be sure to every device's clock is adjusted.
this.batchFileChange = Array.from(new Set([...this.batchFileChange, file.path]));
this.refreshStatusText();
}
}
})();
return;
}
this.watchVaultChangeAsync(file, ...args);
@@ -1232,10 +1243,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
//concat both,
// write data,and delete both old rev.
const p = conflictCheckResult.diff.map((e) => e[1]).join("");
await this.app.vault.modify(file, p);
await this.updateIntoDB(file);
await this.localDatabase.deleteDBEntry(file.path, { rev: conflictCheckResult.left.rev });
await this.localDatabase.deleteDBEntry(file.path, { rev: conflictCheckResult.right.rev });
await this.app.vault.modify(file, p);
await this.updateIntoDB(file);
await this.pullFile(file.path);
Logger("concat both file");
setTimeout(() => {

View File

@@ -60,6 +60,7 @@ export interface ObsidianLiveSyncSettings {
batch_size: number;
batches_limit: number;
useHistory: boolean;
disableRequestURI: boolean;
}
export const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
@@ -101,6 +102,7 @@ export const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
batch_size: 250,
batches_limit: 40,
useHistory: false,
disableRequestURI: false,
};
export const PERIODIC_PLUGIN_SWEEP = 60;

View File

@@ -2,6 +2,7 @@ import { Logger } from "./logger";
import { LOG_LEVEL, VER, VERSIONINFO_DOCID, EntryVersionInfo, EntryDoc } from "./types";
import { resolveWithIgnoreKnownError } from "./utils";
import { PouchDB } from "../pouchdb-browser-webpack/dist/pouchdb-browser.js";
import { requestUrl, RequestUrlParam, RequestUrlResponse } from "obsidian";
export const isValidRemoteCouchDBURI = (uri: string): boolean => {
if (uri.startsWith("https://")) return true;
@@ -12,8 +13,30 @@ let last_post_successed = false;
export const getLastPostFailedBySize = () => {
return !last_post_successed;
};
export const connectRemoteCouchDB = async (uri: string, auth: { username: string; password: string }): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> => {
const fetchByAPI = async (request: RequestUrlParam): Promise<RequestUrlResponse> => {
const ret = await requestUrl(request);
if (ret.status - (ret.status % 100) !== 200) {
const er: Error & { status?: number } = new Error(`Request Error:${ret.status}`);
if (ret.json) {
er.message = ret.json.reason;
er.name = `${ret.json.error ?? ""}:${ret.json.message ?? ""}`;
}
er.status = ret.status;
throw er;
}
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 }> => {
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
let authHeader = "";
if (auth.username && auth.password) {
const utf8str = String.fromCharCode.apply(null, new TextEncoder().encode(`${auth.username}:${auth.password}`));
const encoded = window.btoa(utf8str);
authHeader = "Basic " + encoded;
} else {
authHeader = "";
}
const conf: PouchDB.HttpAdapter.HttpAdapterConfiguration = {
adapter: "http",
auth,
@@ -35,6 +58,51 @@ export const connectRemoteCouchDB = async (uri: string, auth: { username: string
}
size = ` (${opts_length})`;
}
if (!disableRequestURI && typeof url == "string" && typeof (opts.body ?? "") == "string") {
const body = opts.body as string;
const transformedHeaders = { ...(opts.headers as Record<string, string>) };
if (authHeader != "") transformedHeaders["authorization"] = authHeader;
delete transformedHeaders["host"];
delete transformedHeaders["Host"];
delete transformedHeaders["content-length"];
delete transformedHeaders["Content-Length"];
const requestParam: RequestUrlParam = {
url: url as string,
method: opts.method,
body: body,
headers: transformedHeaders,
contentType: "application/json",
// contentType: opts.headers,
};
try {
const r = await fetchByAPI(requestParam);
if (method == "POST" || method == "PUT") {
last_post_successed = r.status - (r.status % 100) == 200;
} else {
last_post_successed = true;
}
Logger(`HTTP:${method}${size} to:${localURL} -> ${r.status}`, LOG_LEVEL.VERBOSE);
return new Response(r.arrayBuffer, {
headers: r.headers,
status: r.status,
statusText: `${r.status}`,
});
} catch (ex) {
Logger(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL.VERBOSE);
if (!size_ok && (method == "POST" || method == "PUT")) {
last_post_successed = false;
}
Logger(ex);
throw ex;
}
}
// -old implementation
try {
const responce: Response = await fetch(url, opts);
if (method == "POST" || method == "PUT") {