mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-12-18 12:11:29 +00:00
Refactored:
- PouchDB handling moved into Common lib.
This commit is contained in:
1
pouchdb-browser-webpack/.gitignore
vendored
1
pouchdb-browser-webpack/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
node_modules
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# PouchDB-browser
|
|
||||||
|
|
||||||
Just webpacked.
|
|
||||||
(Rollup couldn't pack pouchdb-browser into browser bundle)
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,25 +0,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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
/* 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 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 };
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// Generated using webpack-cli https://github.com/webpack/webpack-cli
|
|
||||||
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV == "production";
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
entry: "./src/index.js",
|
|
||||||
output: {
|
|
||||||
filename: "pouchdb-browser.js",
|
|
||||||
path: path.resolve(__dirname, "dist"),
|
|
||||||
library: {
|
|
||||||
type: "module",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
experiments: {
|
|
||||||
outputModule: true,
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
module: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = () => {
|
|
||||||
if (isProduction) {
|
|
||||||
config.mode = "production";
|
|
||||||
} else {
|
|
||||||
config.mode = "development";
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
};
|
|
||||||
1519
src/LocalPouchDB.ts
1519
src/LocalPouchDB.ts
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ import { EntryDoc, LOG_LEVEL, RemoteDBSettings } from "./lib/src/types";
|
|||||||
import { path2id, id2path } from "./utils";
|
import { path2id, id2path } from "./utils";
|
||||||
import { delay, runWithLock, versionNumberString2Number } from "./lib/src/utils";
|
import { delay, runWithLock, versionNumberString2Number } from "./lib/src/utils";
|
||||||
import { Logger } from "./lib/src/logger";
|
import { Logger } from "./lib/src/logger";
|
||||||
import { checkSyncInfo, connectRemoteCouchDBWithSetting } from "./utils_couchdb";
|
import { checkSyncInfo } from "./lib/src/utils_couchdb";
|
||||||
import { testCrypt } from "./lib/src/e2ee_v2";
|
import { testCrypt } from "./lib/src/e2ee_v2";
|
||||||
import ObsidianLiveSyncPlugin from "./main";
|
import ObsidianLiveSyncPlugin from "./main";
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
async testConnection(): Promise<void> {
|
async testConnection(): Promise<void> {
|
||||||
const db = await connectRemoteCouchDBWithSetting(this.plugin.settings, this.plugin.localDatabase.isMobile);
|
const db = await this.plugin.localDatabase.connectRemoteCouchDBWithSetting(this.plugin.settings, this.plugin.localDatabase.isMobile);
|
||||||
if (typeof db === "string") {
|
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);
|
this.plugin.addLog(`could not connect to ${this.plugin.settings.couchDB_URI} : ${this.plugin.settings.couchDB_DBNAME} \n(${db})`, LOG_LEVEL.NOTICE);
|
||||||
return;
|
return;
|
||||||
@@ -225,7 +225,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
passphrase: this.plugin.settings.workingPassphrase,
|
passphrase: this.plugin.settings.workingPassphrase,
|
||||||
};
|
};
|
||||||
console.dir(settingForCheck);
|
console.dir(settingForCheck);
|
||||||
const db = await connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.localDatabase.isMobile);
|
const db = await this.plugin.localDatabase.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.localDatabase.isMobile);
|
||||||
if (typeof db === "string") {
|
if (typeof db === "string") {
|
||||||
Logger("Could not connect to the database.", LOG_LEVEL.NOTICE);
|
Logger("Could not connect to the database.", LOG_LEVEL.NOTICE);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: aacfa353a9...57db68352b
@@ -1,4 +0,0 @@
|
|||||||
import { PouchDB as PouchDB_ } from "../pouchdb-browser-webpack/dist/pouchdb-browser.js";
|
|
||||||
|
|
||||||
const Pouch: PouchDB.Static = PouchDB_;
|
|
||||||
export { Pouch as PouchDB };
|
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
import { Logger } from "./lib/src/logger";
|
|
||||||
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";
|
|
||||||
|
|
||||||
export const isValidRemoteCouchDBURI = (uri: string): boolean => {
|
|
||||||
if (uri.startsWith("https://")) return true;
|
|
||||||
if (uri.startsWith("http://")) return true;
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
let last_successful_post = false;
|
|
||||||
export const getLastPostFailedBySize = () => {
|
|
||||||
return !last_successful_post;
|
|
||||||
};
|
|
||||||
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 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";
|
|
||||||
if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters.";
|
|
||||||
if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces.";
|
|
||||||
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,
|
|
||||||
fetch: async function (url: string | Request, opts: RequestInit) {
|
|
||||||
let size = "";
|
|
||||||
const localURL = url.toString().substring(uri.length);
|
|
||||||
const method = opts.method ?? "GET";
|
|
||||||
if (opts.body) {
|
|
||||||
const opts_length = opts.body.toString().length;
|
|
||||||
if (opts_length > 1024 * 1024 * 10) {
|
|
||||||
// over 10MB
|
|
||||||
if (uri.contains(".cloudantnosqldb.")) {
|
|
||||||
last_successful_post = false;
|
|
||||||
Logger("This request should fail on IBM Cloudant.", LOG_LEVEL.VERBOSE);
|
|
||||||
throw new Error("This request should fail on IBM Cloudant.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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_successful_post = r.status - (r.status % 100) == 200;
|
|
||||||
} else {
|
|
||||||
last_successful_post = true;
|
|
||||||
}
|
|
||||||
Logger(`HTTP:${method}${size} to:${localURL} -> ${r.status}`, LOG_LEVEL.DEBUG);
|
|
||||||
|
|
||||||
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);
|
|
||||||
// limit only in bulk_docs.
|
|
||||||
if (url.toString().indexOf("_bulk_docs") !== -1) {
|
|
||||||
last_successful_post = false;
|
|
||||||
}
|
|
||||||
Logger(ex);
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -old implementation
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response: Response = await fetch(url, opts);
|
|
||||||
if (method == "POST" || method == "PUT") {
|
|
||||||
last_successful_post = response.ok;
|
|
||||||
} else {
|
|
||||||
last_successful_post = true;
|
|
||||||
}
|
|
||||||
Logger(`HTTP:${method}${size} to:${localURL} -> ${response.status}`, LOG_LEVEL.DEBUG);
|
|
||||||
return response;
|
|
||||||
} catch (ex) {
|
|
||||||
Logger(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL.VERBOSE);
|
|
||||||
// limit only in bulk_docs.
|
|
||||||
if (url.toString().indexOf("_bulk_docs") !== -1) {
|
|
||||||
last_successful_post = false;
|
|
||||||
}
|
|
||||||
Logger(ex);
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
// return await fetch(url, opts);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
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 };
|
|
||||||
} catch (ex) {
|
|
||||||
let msg = `${ex.name}:${ex.message}`;
|
|
||||||
if (ex.name == "TypeError" && ex.message == "Failed to fetch") {
|
|
||||||
msg += "\n**Note** This error caused by many reasons. The only sure thing is you didn't touch the server.\nTo check details, open inspector.";
|
|
||||||
}
|
|
||||||
Logger(ex, LOG_LEVEL.VERBOSE);
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// check the version of remote.
|
|
||||||
// if remote is higher than current(or specified) version, return false.
|
|
||||||
export const checkRemoteVersion = async (db: PouchDB.Database, migrate: (from: number, to: number) => Promise<boolean>, barrier: number = VER): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
const versionInfo = (await db.get(VERSIONINFO_DOCID)) as EntryVersionInfo;
|
|
||||||
if (versionInfo.type != "versioninfo") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const version = versionInfo.version;
|
|
||||||
if (version < barrier) {
|
|
||||||
const versionUpResult = await migrate(version, barrier);
|
|
||||||
if (versionUpResult) {
|
|
||||||
await bumpRemoteVersion(db);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (version == barrier) return true;
|
|
||||||
return false;
|
|
||||||
} catch (ex) {
|
|
||||||
if (ex.status && ex.status == 404) {
|
|
||||||
if (await bumpRemoteVersion(db)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
export const bumpRemoteVersion = async (db: PouchDB.Database, barrier: number = VER): Promise<boolean> => {
|
|
||||||
const vi: EntryVersionInfo = {
|
|
||||||
_id: VERSIONINFO_DOCID,
|
|
||||||
version: barrier,
|
|
||||||
type: "versioninfo",
|
|
||||||
};
|
|
||||||
const versionInfo = (await resolveWithIgnoreKnownError(db.get(VERSIONINFO_DOCID), vi)) as EntryVersionInfo;
|
|
||||||
if (versionInfo.type != "versioninfo") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
vi._rev = versionInfo._rev;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export async function putDesignDocuments(db: PouchDB.Database) {
|
|
||||||
type DesignDoc = {
|
|
||||||
_id: string;
|
|
||||||
_rev: string;
|
|
||||||
ver: number;
|
|
||||||
filters: {
|
|
||||||
default: string,
|
|
||||||
push: string,
|
|
||||||
pull: string,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const design: DesignDoc = {
|
|
||||||
"_id": "_design/replicate",
|
|
||||||
"_rev": undefined as string | undefined,
|
|
||||||
"ver": 2,
|
|
||||||
"filters": {
|
|
||||||
"default": function (doc: any, req: any) {
|
|
||||||
return !("remote" in doc && doc.remote);
|
|
||||||
}.toString(),
|
|
||||||
"push": function (doc: any, req: any) {
|
|
||||||
return true;
|
|
||||||
}.toString(),
|
|
||||||
"pull": function (doc: any, req: any) {
|
|
||||||
return !(doc.type && doc.type == "leaf")
|
|
||||||
}.toString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can use the filter on replication : filter: 'replicate/default',
|
|
||||||
|
|
||||||
try {
|
|
||||||
const w = await db.get<DesignDoc>(design._id);
|
|
||||||
if (w.ver < design.ver) {
|
|
||||||
design._rev = w._rev;
|
|
||||||
//@ts-ignore
|
|
||||||
await db.put(design);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (ex) {
|
|
||||||
if (ex.status && ex.status == 404) {
|
|
||||||
delete design._rev;
|
|
||||||
//@ts-ignore
|
|
||||||
await db.put(design);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
Logger("Could not make design documents", LOG_LEVEL.INFO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user