refactor: consume Commonlib as a package

This commit is contained in:
vorotamoroz
2026-07-17 11:13:16 +00:00
parent 6475d32769
commit a721d3b602
665 changed files with 3438 additions and 31592 deletions
@@ -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 {
+5 -5
View File
@@ -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,
+8 -8
View File
@@ -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.
+10 -8
View File
@@ -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>;
+5 -5
View File
@@ -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(),