Compare commits

...

6 Commits
0.7.0 ... 0.8.0

Author SHA1 Message Date
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
vorotamoroz
31ab0e90f6 Fixed:
- Device and vault name is now not stored in the data.json.
You can synchronize LiveSync's configuration!
2022-02-18 20:10:43 +09:00
12 changed files with 180 additions and 51 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.0",
"version": "0.8.0",
"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",

18
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "obsidian-livesync",
"version": "0.7.0",
"version": "0.8.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "obsidian-livesync",
"version": "0.7.0",
"version": "0.8.0",
"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.13.30",
"rollup": "^2.32.1",
"svelte-preprocess": "^4.10.2",
"tslib": "^2.2.0",
@@ -2659,9 +2659,9 @@
}
},
"node_modules/obsidian": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.13.11.tgz",
"integrity": "sha512-KxOvAh4CG5vzcukmHvyuK9hUIr6ZFlM9FQfGZEwrrEV8VG2/W2Tk5cWrg0VM7EkGE3QBmjX6owjIDIO8QDXVUQ==",
"version": "0.13.30",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.13.30.tgz",
"integrity": "sha512-uAOrIyeHE9qYzg1Qjfpy/qlyLUFX9oyKWeHYO8NVDoI+pm5VUTMe7XWcsXPwb9iVsVmggVJcdV15Vqm9bljhxQ==",
"dev": true,
"dependencies": {
"@codemirror/state": "^0.19.6",
@@ -5470,9 +5470,9 @@
}
},
"obsidian": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.13.11.tgz",
"integrity": "sha512-KxOvAh4CG5vzcukmHvyuK9hUIr6ZFlM9FQfGZEwrrEV8VG2/W2Tk5cWrg0VM7EkGE3QBmjX6owjIDIO8QDXVUQ==",
"version": "0.13.30",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.13.30.tgz",
"integrity": "sha512-uAOrIyeHE9qYzg1Qjfpy/qlyLUFX9oyKWeHYO8NVDoI+pm5VUTMe7XWcsXPwb9iVsVmggVJcdV15Vqm9bljhxQ==",
"dev": true,
"requires": {
"@codemirror/state": "^0.19.6",

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.7.0",
"version": "0.8.0",
"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",
@@ -24,7 +24,7 @@
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.25.2",
"obsidian": "^0.13.11",
"obsidian": "^0.13.30",
"rollup": "^2.32.1",
"tslib": "^2.2.0",
"typescript": "^4.2.4",

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")
@@ -875,9 +879,9 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setDesc("")
.addText((text) => {
text.setPlaceholder("desktop-main")
.setValue(this.plugin.settings.deviceAndVaultName)
.setValue(this.plugin.deviceAndVaultName)
.onChange(async (value) => {
this.plugin.settings.deviceAndVaultName = value;
this.plugin.deviceAndVaultName = value;
await this.plugin.saveSettings();
});
// text.inputEl.setAttribute("type", "password");

View File

@@ -56,7 +56,7 @@
$: {
deviceAndPlugins = {};
for (const p of plugins) {
if (p.deviceVaultName == plugin.settings.deviceAndVaultName && !showOwnPlugins) {
if (p.deviceVaultName == plugin.deviceAndVaultName && !showOwnPlugins) {
continue;
}
if (!(p.deviceVaultName in deviceAndPlugins)) {

View File

@@ -63,6 +63,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
statusBar: HTMLElement;
statusBar2: HTMLElement;
suspended: boolean;
deviceAndVaultName: string;
setInterval(handler: () => any, timeout?: number): number {
const timer = window.setInterval(handler, timeout);
@@ -320,6 +321,15 @@ 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;
const lsname = "obsidian-live-sync-vaultanddevicename-" + this.app.vault.getName();
if (this.settings.deviceAndVaultName != "") {
if (!localStorage.getItem(lsname)) {
this.deviceAndVaultName = this.settings.deviceAndVaultName;
localStorage.setItem(lsname, this.deviceAndVaultName);
this.settings.deviceAndVaultName = "";
}
}
this.deviceAndVaultName = localStorage.getItem(lsname) || "";
}
triggerRealizeSettingSyncMode() {
@@ -327,6 +337,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
async saveSettings() {
const lsname = "obsidian-live-sync-vaultanddevicename-" + this.app.vault.getName();
localStorage.setItem(lsname, this.deviceAndVaultName || "");
await this.saveData(this.settings);
this.localDatabase.settings = this.settings;
this.triggerRealizeSettingSyncMode();
@@ -1428,7 +1441,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
plugins[v.deviceVaultName].push(v);
allPlugins[v._id] = v;
if (v.deviceVaultName == this.settings.deviceAndVaultName) {
if (v.deviceVaultName == this.deviceAndVaultName) {
thisDevicePlugins[v.manifest.id] = v;
}
}
@@ -1443,15 +1456,15 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
Logger("You have to encrypt the database to use plugin setting sync.", LOG_LEVEL.NOTICE);
return;
}
if (!this.settings.deviceAndVaultName) {
if (!this.deviceAndVaultName) {
Logger("You have to set your device and vault name.", LOG_LEVEL.NOTICE);
return;
}
Logger("Sweeping plugins", logLevel);
const db = this.localDatabase.localDatabase;
const oldDocs = await db.allDocs({
startkey: `ps:${this.settings.deviceAndVaultName}-`,
endkey: `ps:${this.settings.deviceAndVaultName}.`,
startkey: `ps:${this.deviceAndVaultName}-`,
endkey: `ps:${this.deviceAndVaultName}.`,
include_docs: true,
});
Logger("OLD DOCS.", LOG_LEVEL.VERBOSE);
@@ -1476,9 +1489,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
mtime = (await adapter.stat(path + "/data.json")).mtime;
}
const p: PluginDataEntry = {
_id: `ps:${this.settings.deviceAndVaultName}-${m.id}`,
_id: `ps:${this.deviceAndVaultName}-${m.id}`,
dataJson: pluginData["data.json"],
deviceVaultName: this.settings.deviceAndVaultName,
deviceVaultName: this.deviceAndVaultName,
mainJs: pluginData["main.js"],
styleCss: pluginData["styles.css"],
manifest: m,

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 } from "obsidian";
export const isValidRemoteCouchDBURI = (uri: string): boolean => {
if (uri.startsWith("https://")) return true;
@@ -12,8 +13,17 @@ 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 }> => {
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 +45,54 @@ 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 requestUrl(requestParam);
if (method == "POST" || method == "PUT") {
last_post_successed = r.status - (r.status % 100) == 200;
} else {
last_post_successed = true;
}
if (r.status - (r.status % 100) !== 200) {
throw new Error(`Request Error:${r.status}`);
}
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") {