mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-19 09:07:06 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa97f96960 | ||
|
|
916ec4f276 | ||
|
|
8ea656a7da | ||
|
|
a3231b0906 | ||
|
|
a8fe11e0e0 |
Generated
+4
-4
@@ -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",
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
};
|
||||
|
||||
+10
@@ -12,6 +12,16 @@ Earlier releases remain available in the 1.0 release history, the 1.0 preview hi
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Synchronisation
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed a case where unchanged local files could overwrite newer synchronised content after a restart. (#994)
|
||||
- Local content whose origin cannot be established is now preserved as a conflict, allowing you to review and choose which version to keep.
|
||||
- The same protection applies to ordinary file synchronisation in the command-line tool.
|
||||
- Fast Fetch now avoids repeated requests for the same Security Seed during a transfer, reducing network traffic during initial setup.
|
||||
- Object Storage synchronisation now reads fresh synchronisation parameters before it can write to the remote, then reuses them for that transfer. A failed parameter read stops the transfer before writing.
|
||||
|
||||
## 1.0.29
|
||||
|
||||
16th September, 2026
|
||||
|
||||
Reference in New Issue
Block a user