mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-19 00:57:05 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c2b9c90e4 | ||
|
|
ae264c3383 | ||
|
|
df023f016e | ||
|
|
8dcd53090a | ||
|
|
9998bd6dde | ||
|
|
fa97f96960 | ||
|
|
916ec4f276 | ||
|
|
8ea656a7da | ||
|
|
a3231b0906 | ||
|
|
a8fe11e0e0 |
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "1.0.29",
|
||||
"version": "1.0.30",
|
||||
"minAppVersion": "1.7.2",
|
||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"author": "vorotamoroz",
|
||||
|
||||
Generated
+12
-12
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "1.0.29",
|
||||
"version": "1.0.30",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-livesync",
|
||||
"version": "1.0.29",
|
||||
"version": "1.0.30",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"src/apps/cli",
|
||||
@@ -23,7 +23,7 @@
|
||||
"@smithy/types": "^4.14.3",
|
||||
"@smithy/util-retry": "^4.4.5",
|
||||
"@vrtmrz/browser-ui-kit": "0.1.0",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.26",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.27",
|
||||
"@vrtmrz/obsidian-plugin-kit": "0.1.4",
|
||||
"@vrtmrz/ui-interactions": "0.1.2",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
@@ -4567,9 +4567,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vrtmrz/livesync-commonlib": {
|
||||
"version": "0.1.26",
|
||||
"resolved": "https://registry.npmjs.org/@vrtmrz/livesync-commonlib/-/livesync-commonlib-0.1.26.tgz",
|
||||
"integrity": "sha512-AVJky976PP1M+g18im6tJ1eKSq82uN73vkS98WWZWvMJ1UDz6O8YRVWa9dkFakAVXecdxxxLkiD45g5HTRnn6Q==",
|
||||
"version": "0.1.27",
|
||||
"resolved": "https://registry.npmjs.org/@vrtmrz/livesync-commonlib/-/livesync-commonlib-0.1.27.tgz",
|
||||
"integrity": "sha512-n/AIqXPGN9Z7zmq0QSKnlE5uY5hTZgXW6VM6NJERR0/ggx3TTv1b3WsSbiLpCeU7Mu6KbPqC2BSjr4jCGvSdCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.808.0",
|
||||
@@ -5629,9 +5629,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/devalue": {
|
||||
"version": "5.8.1",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz",
|
||||
"integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==",
|
||||
"version": "5.9.2",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.2.tgz",
|
||||
"integrity": "sha512-po4PAY5c53tw5XMocSnf8A/5OHhbbUftpr93aEN6BBoAdntUmK7vu7wOATqvt7cXO7m1Cl4gMVn6p7n6n4mj0w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -12665,7 +12665,7 @@
|
||||
},
|
||||
"src/apps/cli": {
|
||||
"name": "self-hosted-livesync-cli",
|
||||
"version": "1.0.29-cli",
|
||||
"version": "1.0.30-cli",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"minimatch": "^10.2.5",
|
||||
@@ -12690,7 +12690,7 @@
|
||||
},
|
||||
"src/apps/webapp": {
|
||||
"name": "livesync-webapp",
|
||||
"version": "1.0.29-webapp",
|
||||
"version": "1.0.30-webapp",
|
||||
"dependencies": {
|
||||
"octagonal-wheels": "^0.1.54"
|
||||
},
|
||||
@@ -12702,7 +12702,7 @@
|
||||
}
|
||||
},
|
||||
"src/apps/webpeer": {
|
||||
"version": "1.0.29-webpeer",
|
||||
"version": "1.0.30-webpeer",
|
||||
"dependencies": {
|
||||
"octagonal-wheels": "^0.1.54"
|
||||
},
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "1.0.29",
|
||||
"version": "1.0.30",
|
||||
"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",
|
||||
@@ -183,7 +183,7 @@
|
||||
"@smithy/types": "^4.14.3",
|
||||
"@smithy/util-retry": "^4.4.5",
|
||||
"@vrtmrz/browser-ui-kit": "0.1.0",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.26",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.27",
|
||||
"@vrtmrz/obsidian-plugin-kit": "0.1.4",
|
||||
"@vrtmrz/ui-interactions": "0.1.2",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "self-hosted-livesync-cli",
|
||||
"private": true,
|
||||
"version": "1.0.29-cli",
|
||||
"version": "1.0.30-cli",
|
||||
"main": "dist/index.cjs",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "livesync-webapp",
|
||||
"private": true,
|
||||
"version": "1.0.29-webapp",
|
||||
"version": "1.0.30-webapp",
|
||||
"type": "module",
|
||||
"description": "Browser-based Self-hosted LiveSync using FileSystem API",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "webpeer",
|
||||
"private": true,
|
||||
"version": "1.0.29-webpeer",
|
||||
"version": "1.0.30-webpeer",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
CAPABILITY_NOT_APPLICABLE,
|
||||
CENTRAL_REMOTE_REPLICATION_READINESS,
|
||||
NO_INTERACTION,
|
||||
PROVIDER_OWNED_CENTRAL_REMOTE_REPLICATION_READINESS,
|
||||
REPLICATION_PROGRESS_PRESENTATIONS,
|
||||
REMOTE_RESOURCE_KINDS,
|
||||
defineReplicatorProviderDefinitions,
|
||||
@@ -134,7 +135,7 @@ export function createCentralReplicatorProviderDefinitions(
|
||||
[REMOTE_MINIO]: {
|
||||
kind: REMOTE_MINIO,
|
||||
diagnosticName: "Object Storage",
|
||||
readiness: CENTRAL_REMOTE_REPLICATION_READINESS,
|
||||
readiness: PROVIDER_OWNED_CENTRAL_REMOTE_REPLICATION_READINESS,
|
||||
isConfigured: (settings) =>
|
||||
settings.remoteType === REMOTE_MINIO && !!settings.endpoint?.trim() && !!settings.bucket?.trim(),
|
||||
configurationIdentity: getObjectStorageReplicatorConfigurationIdentity,
|
||||
|
||||
@@ -47,6 +47,12 @@ describe("central Replicator provider definitions", () => {
|
||||
.toEqual(["connection", "preferred-tweak", "security-seed", "synchronisation-information"].sort());
|
||||
});
|
||||
|
||||
it("lets Journal prepare its own fresh Security Seed while CouchDB uses central preparation", () => {
|
||||
const definitions = createCentralReplicatorProviderDefinitions({} as never);
|
||||
expect(definitions.get(REMOTE_COUCHDB)?.readiness.centralRemotePreparation).toBe("required");
|
||||
expect(definitions.get(REMOTE_MINIO)?.readiness.centralRemotePreparation).toBe("provider-owned");
|
||||
});
|
||||
|
||||
it("composes CouchDB and Object Storage policies outside LiveSyncBaseCore", async () => {
|
||||
const host = {} as Parameters<typeof createCentralReplicatorProviderDefinitions>[0];
|
||||
const definitions = createCentralReplicatorProviderDefinitions(host);
|
||||
|
||||
@@ -285,6 +285,46 @@ describe("replicator probe factories", () => {
|
||||
expect(objectReplicator.closeReplication).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("reads the Security Seed once per resource, including concurrent reads, and refreshes for a new resource", async () => {
|
||||
const settings = createSettings();
|
||||
const factory = createCouchDBSecuritySeedResourceFactory({} as never);
|
||||
const firstResource = await factory(settings);
|
||||
const firstReplicator = mocks.couchDB[0];
|
||||
const firstSeed = new Uint8Array([1]);
|
||||
firstReplicator.getReplicationPBKDF2Salt.mockResolvedValue(firstSeed);
|
||||
|
||||
const [first, concurrent] = await Promise.all([firstResource.read(), firstResource.read()]);
|
||||
expect(first).toBe(firstSeed);
|
||||
expect(concurrent).toBe(firstSeed);
|
||||
await expect(firstResource.read()).resolves.toBe(firstSeed);
|
||||
expect(firstReplicator.getReplicationPBKDF2Salt).toHaveBeenCalledOnce();
|
||||
expect(firstReplicator.getReplicationPBKDF2Salt).toHaveBeenCalledWith({ ...settings }, true);
|
||||
await firstResource.dispose();
|
||||
|
||||
const nextResource = await factory(settings);
|
||||
const nextReplicator = mocks.couchDB[1];
|
||||
const nextSeed = new Uint8Array([2]);
|
||||
nextReplicator.getReplicationPBKDF2Salt.mockResolvedValue(nextSeed);
|
||||
await expect(nextResource.read()).resolves.toBe(nextSeed);
|
||||
expect(nextReplicator.getReplicationPBKDF2Salt).toHaveBeenCalledOnce();
|
||||
expect(nextReplicator.getReplicationPBKDF2Salt).toHaveBeenCalledWith({ ...settings }, true);
|
||||
await nextResource.dispose();
|
||||
});
|
||||
|
||||
it("retries a failed Security Seed read within the same resource", async () => {
|
||||
const resource = await createCouchDBSecuritySeedResourceFactory({} as never)(createSettings());
|
||||
const replicator = mocks.couchDB[0];
|
||||
const failure = new Error("connection interrupted");
|
||||
const seed = new Uint8Array([1]);
|
||||
replicator.getReplicationPBKDF2Salt.mockRejectedValueOnce(failure).mockResolvedValueOnce(seed);
|
||||
|
||||
await expect(resource.read()).rejects.toBe(failure);
|
||||
await expect(resource.read()).resolves.toBe(seed);
|
||||
await expect(resource.read()).resolves.toBe(seed);
|
||||
expect(replicator.getReplicationPBKDF2Salt).toHaveBeenCalledTimes(2);
|
||||
await resource.dispose();
|
||||
});
|
||||
|
||||
it("checks synchronisation information through an owned connection and disposes the private Replicator", async () => {
|
||||
const settings = createSettings();
|
||||
const snapshot = { ...settings };
|
||||
|
||||
@@ -21,8 +21,19 @@ function createSecuritySeedResourceFactory(
|
||||
return (setting) => {
|
||||
const snapshot = snapshotRemoteSettings(setting);
|
||||
const replicator = createReplicator();
|
||||
let readPromise: Promise<Uint8Array<ArrayBuffer>> | undefined;
|
||||
const read = () => {
|
||||
if (readPromise) return readPromise;
|
||||
const pending = Promise.resolve().then(() => replicator.getReplicationPBKDF2Salt(snapshot, true));
|
||||
readPromise = pending;
|
||||
// A failed read must not poison a later retry within the same resource.
|
||||
void pending.catch(() => {
|
||||
if (readPromise === pending) readPromise = undefined;
|
||||
});
|
||||
return pending;
|
||||
};
|
||||
return Promise.resolve({
|
||||
read: () => replicator.getReplicationPBKDF2Salt(snapshot, true),
|
||||
read,
|
||||
dispose: createReplicatorDisposer(replicator),
|
||||
});
|
||||
};
|
||||
|
||||
+13
@@ -12,6 +12,19 @@ Earlier releases remain available in the 1.0 release history, the 1.0 preview hi
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 1.0.30
|
||||
|
||||
18th September, 2026
|
||||
|
||||
### Synchronisation
|
||||
|
||||
#### Fixed
|
||||
|
||||
- After a restart, unchanged local files no longer overwrite newer synchronised content. (#994)
|
||||
- When LiveSync cannot establish a local file's origin, it keeps the file as a conflict for you to review. This also applies to ordinary file synchronisation in the command-line tool.
|
||||
- Fast Fetch completes initial setup with fewer remote requests.
|
||||
- Object Storage synchronisation makes fewer remote requests while still checking its parameters before writing.
|
||||
|
||||
## 1.0.29
|
||||
|
||||
16th September, 2026
|
||||
|
||||
+2
-1
@@ -40,5 +40,6 @@
|
||||
"1.0.26": "1.7.2",
|
||||
"1.0.27": "1.7.2",
|
||||
"1.0.28": "1.7.2",
|
||||
"1.0.29": "1.7.2"
|
||||
"1.0.29": "1.7.2",
|
||||
"1.0.30": "1.7.2"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user