mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-07-25 14:02:58 +00:00
refactor: consume Commonlib as a package
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { getLanguage } from "@/deps";
|
||||
import { createServiceFeature } from "@lib/interfaces/ServiceModule";
|
||||
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@lib/common/rosetta";
|
||||
import { $msg, __onMissingTranslation, setLang } from "@lib/common/i18n";
|
||||
import { createServiceFeature } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@vrtmrz/livesync-commonlib/compat/common/rosetta";
|
||||
import { $msg, __onMissingTranslation, setLang } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
||||
|
||||
function tryGetLanguage() {
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
|
||||
import type { NecessaryServices } from "@lib/interfaces/ServiceModule";
|
||||
import { type LogFunction } from "@lib/services/lib/logUtils";
|
||||
import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager";
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import { type LogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import { UnresolvedErrorManager } from "@vrtmrz/livesync-commonlib/compat/services/base/UnresolvedErrorManager";
|
||||
import {
|
||||
ExtraOnLocal,
|
||||
ExtraOnRemote,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
normaliseFullScanOptions,
|
||||
synchroniseAllFilesBetweenDBandStorage,
|
||||
type FullScanOptions,
|
||||
} from "@lib/serviceFeatures/offlineScanner";
|
||||
} from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner";
|
||||
import { adjustSettingToRemoteIfNeeded, processVaultInitialisation } from "./redFlag";
|
||||
|
||||
export const SIMPLE_FETCH_STAGE1_REMOTE_WINS = "Overwrite all with remote files";
|
||||
@@ -215,7 +215,7 @@ export async function askAndPerformFastSetupOnScheduledFetchAll(
|
||||
await host.serviceModules.rebuilder.$fetchLocalDBFast(false);
|
||||
|
||||
// 2. Call the extended synchroniseAllFilesBetweenDBandStorage to reflect changes in storage
|
||||
const errorManager = new UnresolvedErrorManager(host.services.appLifecycle);
|
||||
const errorManager = new UnresolvedErrorManager(host.services.appLifecycle, host.services.context.events);
|
||||
const syncResult = await synchroniseAllFilesBetweenDBandStorage(
|
||||
host,
|
||||
log,
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||
import type { NecessaryServices } from "@lib/interfaces/ServiceModule";
|
||||
import { createInstanceLogFunction, type LogFunction } from "@lib/services/lib/logUtils";
|
||||
import { FlagFilesHumanReadable, FlagFilesOriginal } from "@lib/common/models/redflag.const";
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import { createInstanceLogFunction, type LogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import { FlagFilesHumanReadable, FlagFilesOriginal } from "@vrtmrz/livesync-commonlib/compat/common/models/redflag.const";
|
||||
import FetchEverything from "@/modules/features/SetupWizard/dialogs/FetchEverything.svelte";
|
||||
import RebuildEverything from "@/modules/features/SetupWizard/dialogs/RebuildEverything.svelte";
|
||||
import { extractObject } from "octagonal-wheels/object";
|
||||
import { REMOTE_MINIO, REMOTE_P2P } from "@lib/common/models/setting.const";
|
||||
import type { ObsidianLiveSyncSettings } from "@lib/common/models/setting.type";
|
||||
import { TweakValuesShouldMatchedTemplate } from "@lib/common/models/tweak.definition";
|
||||
import { REMOTE_MINIO, REMOTE_P2P } from "@vrtmrz/livesync-commonlib/compat/common/models/setting.const";
|
||||
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/models/setting.type";
|
||||
import { TweakValuesShouldMatchedTemplate } from "@vrtmrz/livesync-commonlib/compat/common/models/tweak.definition";
|
||||
import type {
|
||||
FetchEverythingResult,
|
||||
RebuildEverythingResult,
|
||||
} from "@/modules/features/SetupWizard/dialogs/setupDialogTypes";
|
||||
import { askAndPerformFastSetupOnScheduledFetchAll } from "./redFlag.simpleFetch";
|
||||
import { ConnectionStringParser } from "@lib/common/ConnectionString";
|
||||
import { activateRemoteConfiguration } from "@lib/serviceFeatures/remoteConfig";
|
||||
import { ConnectionStringParser } from "@vrtmrz/livesync-commonlib/compat/common/ConnectionString";
|
||||
import { activateRemoteConfiguration } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/remoteConfig";
|
||||
|
||||
/**
|
||||
* Flag file handler interface, similar to target filter pattern.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import type { LogFunction } from "@lib/services/lib/logUtils";
|
||||
import { FlagFilesHumanReadable, FlagFilesOriginal } from "@lib/common/models/redflag.const";
|
||||
import { REMOTE_MINIO } from "@lib/common/models/setting.const";
|
||||
import { createServiceContext } from "@vrtmrz/livesync-commonlib/context";
|
||||
import type { LogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import { FlagFilesHumanReadable, FlagFilesOriginal } from "@vrtmrz/livesync-commonlib/compat/common/models/redflag.const";
|
||||
import { REMOTE_MINIO } from "@vrtmrz/livesync-commonlib/compat/common/models/setting.const";
|
||||
import {
|
||||
createFetchAllFlagHandler,
|
||||
createRebuildFlagHandler,
|
||||
@@ -18,12 +19,12 @@ import {
|
||||
TweakValuesRecommendedTemplate,
|
||||
TweakValuesShouldMatchedTemplate,
|
||||
TweakValuesTemplate,
|
||||
} from "@lib/common/types";
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import {
|
||||
ExtraOnLocal,
|
||||
FullScanModes,
|
||||
synchroniseAllFilesBetweenDBandStorage,
|
||||
} from "@lib/serviceFeatures/offlineScanner";
|
||||
} from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner";
|
||||
import {
|
||||
SIMPLE_FETCH_STAGE1_LEGACY,
|
||||
SIMPLE_FETCH_STAGE1_NEWER_WINS,
|
||||
@@ -36,9 +37,9 @@ import {
|
||||
askAndPerformFastSetupOnScheduledFetchAll,
|
||||
askSimpleFetchMode,
|
||||
} from "./redFlag.simpleFetch";
|
||||
import { activateRemoteConfiguration } from "@lib/serviceFeatures/remoteConfig";
|
||||
import { activateRemoteConfiguration } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/remoteConfig";
|
||||
//Mock synchroniseAllFilesBetweenDBandStorage
|
||||
vi.mock("@/lib/src/serviceFeatures/offlineScanner", async (importOriginal) => {
|
||||
vi.mock("@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner", async (importOriginal) => {
|
||||
const originalModule = (await importOriginal()) as any;
|
||||
return {
|
||||
...originalModule,
|
||||
@@ -46,7 +47,7 @@ vi.mock("@/lib/src/serviceFeatures/offlineScanner", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@lib/serviceFeatures/remoteConfig", () => {
|
||||
vi.mock("@vrtmrz/livesync-commonlib/compat/serviceFeatures/remoteConfig", () => {
|
||||
return {
|
||||
activateRemoteConfiguration: vi.fn((settings: any, configurationId: string) => {
|
||||
if (!settings?.remoteConfigurations?.[configurationId]) return false;
|
||||
@@ -159,6 +160,7 @@ const createHostMock = () => {
|
||||
|
||||
return {
|
||||
services: {
|
||||
context: createServiceContext(),
|
||||
setting: settingMock,
|
||||
appLifecycle: appLifecycleMock,
|
||||
UI: uiMock,
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import {
|
||||
encodeQR,
|
||||
encodeSettingsToQRCodeData,
|
||||
OutputFormat,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
|
||||
import { EVENT_REQUEST_SHOW_SETUP_QR } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { fireAndForget } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||
import type { SetupFeatureHost } from "./types";
|
||||
|
||||
export async function encodeSetupSettingsAsQR(host: SetupFeatureHost) {
|
||||
const settingString = encodeSettingsToQRCodeData(host.services.setting.currentSettings());
|
||||
const result = encodeQR(settingString, OutputFormat.SVG);
|
||||
if (result === "") {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (typeof result === "string") {
|
||||
const msg = host.services.context.translate("Setup.QRCode", { qr_image: result });
|
||||
await host.services.UI.confirm.confirmWithMessage("Settings QR Code", msg, ["OK"], "OK");
|
||||
return result;
|
||||
} else {
|
||||
// Multi-page QR code
|
||||
let currentIndex = 0;
|
||||
while (currentIndex < result.total) {
|
||||
const msg = `The setting is too large for a single QR code.
|
||||
We are using the aggregator to combine multiple QR codes.
|
||||
Your settings will not be sent to any server; they will be processed only on your device.
|
||||
Please scan this QR code with your mobile's camera, and open the page in your browser.
|
||||
After all parts are collected, the page will navigate you back to Obsidian with the aggregated settings.
|
||||
|
||||
Progress: ${currentIndex + 1} / ${result.total}
|
||||
${result.parts[currentIndex]}`;
|
||||
|
||||
const buttons = [];
|
||||
if (currentIndex > 0) buttons.push("Back");
|
||||
if (currentIndex < result.total - 1) {
|
||||
buttons.push("Next");
|
||||
buttons.push("Cancel");
|
||||
} else {
|
||||
buttons.push("Done");
|
||||
}
|
||||
|
||||
const choice = await host.services.UI.confirm.confirmWithMessage(
|
||||
"Settings QR Code (Aggregated)",
|
||||
msg,
|
||||
buttons,
|
||||
buttons[buttons.indexOf("Next") !== -1 ? buttons.indexOf("Next") : buttons.indexOf("Done")]
|
||||
);
|
||||
|
||||
if (choice === "Next") {
|
||||
currentIndex++;
|
||||
} else if (choice === "Back") {
|
||||
currentIndex--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result.parts[0]; // Return the first one for compatibility
|
||||
}
|
||||
}
|
||||
|
||||
export function useSetupQRCodeFeature(host: NecessaryServices<"API" | "UI" | "setting" | "appLifecycle", never>) {
|
||||
host.services.appLifecycle.onLoaded.addHandler(() => {
|
||||
host.services.API.addCommand({
|
||||
id: "livesync-setting-qr",
|
||||
name: "Show settings as a QR code",
|
||||
callback: () => fireAndForget(encodeSetupSettingsAsQR(host)),
|
||||
});
|
||||
host.services.context.events.onEvent(EVENT_REQUEST_SHOW_SETUP_QR, () =>
|
||||
fireAndForget(() => encodeSetupSettingsAsQR(host))
|
||||
);
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { describe, expect, it, vi, afterEach } from "vitest";
|
||||
import { EVENT_REQUEST_SHOW_SETUP_QR } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { createServiceContext } from "@vrtmrz/livesync-commonlib/compat/services/base/ServiceBase";
|
||||
import { encodeSetupSettingsAsQR, useSetupQRCodeFeature } from "./qrCode";
|
||||
import { encodeQR, encodeSettingsToQRCodeData } from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
|
||||
|
||||
vi.mock("@vrtmrz/livesync-commonlib/compat/API/processSetting", () => {
|
||||
return {
|
||||
encodeQR: vi.fn(),
|
||||
encodeSettingsToQRCodeData: vi.fn(),
|
||||
OutputFormat: {
|
||||
SVG: "svg",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe("setupObsidian/qrCode", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("encodeSetupSettingsAsQR should return empty string when QR generation fails", async () => {
|
||||
const confirmWithMessage = vi.fn();
|
||||
const host = {
|
||||
services: {
|
||||
context: createServiceContext(),
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => ({ any: "settings" })),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
confirmWithMessage,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
vi.mocked(encodeSettingsToQRCodeData).mockReturnValue("encoded-settings");
|
||||
vi.mocked(encodeQR).mockReturnValue("");
|
||||
|
||||
const result = await encodeSetupSettingsAsQR(host);
|
||||
|
||||
expect(result).toBe("");
|
||||
expect(confirmWithMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("encodeSetupSettingsAsQR should show confirm dialog when QR is generated", async () => {
|
||||
const confirmWithMessage = vi.fn(() => true);
|
||||
const translate = vi.fn(() => "qr-message");
|
||||
const host = {
|
||||
services: {
|
||||
context: createServiceContext({ translate }),
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => ({ any: "settings" })),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
confirmWithMessage,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
vi.mocked(encodeSettingsToQRCodeData).mockReturnValue("encoded-settings");
|
||||
vi.mocked(encodeQR).mockReturnValue("<svg/>");
|
||||
|
||||
const result = await encodeSetupSettingsAsQR(host);
|
||||
|
||||
expect(result).toBe("<svg/>");
|
||||
expect(translate).toHaveBeenCalledWith("Setup.QRCode", { qr_image: "<svg/>" });
|
||||
expect(confirmWithMessage).toHaveBeenCalledWith("Settings QR Code", "qr-message", ["OK"], "OK");
|
||||
});
|
||||
|
||||
it("useSetupQRCodeFeature should register onLoaded handler that wires command and event", async () => {
|
||||
const addHandler = vi.fn();
|
||||
const addCommand = vi.fn();
|
||||
const context = createServiceContext();
|
||||
const onEventSpy = vi.spyOn(context.events, "onEvent");
|
||||
|
||||
const host = {
|
||||
services: {
|
||||
context,
|
||||
API: {
|
||||
addCommand,
|
||||
},
|
||||
appLifecycle: {
|
||||
onLoaded: {
|
||||
addHandler,
|
||||
},
|
||||
},
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => ({ any: "settings" })),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
confirmWithMessage: vi.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
useSetupQRCodeFeature(host);
|
||||
expect(addHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
const loadedHandler = addHandler.mock.calls[0][0] as () => Promise<boolean>;
|
||||
await loadedHandler();
|
||||
|
||||
expect(addCommand).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: "livesync-setting-qr",
|
||||
name: "Show settings as a QR code",
|
||||
})
|
||||
);
|
||||
expect(onEventSpy).toHaveBeenCalledWith(EVENT_REQUEST_SHOW_SETUP_QR, expect.any(Function));
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,8 @@
|
||||
import { type SetupManager, UserMode } from "@/modules/features/SetupManager";
|
||||
import type { SetupFeatureHost } from "@lib/serviceFeatures/setupObsidian/types";
|
||||
import { EVENT_REQUEST_OPEN_P2P_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI } from "@lib/events/coreEvents";
|
||||
import { eventHub } from "@lib/hub/hub";
|
||||
import { fireAndForget } from "@lib/common/utils";
|
||||
import type { NecessaryServices } from "@lib/interfaces/ServiceModule";
|
||||
import type { SetupFeatureHost } from "@/serviceFeatures/setupObsidian/types";
|
||||
import { EVENT_REQUEST_OPEN_P2P_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { fireAndForget } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
|
||||
export async function openSetupURI(setupManager: SetupManager) {
|
||||
await setupManager.onUseSetupURI(UserMode.Unknown);
|
||||
@@ -24,8 +23,10 @@ export function useSetupManagerHandlersFeature(
|
||||
callback: () => fireAndForget(openSetupURI(setupManager)),
|
||||
});
|
||||
|
||||
eventHub.onEvent(EVENT_REQUEST_OPEN_SETUP_URI, () => fireAndForget(() => openSetupURI(setupManager)));
|
||||
eventHub.onEvent(EVENT_REQUEST_OPEN_P2P_SETTINGS, () =>
|
||||
host.services.context.events.onEvent(EVENT_REQUEST_OPEN_SETUP_URI, () =>
|
||||
fireAndForget(() => openSetupURI(setupManager))
|
||||
);
|
||||
host.services.context.events.onEvent(EVENT_REQUEST_OPEN_P2P_SETTINGS, () =>
|
||||
fireAndForget(() => openP2PSettings(host, setupManager))
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, it, vi, afterEach } from "vitest";
|
||||
import { eventHub } from "@lib/hub/hub";
|
||||
import { EVENT_REQUEST_OPEN_P2P_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI } from "@lib/events/coreEvents";
|
||||
import { EVENT_REQUEST_OPEN_P2P_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { openP2PSettings, openSetupURI, useSetupManagerHandlersFeature } from "./setupManagerHandlers";
|
||||
|
||||
vi.mock("@/modules/features/SetupManager", () => {
|
||||
@@ -47,10 +46,11 @@ describe("setupObsidian/setupManagerHandlers", () => {
|
||||
it("useSetupManagerHandlersFeature should register onLoaded handler that wires command and events", async () => {
|
||||
const addHandler = vi.fn();
|
||||
const addCommand = vi.fn();
|
||||
const onEventSpy = vi.spyOn(eventHub, "onEvent");
|
||||
const events = { onEvent: vi.fn() };
|
||||
|
||||
const host = {
|
||||
services: {
|
||||
context: { events },
|
||||
API: {
|
||||
addCommand,
|
||||
},
|
||||
@@ -81,7 +81,7 @@ describe("setupObsidian/setupManagerHandlers", () => {
|
||||
name: "Use the copied setup URI (Formerly Open setup URI)",
|
||||
})
|
||||
);
|
||||
expect(onEventSpy).toHaveBeenCalledWith(EVENT_REQUEST_OPEN_SETUP_URI, expect.any(Function));
|
||||
expect(onEventSpy).toHaveBeenCalledWith(EVENT_REQUEST_OPEN_P2P_SETTINGS, expect.any(Function));
|
||||
expect(events.onEvent).toHaveBeenCalledWith(EVENT_REQUEST_OPEN_SETUP_URI, expect.any(Function));
|
||||
expect(events.onEvent).toHaveBeenCalledWith(EVENT_REQUEST_OPEN_P2P_SETTINGS, expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "@lib/common/types";
|
||||
import type { LogFunction } from "@lib/services/lib/logUtils";
|
||||
import { createInstanceLogFunction } from "@lib/services/lib/logUtils";
|
||||
import type { SetupFeatureHost } from "@lib/serviceFeatures/setupObsidian/types";
|
||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { LogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import { createInstanceLogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import type { SetupFeatureHost } from "@/serviceFeatures/setupObsidian/types";
|
||||
import { configURIBase } from "@/common/types";
|
||||
import type { NecessaryServices } from "@lib/interfaces/ServiceModule";
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import { type SetupManager, UserMode } from "@/modules/features/SetupManager";
|
||||
|
||||
async function handleSetupProtocol(setupManager: SetupManager, conf: Record<string, string>) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { LOG_LEVEL_NOTICE, type ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { LogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import { createInstanceLogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import { encodeSettingsToSetupURI } from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
|
||||
import { EVENT_REQUEST_COPY_SETUP_URI } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { fireAndForget } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import type { SetupFeatureHost } from "./types";
|
||||
|
||||
export async function askEncryptingPassphrase(host: SetupFeatureHost): Promise<string | false> {
|
||||
return await host.services.UI.confirm.askString(
|
||||
"Encrypt your settings",
|
||||
"The passphrase to encrypt the setup URI",
|
||||
"",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
export async function copySetupURI(host: SetupFeatureHost, log: LogFunction, stripExtra = true) {
|
||||
const encryptingPassphrase = await askEncryptingPassphrase(host);
|
||||
if (encryptingPassphrase === false) return;
|
||||
const encryptedURI = await encodeSettingsToSetupURI(
|
||||
host.services.setting.currentSettings(),
|
||||
encryptingPassphrase,
|
||||
[...((stripExtra ? ["pluginSyncExtendedSetting"] : []) as (keyof ObsidianLiveSyncSettings)[])],
|
||||
true
|
||||
);
|
||||
if (await host.services.UI.promptCopyToClipboard("Setup URI", encryptedURI)) {
|
||||
log("Setup URI copied to clipboard", LOG_LEVEL_NOTICE);
|
||||
}
|
||||
}
|
||||
|
||||
export async function copySetupURIFull(host: SetupFeatureHost, log: LogFunction) {
|
||||
const encryptingPassphrase = await askEncryptingPassphrase(host);
|
||||
if (encryptingPassphrase === false) return;
|
||||
const encryptedURI = await encodeSettingsToSetupURI(
|
||||
host.services.setting.currentSettings(),
|
||||
encryptingPassphrase,
|
||||
[],
|
||||
false
|
||||
);
|
||||
if (await host.services.UI.promptCopyToClipboard("Setup URI", encryptedURI)) {
|
||||
log("Setup URI copied to clipboard", LOG_LEVEL_NOTICE);
|
||||
}
|
||||
}
|
||||
|
||||
export function useSetupURIFeature(host: NecessaryServices<"API" | "UI" | "setting" | "appLifecycle", never>) {
|
||||
const log = createInstanceLogFunction("SF:SetupURI", host.services.API);
|
||||
host.services.appLifecycle.onLoaded.addHandler(() => {
|
||||
host.services.API.addCommand({
|
||||
id: "livesync-copysetupuri",
|
||||
name: "Copy settings as a new setup URI",
|
||||
callback: () => fireAndForget(copySetupURI(host, log)),
|
||||
});
|
||||
|
||||
host.services.API.addCommand({
|
||||
id: "livesync-copysetupuri-short",
|
||||
name: "Copy settings as a new setup URI (With customization sync)",
|
||||
callback: () => fireAndForget(copySetupURI(host, log, false)),
|
||||
});
|
||||
|
||||
host.services.API.addCommand({
|
||||
id: "livesync-copysetupurifull",
|
||||
name: "Copy settings as a new setup URI (Full)",
|
||||
callback: () => fireAndForget(copySetupURIFull(host, log)),
|
||||
});
|
||||
|
||||
host.services.context.events.onEvent(EVENT_REQUEST_COPY_SETUP_URI, () =>
|
||||
fireAndForget(() => copySetupURI(host, log))
|
||||
);
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { describe, expect, it, vi, afterEach } from "vitest";
|
||||
import { EVENT_REQUEST_COPY_SETUP_URI } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { createServiceContext } from "@vrtmrz/livesync-commonlib/compat/services/base/ServiceBase";
|
||||
import { askEncryptingPassphrase, copySetupURI, copySetupURIFull, useSetupURIFeature } from "./setupUri";
|
||||
import { encodeSettingsToSetupURI } from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
|
||||
|
||||
vi.mock("@vrtmrz/livesync-commonlib/compat/API/processSetting", () => {
|
||||
return {
|
||||
encodeSettingsToSetupURI: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("setupObsidian/setupUri", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("askEncryptingPassphrase should delegate to confirm.askString", async () => {
|
||||
const askString = vi.fn(() => "secret");
|
||||
const host = {
|
||||
services: {
|
||||
UI: {
|
||||
confirm: {
|
||||
askString,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
const result = await askEncryptingPassphrase(host);
|
||||
expect(result).toBe("secret");
|
||||
expect(askString).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("copySetupURI should return early when user cancels passphrase", async () => {
|
||||
const promptCopyToClipboard = vi.fn();
|
||||
const host = {
|
||||
services: {
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => ({ foo: "bar" })),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
askString: vi.fn(() => false),
|
||||
},
|
||||
promptCopyToClipboard,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
const log = vi.fn();
|
||||
|
||||
await copySetupURI(host, log);
|
||||
|
||||
expect(encodeSettingsToSetupURI).not.toHaveBeenCalled();
|
||||
expect(promptCopyToClipboard).not.toHaveBeenCalled();
|
||||
expect(log).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("copySetupURI should encode with short mode by default", async () => {
|
||||
const promptCopyToClipboard = vi.fn(() => true);
|
||||
const currentSettings = { pluginSyncExtendedSetting: true, x: 1 };
|
||||
const host = {
|
||||
services: {
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => currentSettings),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
askString: vi.fn(() => "pass"),
|
||||
},
|
||||
promptCopyToClipboard,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
const log = vi.fn();
|
||||
vi.mocked(encodeSettingsToSetupURI).mockResolvedValue("uri://value" as any);
|
||||
|
||||
await copySetupURI(host, log);
|
||||
|
||||
expect(encodeSettingsToSetupURI).toHaveBeenCalledWith(
|
||||
currentSettings,
|
||||
"pass",
|
||||
["pluginSyncExtendedSetting"],
|
||||
true
|
||||
);
|
||||
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "uri://value");
|
||||
expect(log).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("copySetupURIFull should encode with full mode", async () => {
|
||||
const promptCopyToClipboard = vi.fn(() => true);
|
||||
const currentSettings = { pluginSyncExtendedSetting: true, x: 1 };
|
||||
const host = {
|
||||
services: {
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => currentSettings),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
askString: vi.fn(() => "pass-full"),
|
||||
},
|
||||
promptCopyToClipboard,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
const log = vi.fn();
|
||||
vi.mocked(encodeSettingsToSetupURI).mockResolvedValue("uri://full" as any);
|
||||
|
||||
await copySetupURIFull(host, log);
|
||||
|
||||
expect(encodeSettingsToSetupURI).toHaveBeenCalledWith(currentSettings, "pass-full", [], false);
|
||||
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "uri://full");
|
||||
expect(log).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("useSetupURIFeature should register onLoaded handler that wires commands and event", async () => {
|
||||
const addHandler = vi.fn();
|
||||
const addCommand = vi.fn();
|
||||
const context = createServiceContext();
|
||||
const onEventSpy = vi.spyOn(context.events, "onEvent");
|
||||
|
||||
const host = {
|
||||
services: {
|
||||
context,
|
||||
API: {
|
||||
addCommand,
|
||||
addLog: vi.fn(),
|
||||
},
|
||||
appLifecycle: {
|
||||
onLoaded: {
|
||||
addHandler,
|
||||
},
|
||||
},
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => ({ x: 1 })),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
askString: vi.fn(() => "pass"),
|
||||
},
|
||||
promptCopyToClipboard: vi.fn(() => true),
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
useSetupURIFeature(host);
|
||||
expect(addHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
const loadedHandler = addHandler.mock.calls[0][0] as () => Promise<boolean>;
|
||||
await loadedHandler();
|
||||
|
||||
expect(addCommand).toHaveBeenCalledTimes(3);
|
||||
expect(addCommand).toHaveBeenCalledWith(expect.objectContaining({ id: "livesync-copysetupuri" }));
|
||||
expect(addCommand).toHaveBeenCalledWith(expect.objectContaining({ id: "livesync-copysetupuri-short" }));
|
||||
expect(addCommand).toHaveBeenCalledWith(expect.objectContaining({ id: "livesync-copysetupurifull" }));
|
||||
expect(onEventSpy).toHaveBeenCalledWith(EVENT_REQUEST_COPY_SETUP_URI, expect.any(Function));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
|
||||
export type SetupFeatureHost = NecessaryServices<"API" | "UI" | "setting", never>;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { eventHub, EVENT_REQUEST_OPEN_P2P } from "@/common/events";
|
||||
import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
|
||||
import type { NecessaryServices } from "@lib/interfaces/ServiceModule";
|
||||
import { type UseP2PReplicatorResult } from "@lib/replication/trystero/UseP2PReplicatorResult";
|
||||
import { P2PLogCollector } from "@lib/replication/trystero/P2PLogCollector";
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import { type UseP2PReplicatorResult } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/UseP2PReplicatorResult";
|
||||
import { P2PLogCollector } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PLogCollector";
|
||||
import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "@/features/P2PSync/P2PReplicator/P2PReplicatorPaneView";
|
||||
import {
|
||||
P2PServerStatusPaneView,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "@/features/P2PSync/P2PReplicator/P2PServerStatusPaneView";
|
||||
import type { LiveSyncCore } from "@/main";
|
||||
import type { WorkspaceLeaf } from "@/deps";
|
||||
import { REMOTE_P2P } from "@lib/common/models/setting.const";
|
||||
import { REMOTE_P2P } from "@vrtmrz/livesync-commonlib/compat/common/models/setting.const";
|
||||
|
||||
/**
|
||||
* ServiceFeature: P2P Replicator lifecycle management.
|
||||
@@ -54,7 +54,7 @@ export function useP2PReplicatorUI(
|
||||
|
||||
// const env: LiveSyncTrysteroReplicatorEnv = { services: host.services as any };
|
||||
const getReplicator = () => replicator.replicator;
|
||||
const p2pLogCollector = new P2PLogCollector();
|
||||
const p2pLogCollector = new P2PLogCollector(host.services.context.events);
|
||||
const storeP2PStatusLine = reactiveSource("");
|
||||
p2pLogCollector.p2pReplicationLine.onChanged((line) => {
|
||||
storeP2PStatusLine.value = line.value;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createServiceContext } from "@vrtmrz/livesync-commonlib/context";
|
||||
|
||||
vi.mock("@/features/P2PSync/P2PReplicator/P2PReplicatorPaneView", () => ({
|
||||
P2PReplicatorPaneView: class {},
|
||||
@@ -19,6 +20,7 @@ describe("useP2PReplicatorUI commands", () => {
|
||||
const runFiniteReplicationActivity = vi.fn(async (task: () => unknown) => await task());
|
||||
const host = {
|
||||
services: {
|
||||
context: createServiceContext(),
|
||||
API: {
|
||||
showWindow: vi.fn(async () => undefined),
|
||||
registerWindow: vi.fn(),
|
||||
|
||||
Reference in New Issue
Block a user