Simplify remote feature checks and clarify manual rebuild guidance

This commit is contained in:
vorotamoroz
2026-09-27 11:08:53 +00:00
parent 9a047d44f3
commit 2c6cd56618
15 changed files with 251 additions and 684 deletions
@@ -5,7 +5,6 @@ import {
DEFAULT_SETTINGS,
LOG_LEVEL_NOTICE,
type ObsidianLiveSyncSettings,
type EncryptionSettings,
LOG_LEVEL_VERBOSE,
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import { Menu, type ButtonComponent } from "@/deps.ts";
@@ -32,13 +31,11 @@ import {
import { ConnectionStringParser } from "@vrtmrz/livesync-commonlib/compat/common/ConnectionString";
import type { RemoteConfigurationResult } from "@vrtmrz/livesync-commonlib/compat/common/ConnectionString";
import SetupRemote from "@/modules/features/SetupWizard/dialogs/SetupRemote.svelte";
import SetupRemoteE2EE from "@/modules/features/SetupWizard/dialogs/SetupRemoteE2EE.svelte";
import SetupRemoteCouchDB from "@/modules/features/SetupWizard/dialogs/SetupRemoteCouchDB.svelte";
import SetupRemoteBucket from "@/modules/features/SetupWizard/dialogs/SetupRemoteBucket.svelte";
import type {
SetupRemoteCouchDBInitialData,
SetupRemoteCouchDBResultType,
SetupRemoteE2EEResultType,
} from "@/modules/features/SetupWizard/dialogs/setupDialogTypes.ts";
import { syncActivatedRemoteSettings } from "./remoteConfigBuffer.ts";
@@ -119,34 +116,15 @@ export function paneRemoteConfig(
.onClick(async () => {
const setupManager = this.core.getModule(SetupManager);
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
const e2eeConf = await setupManager.dialogManager.openWithExplicitCancel<
SetupRemoteE2EEResultType,
EncryptionSettings
>(SetupRemoteE2EE, originalSettings);
if (e2eeConf === "cancelled") {
return;
}
const onlyInternalMetadataPreferenceChanged =
originalSettings.encryptInternalMetadata !== e2eeConf.encryptInternalMetadata &&
originalSettings.encrypt === e2eeConf.encrypt &&
originalSettings.passphrase === e2eeConf.passphrase &&
originalSettings.E2EEAlgorithm === e2eeConf.E2EEAlgorithm &&
originalSettings.usePathObfuscation === e2eeConf.usePathObfuscation;
if (onlyInternalMetadataPreferenceChanged) {
await this.services.setting.applyPartial(
{ encryptInternalMetadata: e2eeConf.encryptInternalMetadata },
true
);
this.editingSettings.encryptInternalMetadata = e2eeConf.encryptInternalMetadata;
const applied = await setupManager.onlyE2EEConfiguration(UserMode.Update, originalSettings);
if (applied) {
this.editingSettings.encryptInternalMetadata =
this.core.settings.encryptInternalMetadata;
if (this.initialSettings) {
this.initialSettings.encryptInternalMetadata = e2eeConf.encryptInternalMetadata;
this.initialSettings.encryptInternalMetadata =
this.core.settings.encryptInternalMetadata;
}
this.requestUpdate();
} else {
await setupManager.onConfirmApplySettingsFromWizard(
{ ...originalSettings, ...e2eeConf },
UserMode.Update
);
}
updateE2EESummary();
})
@@ -162,24 +162,15 @@ describe("paneRemoteConfig", () => {
encryptInternalMetadata: false,
remoteConfigurations: {},
};
const applyPartial = vi.fn(async () => {});
const onConfirmApplySettingsFromWizard = vi.fn(async () => {});
const setupManager = {
dialogManager: {
openWithExplicitCancel: vi.fn(async () => ({
encrypt: true,
passphrase: "passphrase",
E2EEAlgorithm: "v2",
usePathObfuscation: true,
encryptInternalMetadata: true,
})),
},
onConfirmApplySettingsFromWizard,
onlyE2EEConfiguration: vi.fn(async () => {
host.core.settings.encryptInternalMetadata = true;
return true;
}),
};
const host = {
editingSettings: { ...originalSettings },
initialSettings: { ...originalSettings },
services: { setting: { applyPartial } },
core: {
settings: { ...originalSettings },
getModule: vi.fn(() => setupManager),
@@ -198,8 +189,7 @@ describe("paneRemoteConfig", () => {
paneRemoteConfig.call(host as never, {} as HTMLElement, { addPanel } as never);
await runtime.clickHandlers[0]();
expect(applyPartial).toHaveBeenCalledWith({ encryptInternalMetadata: true }, true);
expect(onConfirmApplySettingsFromWizard).not.toHaveBeenCalled();
expect(setupManager.onlyE2EEConfiguration).toHaveBeenCalledOnce();
expect(host.editingSettings.encryptInternalMetadata).toBe(true);
expect(host.initialSettings.encryptInternalMetadata).toBe(true);
expect(host.requestUpdate).toHaveBeenCalledOnce();
+25
View File
@@ -336,6 +336,31 @@ export class SetupManager extends AbstractModule {
this._log("E2EE configuration cancelled.", LOG_LEVEL_NOTICE);
return false;
}
const onlyInternalMetadataPreferenceChanged =
currentSetting.encryptInternalMetadata !== e2eeConf.encryptInternalMetadata &&
currentSetting.encrypt === e2eeConf.encrypt &&
currentSetting.passphrase === e2eeConf.passphrase &&
currentSetting.E2EEAlgorithm === e2eeConf.E2EEAlgorithm &&
currentSetting.usePathObfuscation === e2eeConf.usePathObfuscation;
if (userMode === UserMode.Update && onlyInternalMetadataPreferenceChanged) {
if (e2eeConf.encryptInternalMetadata && currentSetting.remoteType === REMOTE_COUCHDB) {
const proceed = "Enable without rebuilding — update every other device first";
const choice = await this.core.confirm.askSelectStringDialogue(
"A manual remote Rebuild is strongly recommended to protect existing Metadata. " +
"Before continuing without rebuilding, update every other synchronising device to a version " +
"which supports this option, including devices currently running LiveSync. " +
"Existing Metadata remains unchanged until it is rewritten or rebuilt.",
[proceed, "Cancel"],
{ title: "Encrypt internal file Metadata", defaultAction: "Cancel" }
);
if (choice !== proceed) return false;
}
await this.services.setting.applyPartial(
{ encryptInternalMetadata: e2eeConf.encryptInternalMetadata },
true
);
return true;
}
const newSetting = {
...currentSetting,
...e2eeConf,
@@ -659,3 +659,23 @@ describe("SetupManager", () => {
expect(setting.currentSettings().P2P_ActiveRemoteConfigurationId).toBe("existing");
});
});
describe("internal Metadata configuration", () => {
it.each([true, false])(
"applies the preference only after accepting the no-Rebuild warning (%s)",
async (accept) => {
const { manager, setting, dialogManager, core } = createSetupManager();
const current = { ...setting.settings, encryptInternalMetadata: false, remoteType: REMOTE_COUCHDB };
dialogManager.openWithExplicitCancel.mockResolvedValue({ ...current, encryptInternalMetadata: true });
const ask = vi.fn(async (_message: string, choices: string[]) => (accept ? choices[0] : "Cancel"));
core.confirm = { askSelectStringDialogue: ask };
const apply = vi.spyOn(setting, "applyPartial").mockResolvedValue(undefined);
await expect(manager.onlyE2EEConfiguration(UserMode.Update, current)).resolves.toBe(accept);
expect(ask.mock.calls[0][1][0]).toContain("update every other device first");
expect(ask.mock.calls[0][0]).toContain("currently running LiveSync");
expect(apply).toHaveBeenCalledTimes(accept ? 1 : 0);
expect(core.rebuilder.scheduleRebuild).not.toHaveBeenCalled();
expect(core.rebuilder.scheduleFetch).not.toHaveBeenCalled();
}
);
});
@@ -105,7 +105,8 @@
(Obfuscate Properties). The remote type is selected later in this setup wizard.
<br />
It protects Metadata written after the option is enabled; existing Metadata is not rewritten. A manual remote
Rebuild is strongly recommended to protect existing Metadata.
Rebuild is strongly recommended to protect existing Metadata. Update every other synchronising device to a compatible
version before enabling this option, including devices currently running LiveSync.
</InfoNote>
<ExtraItems title={translateMessage("Advanced")}>