Compare commits

...
7 changed files with 75 additions and 7 deletions
+4 -4
View File
@@ -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
View File
@@ -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",
+2 -1
View File
@@ -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 };
+12 -1
View File
@@ -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
View File
@@ -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