mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-19 15:52:31 +00:00
Merge pull request #1204 from vrtmrz/fix/fast-fetch-security-seed-reads
Avoid repeated remote parameter reads during Fast Fetch and Journal transfers
This commit is contained in:
Generated
+4
-4
@@ -23,7 +23,7 @@
|
|||||||
"@smithy/types": "^4.14.3",
|
"@smithy/types": "^4.14.3",
|
||||||
"@smithy/util-retry": "^4.4.5",
|
"@smithy/util-retry": "^4.4.5",
|
||||||
"@vrtmrz/browser-ui-kit": "0.1.0",
|
"@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/obsidian-plugin-kit": "0.1.4",
|
||||||
"@vrtmrz/ui-interactions": "0.1.2",
|
"@vrtmrz/ui-interactions": "0.1.2",
|
||||||
"diff-match-patch": "^1.0.5",
|
"diff-match-patch": "^1.0.5",
|
||||||
@@ -4567,9 +4567,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vrtmrz/livesync-commonlib": {
|
"node_modules/@vrtmrz/livesync-commonlib": {
|
||||||
"version": "0.1.26",
|
"version": "0.1.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vrtmrz/livesync-commonlib/-/livesync-commonlib-0.1.26.tgz",
|
"resolved": "https://registry.npmjs.org/@vrtmrz/livesync-commonlib/-/livesync-commonlib-0.1.27.tgz",
|
||||||
"integrity": "sha512-AVJky976PP1M+g18im6tJ1eKSq82uN73vkS98WWZWvMJ1UDz6O8YRVWa9dkFakAVXecdxxxLkiD45g5HTRnn6Q==",
|
"integrity": "sha512-n/AIqXPGN9Z7zmq0QSKnlE5uY5hTZgXW6VM6NJERR0/ggx3TTv1b3WsSbiLpCeU7Mu6KbPqC2BSjr4jCGvSdCA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.808.0",
|
"@aws-sdk/client-s3": "^3.808.0",
|
||||||
|
|||||||
+1
-1
@@ -183,7 +183,7 @@
|
|||||||
"@smithy/types": "^4.14.3",
|
"@smithy/types": "^4.14.3",
|
||||||
"@smithy/util-retry": "^4.4.5",
|
"@smithy/util-retry": "^4.4.5",
|
||||||
"@vrtmrz/browser-ui-kit": "0.1.0",
|
"@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/obsidian-plugin-kit": "0.1.4",
|
||||||
"@vrtmrz/ui-interactions": "0.1.2",
|
"@vrtmrz/ui-interactions": "0.1.2",
|
||||||
"diff-match-patch": "^1.0.5",
|
"diff-match-patch": "^1.0.5",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
CAPABILITY_NOT_APPLICABLE,
|
CAPABILITY_NOT_APPLICABLE,
|
||||||
CENTRAL_REMOTE_REPLICATION_READINESS,
|
CENTRAL_REMOTE_REPLICATION_READINESS,
|
||||||
NO_INTERACTION,
|
NO_INTERACTION,
|
||||||
|
PROVIDER_OWNED_CENTRAL_REMOTE_REPLICATION_READINESS,
|
||||||
REPLICATION_PROGRESS_PRESENTATIONS,
|
REPLICATION_PROGRESS_PRESENTATIONS,
|
||||||
REMOTE_RESOURCE_KINDS,
|
REMOTE_RESOURCE_KINDS,
|
||||||
defineReplicatorProviderDefinitions,
|
defineReplicatorProviderDefinitions,
|
||||||
@@ -134,7 +135,7 @@ export function createCentralReplicatorProviderDefinitions(
|
|||||||
[REMOTE_MINIO]: {
|
[REMOTE_MINIO]: {
|
||||||
kind: REMOTE_MINIO,
|
kind: REMOTE_MINIO,
|
||||||
diagnosticName: "Object Storage",
|
diagnosticName: "Object Storage",
|
||||||
readiness: CENTRAL_REMOTE_REPLICATION_READINESS,
|
readiness: PROVIDER_OWNED_CENTRAL_REMOTE_REPLICATION_READINESS,
|
||||||
isConfigured: (settings) =>
|
isConfigured: (settings) =>
|
||||||
settings.remoteType === REMOTE_MINIO && !!settings.endpoint?.trim() && !!settings.bucket?.trim(),
|
settings.remoteType === REMOTE_MINIO && !!settings.endpoint?.trim() && !!settings.bucket?.trim(),
|
||||||
configurationIdentity: getObjectStorageReplicatorConfigurationIdentity,
|
configurationIdentity: getObjectStorageReplicatorConfigurationIdentity,
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ describe("central Replicator provider definitions", () => {
|
|||||||
.toEqual(["connection", "preferred-tweak", "security-seed", "synchronisation-information"].sort());
|
.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 () => {
|
it("composes CouchDB and Object Storage policies outside LiveSyncBaseCore", async () => {
|
||||||
const host = {} as Parameters<typeof createCentralReplicatorProviderDefinitions>[0];
|
const host = {} as Parameters<typeof createCentralReplicatorProviderDefinitions>[0];
|
||||||
const definitions = createCentralReplicatorProviderDefinitions(host);
|
const definitions = createCentralReplicatorProviderDefinitions(host);
|
||||||
|
|||||||
@@ -285,6 +285,46 @@ describe("replicator probe factories", () => {
|
|||||||
expect(objectReplicator.closeReplication).toHaveBeenCalledOnce();
|
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 () => {
|
it("checks synchronisation information through an owned connection and disposes the private Replicator", async () => {
|
||||||
const settings = createSettings();
|
const settings = createSettings();
|
||||||
const snapshot = { ...settings };
|
const snapshot = { ...settings };
|
||||||
|
|||||||
@@ -21,8 +21,19 @@ function createSecuritySeedResourceFactory(
|
|||||||
return (setting) => {
|
return (setting) => {
|
||||||
const snapshot = snapshotRemoteSettings(setting);
|
const snapshot = snapshotRemoteSettings(setting);
|
||||||
const replicator = createReplicator();
|
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({
|
return Promise.resolve({
|
||||||
read: () => replicator.getReplicationPBKDF2Salt(snapshot, true),
|
read,
|
||||||
dispose: createReplicatorDisposer(replicator),
|
dispose: createReplicatorDisposer(replicator),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
+10
@@ -12,6 +12,16 @@ Earlier releases remain available in the 1.0 release history, the 1.0 preview hi
|
|||||||
|
|
||||||
## Unreleased
|
## 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
|
## 1.0.29
|
||||||
|
|
||||||
16th September, 2026
|
16th September, 2026
|
||||||
|
|||||||
Reference in New Issue
Block a user