Compare commits

...
Author SHA1 Message Date
vorotamoroz 22ae1b4a6e Credit P2P transport compatibility diagnosis 2026-08-21 11:17:36 +00:00
vorotamoroz d1ae42a134 Document P2P transport compatibility and Coturn setup 2026-08-21 11:05:40 +00:00
vorotamoroz c7443ee728 Merge pull request #1123 from nimula/fix/settings-manifest-translator
Wire the display-language translator into the settings manifest
2026-08-21 17:25:13 +09:00
nimulaandClaude Opus 5 f8ee3c8662 i18n: wire the display-language translator into the settings manifest
Commonlib's `getConfig(key, translate?)` and `getConfName(key, translate?)`
default `translate` to `englishMessageTranslator`, and this plug-in never
passed the second argument. Every automatically wired setting therefore
rendered its name and description in English, whatever `displayLanguage` was
set to. Commonlib's own Config Doctor already threads a translator through
`getConfName`, so this only restores the argument which was missing here.

`src/modules/features/SettingDialogue/settingConstants.ts` now re-exports the
names it supplies explicitly and adds thin `getConfig` and `getConfName`
wrappers which default the translator to `translateLiveSyncMessage`. That
reaches all three existing call sites, and therefore the 102 `setAuto` and
`autoWire*` calls across the setting panes, the setup-wizard configuration
summaries, and the externally-modified-setting prompt. Of the 225 distinct
name and description strings in the two manifest tables, 160 are already
catalogue keys with translations; the remaining 65 are not catalogue keys and
pass through unchanged.

`ModuleResolveMismatchedTweaks` used `confName()`, which accepts no
translator, so it gains a local `localisedConfName()` instead. Swapping in
`getConfName()` there would have silently dropped the `statusDisplay()`
suffix, replaced the empty-string fallback for an unknown key with
`${key} (No info)`, and introduced `SettingInformation` as a second source.

English output is unchanged: every catalogue key which contains a space has a
value identical to the key itself, so translating under the default language
is idempotent.

Verified with `npm run check`, `npm run test:unit`, and `npm run build`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 02:17:20 +00:00
vorotamoroz fbe868092a Merge pull request #1122 from vrtmrz/1_0_16
Releasing 1.0.16
2026-08-19 19:25:44 +09:00
12 changed files with 441 additions and 6 deletions
+2
View File
@@ -3,6 +3,8 @@
A fully self-hosted CouchDB stack for the [obsidian-livesync](https://github.com/vrtmrz/obsidian-livesync) plugin.
**No fly.io. No IBM Cloudant. No cloud accounts required for basic use.**
The optional [Coturn Compose starter](coturn/README.md) is a separate Linux-only service for P2P connectivity. It is not part of the CouchDB stack below.
> ✅ **Tested on Docker Desktop for Windows (Docker 29.2, Compose v5, WSL2 backend)** — full init, CORS, auth, and idempotent restart verified.
---
+11
View File
@@ -0,0 +1,11 @@
# Public DNS name used as the TURN authentication realm.
TURN_REALM=
# Public IPv4 address advertised by Coturn. If Coturn is behind NAT, forward
# port 3478 (TCP and UDP) and the UDP relay range to this host.
TURN_EXTERNAL_IP=
# Static long-term credential used by the LiveSync P2P profile. Use a simple
# username without a colon and a high-entropy password, such as hexadecimal.
TURN_USERNAME=
TURN_PASSWORD=
+1
View File
@@ -0,0 +1 @@
/.env
+81
View File
@@ -0,0 +1,81 @@
# Coturn starter for LiveSync P2P
This optional Compose project runs a small, static-credential TURN service for LiveSync P2P. It uses the upstream `coturn/coturn` image directly; the repository does not maintain a separate Coturn Dockerfile.
The starter is deliberately limited to a Linux server with a public IPv4 address, TURN over UDP and TCP on port 3478, and UDP relay ports 4916049200. It does not configure TLS, automatic certificate renewal, monitoring, quotas, or a managed credential endpoint.
## Before starting
Prepare:
- a Linux host with Docker Engine and the Compose plug-in;
- a public IPv4 address, either on the host or forwarded to it;
- a DNS name such as `turn.example.com`;
- firewall and NAT rules for TCP and UDP port 3478, and UDP ports 4916049200; and
- enough bandwidth for every relayed P2P transfer.
Docker host networking is intentional. Coturn's upstream image recommends it because forwarding a large relay port range through Docker performs poorly. This starter therefore does not support Docker Desktop.
## Configure and start
From this directory:
```sh
cp .env.example .env
chmod 600 .env
```
Set every value in `.env`. Generate a high-entropy password, for example:
```sh
openssl rand -hex 32
```
Use a simple username without a colon. The static username and password are passed to Coturn as process arguments. They are visible to a local Docker administrator, who already controls the host. The `.env` file is excluded from Git and should remain private. The resolved output of `docker compose config` also contains the credential, so do not publish it.
Validate the resolved configuration, then start it:
```sh
docker compose config
docker compose up -d
docker compose logs -f coturn
```
The pinned image version is deliberate. Review the upstream Coturn release notes and update the pin explicitly rather than following `latest` automatically.
## Configure LiveSync
Enter both client paths in the P2P profile's TURN server list:
```text
turn:turn.example.com:3478?transport=udp,turn:turn.example.com:3478?transport=tcp
```
Use `TURN_USERNAME` and `TURN_PASSWORD` as the TURN username and credential. Keep the normal `Automatic` ICE policy unless a future LiveSync release offers `TURN relay only` and the direct path needs to be excluded deliberately.
Both synchronising devices must be able to reach the server. Prove an explicit two-way `Replicate now` round trip on the intended networks before relying on the configuration.
The repository check can validate Compose expansion and local TURN allocations. It cannot prove the public firewall, NAT, carrier, or client path for a particular deployment. Validate both UDP and TCP from outside the server network.
## TLS and port 443 are advanced extensions
This starter does not recommend putting Coturn on port 443. Coturn cannot bind to the same IP address and TCP port as Caddy or another HTTPS entry point. In particular, it conflicts with the bundled CouchDB Caddy profile when both use the same host address.
If a restrictive network requires TURN over TLS on port 443, prefer a separate TURN host or a separate public IP address. An outbound tunnel used for CouchDB may also leave the host's public port 443 available for Coturn, provided that TURN uses a separate DNS record which resolves directly to that host. The tunnel itself does not carry TURN traffic.
A single public IP can technically be shared when one layer-4 TLS router owns port 443 and routes separate CouchDB and TURN hostnames by Server Name Indication (SNI). This adds another certificate and connection-routing boundary, depends on every intended TURN client supplying usable SNI, and is outside this starter. The standard Caddy image used by the bundled CouchDB profile does not provide that layer-4 routing.
TURN over TLS is not HTTP. An ordinary HTTP reverse proxy or Cloudflare Tunnel route is not a substitute for a TURN listener. Follow Coturn's upstream configuration guidance for `tls-listening-port`, `cert`, and `pkey`, arrange renewal and restart behaviour, and test the resulting `turns:` URL from outside the server network.
This starter disables Coturn's TLS listener and does not add a TURN-over-DTLS path, so it cannot appear to provide a secure TURN port without those operator-owned prerequisites. This does not disable the end-to-end DTLS encryption used by the WebRTC peer connection carried through TURN.
## Security and operations
- Rotate the static credential if the Setup URI, `.env` file, or credential is exposed.
- Treat TURN as an internet-facing bandwidth service and monitor traffic and logs.
- Add appropriate allocation and bandwidth quotas for a shared or public deployment.
- Keep the private-address restrictions unless the TURN server is intentionally permitted to relay to those networks.
- Keep independent Vault backups. TURN improves connection reachability; it does not store a backup of Vault data.
- A TURN operator can observe endpoint addresses, timing, and traffic volume even though LiveSync content remains end-to-end encrypted.
The authoritative image and configuration references are the [Coturn Docker image guide](https://github.com/coturn/coturn/blob/master/docker/coturn/README.md) and [Coturn server documentation](https://github.com/coturn/coturn/blob/master/README.turnserver).
+33
View File
@@ -0,0 +1,33 @@
name: livesync-coturn
services:
coturn:
image: coturn/coturn:4.17.2
restart: unless-stopped
network_mode: host
# Compose has already interpolated the environment values. Invoke Coturn
# directly so that the image's shell entrypoint does not expand them again.
entrypoint:
- turnserver
command:
- "-n"
- "--log-file=stdout"
- "--pidfile=/tmp/turnserver.pid"
- "--listening-ip=0.0.0.0"
- "--listening-port=3478"
- "--min-port=49160"
- "--max-port=49200"
- "--external-ip=${TURN_EXTERNAL_IP:?Set TURN_EXTERNAL_IP in docker/coturn/.env}"
- "--realm=${TURN_REALM:?Set TURN_REALM in docker/coturn/.env}"
- "--user=${TURN_USERNAME:?Set TURN_USERNAME in docker/coturn/.env}:${TURN_PASSWORD:?Set TURN_PASSWORD in docker/coturn/.env}"
- "--fingerprint"
- "--lt-cred-mech"
- "--stale-nonce=600"
- "--unauthorized-ratelimit"
- "--no-multicast-peers"
- "--denied-peer-ip=10.0.0.0-10.255.255.255"
- "--denied-peer-ip=100.64.0.0-100.127.255.255"
- "--denied-peer-ip=169.254.0.0-169.254.255.255"
- "--denied-peer-ip=172.16.0.0-172.31.255.255"
- "--denied-peer-ip=192.168.0.0-192.168.255.255"
- "--no-tls"
@@ -0,0 +1,176 @@
# Architectural Decision Record: P2P Transport Compatibility Controls
## Status
Accepted — the user-facing controls will be introduced in stages. This record defines their boundaries before Commonlib settings and LiveSync interfaces are changed.
## Context
WebRTC connectivity depends on both devices, their browsers or embedded WebViews, NAT behaviour, carrier networks, VPNs, firewalls, and the path between them. A configuration which works on desktop Wi-Fi may fail on a mobile carrier, and moving the same devices through a mesh VPN may change the result without changing LiveSync.
The current P2P transport has several relevant properties:
- Trystero supplies the Nostr signalling strategy and the browser-owned WebRTC connection.
- Commonlib limits one RPC wire payload to 15,360 bytes so it remains below Trystero's own action-chunk boundary.
- Trystero supplies ordinary STUN servers and accepts an optional TURN server list with one username and credential.
- ICE chooses a direct, server-reflexive, or TURN-relayed path automatically.
- Commonlib can collect raw WebRTC statistics, but LiveSync does not yet present the selected candidate route in a concise diagnostic result.
Issue reports suggest that reducing the application payload may improve some mobile and constrained-network paths. A VPN such as Tailscale may also turn an unreliable route into a reliable one. These observations are consistent with NAT, path-MTU, fragmentation, or intermediary behaviour, but they do not prove one universal cause. Browser WebRTC implementations retain responsibility for SCTP, DTLS, ICE, packetisation, congestion control, and retransmission.
One low-level number cannot represent all of these concerns. Users need a small set of meaningful compatibility choices, while transport-internal controls which cannot be selected safely should remain implementation details.
## Decision
### Message-size presets
LiveSync will expose a `P2P message size` choice with four presets:
| Label | Maximum RPC wire payload | Intended use |
| ----------------------- | -----------------------: | ------------------------------------------------------------------------------- |
| `Standard` | 15,360 bytes | Existing default and best throughput. |
| `Reduced` | 2,048 bytes | First compatibility step for an unreliable path. |
| `Conservative` | 1,024 bytes | Stronger compatibility at greater framing and processing cost. |
| `Maximum compatibility` | 800 bytes | Most conservative offered value for paths suspected of dropping larger packets. |
This value limits Commonlib RPC wire payloads before Trystero applies its own framing. It is not a LiveSync file Chunk size, an IP MTU, an SCTP fragment size, or a guarantee that lower layers will avoid fragmentation. The smaller presets reduce the amount presented to the transport at once and trade throughput for compatibility.
The bound applies to outgoing messages. A device which only lowers its own value still receives messages produced under the sender's value. The selected preset therefore belongs to the P2P profile and is included in an encrypted Setup URI for additional devices. A device which was configured earlier must be changed separately; the interface and troubleshooting guidance must state that the same conservative preset should be selected on every participating device. An absent key preserves the current 15,360-byte default.
Automatic negotiation or fallback between presets is deferred. A failed ordered data channel may require connection replacement before a smaller retry can prove anything, and changing transport parameters during a replication session would broaden the lifecycle contract considerably. The first implementation remains explicit, stable for one room lifetime, and inspectable.
### Connection path
LiveSync will expose a separate `Connection path` choice:
- `Automatic` retains normal ICE selection and is the default.
- `TURN relay only` supplies `iceTransportPolicy: 'relay'` and prevents direct or server-reflexive candidates from being selected.
`TURN relay only` is enabled only when at least one syntactically valid `turn:` or `turns:` URL is configured. If the last valid TURN URL is removed while relay-only mode is selected, saving the settings restores `Automatic` and displays a concise explanation.
The route policy is stored per P2P profile on the current device and is omitted from Setup URIs. It is a diagnostic and compatibility choice for the current device and network; forcing every synchronising device through TURN merely because one mobile path needs it would add avoidable latency, bandwidth cost, and metadata exposure.
No `Direct only` choice will be added. `Automatic` already prefers viable non-relayed candidates, and preventing TURN fallback would mainly create another failure mode.
### TURN server presentation
The first settings revision retains the existing storage contract of one credential shared by a list of TURN URLs. The dialogue will present it as a profile rather than as one comma-separated text field:
- an ordered list of `turn:` and `turns:` URL rows;
- one username;
- one credential; and
- the connection-path choice below the profile.
The interface may parse and serialise the existing comma-separated value so older profiles and Setup URIs remain compatible. A structured list of multiple credential profiles is deferred until a provider or self-hosted use case requires different credentials in the same P2P profile.
Static long-term credentials are the supported first stage. Managed providers may return short-lived credentials, but LiveSync must not store a provider API token or a Coturn shared authentication secret. A future managed-credential design needs a separately trusted HTTPS endpoint, expiry handling, refresh behaviour, failure reporting, and a clear Setup URI policy. It is not represented as another static password field.
### TURN allocation check and route diagnostics
A future `Test TURN server` action should create a disposable WebRTC check with `iceTransportPolicy: 'relay'`, request candidate gathering, and require at least one relay candidate. It must not read a Vault, join a LiveSync P2P room, or claim that document synchronisation has succeeded.
Where the browser exposes the evidence, the result should report:
- whether a relay candidate was gathered;
- the TURN URL used for that candidate;
- UDP, TCP, or TLS transport; and
- a bounded failure or inconclusive result.
Ordinary P2P diagnostics should later summarise the selected candidate pair as direct, server-reflexive, or relayed, with its transport. Raw `getStats()` output remains supporting evidence rather than the primary interface.
### Placement and defaults
These controls belong inside `P2P Configuration` under a `Connection compatibility` section. They do not require the repository-wide Advanced, Power User, or Edge Case modes. P2P itself remains a supported opt-in feature.
Existing profiles retain the following defaults:
- `P2P message size`: `Standard`;
- `Connection path`: `Automatic`; and
- TURN credentials and URLs: unchanged.
Settings which replace a room continue to use the established P2P room and transport lifecycle. No new reconnect interval, handshake timeout, keepalive interval, trickle-ICE, candidate-pool, data-channel reliability, or backpressure setting is exposed.
## Self-hosted TURN example
The repository supplies an optional Coturn Compose example under `docker/coturn/`. It uses a versioned upstream `coturn/coturn` image rather than maintaining another LiveSync Dockerfile.
The example deliberately covers one small static-credential deployment:
- Linux host networking, which avoids Docker's large port-range forwarding cost;
- TURN over UDP and TCP on port 3478;
- a bounded UDP relay port range;
- explicit long-term credentials;
- an explicit public IPv4 address;
- no TLS or DTLS in the starter configuration; and
- restrictions which prevent relaying to common private IPv4 ranges.
The starter does not recommend `turns:` on port 443. It conflicts with an HTTPS entry point which already owns the same IP address and TCP port, including the bundled CouchDB Caddy profile. When a restrictive network requires this path, the preferred deployment uses a separate TURN host or public IP address.
An outbound tunnel used for CouchDB may leave the host's public port 443 available when TURN uses a separate DNS record which resolves directly to that host, but the tunnel itself cannot carry TURN traffic. A layer-4 TLS router can also own the shared port and select separate CouchDB and TURN backends by SNI. That alternative adds certificate and routing responsibilities, depends on the intended TURN clients supplying usable SNI, and is outside the supplied Compose example. The standard Caddy image used by the CouchDB profile does not provide that layer-4 routing.
TURN over TLS is not HTTP and must reach Coturn directly or through a compatible layer-4 proxy. Supporting it also adds private-key, renewal, privileged-port, and real-network verification responsibilities.
The Compose example is not a hosted service supplied by the project, an availability guarantee, or a substitute for firewall and abuse controls. Operators remain responsible for DNS, certificates when enabled, port forwarding, bandwidth, quotas, monitoring, software updates, credential rotation, and legal or provider constraints.
## Security and privacy
TURN relays the already encrypted WebRTC connection. A TURN operator cannot read LiveSync's end-to-end encrypted Vault contents, but can observe endpoint addresses, timing, traffic volume, and service credentials.
Static credentials allow use of the operator's bandwidth until they are changed. They should be unique, high entropy, and limited to the intended deployment. Setup URIs are encrypted but still contain the P2P connection profile; they and their separate passphrases must be protected.
The Coturn Docker example uses environment interpolation for its static credential. A local Docker administrator can inspect the resulting container arguments and already has equivalent control of that host. The `.env` file remains untracked and should be readable only by the operator.
## Alternatives rejected
### Expose a free-form byte field
Most users cannot infer a safe application payload from a network MTU, and an arbitrary value makes reports difficult to compare. Four named presets provide a bounded troubleshooting ladder.
### Apply the smaller payload only on the affected mobile device
The bound controls outgoing messages. This would leave larger messages from another sender unchanged and could fail during the direction which matters most for an initial fetch.
### Force TURN whenever a TURN server is configured
TURN is normally a fallback. Forcing it by default adds latency and bandwidth cost, and exposes more connection metadata even when a direct path works.
### Automatically decrease the payload after a transfer failure
A transfer failure does not identify message size as the cause. Reusing a possibly wedged ordered channel would also make the retry inconclusive, while rebuilding the connection expands the lifecycle and user-notification design.
### Add browser-specific defaults
Safari, mobile Safari, Chrome, and Chrome on Android use different platform WebRTC implementations and lifecycle policies, but the failing route also depends on both networks and the remote peer. There is not enough stable evidence for a browser-name heuristic. Explicit cross-platform presets are more predictable.
### Build and maintain a LiveSync Coturn image
The upstream project already publishes a multi-platform image and documents its configuration contract. A local Dockerfile would duplicate security updates and release work without adding a LiveSync-specific server component.
### Bundle a shared port-443 router
A layer-4 TLS router could share one public address between distinct CouchDB and TURN hostnames by inspecting SNI. Bundling that topology would replace the current Caddy ownership of port 443, add another certificate and routing lifecycle, and rely on the intended TURN clients presenting usable SNI. A separate TURN host or public IP address keeps those failure and ownership boundaries explicit.
## Verification
The implementation stage must add focused tests before production changes:
- settings-schema defaults for absent keys;
- Setup URI round trips which retain the message-size preset but omit the device-local connection path;
- compatibility parsing and serialisation of the existing TURN URL string;
- mapping each message-size preset to the exact Commonlib wire bound;
- mapping relay-only mode to `iceTransportPolicy: 'relay'`;
- rejection or automatic reset of relay-only mode without a valid TURN URL;
- room replacement after either effective transport setting changes;
- a disposable TURN allocation check using injected WebRTC boundaries; and
- a real transport test only for the device- or network-owned behaviour which deterministic injection cannot prove.
The Coturn example is checked independently with `docker compose config`. Runtime verification uses a real Coturn allocation from outside the server network and confirms both UDP and TCP client paths before it is presented as a known-working deployment.
## Consequences
- Users gain a small compatibility ladder without learning WebRTC internals.
- A conservative message size affects throughput wherever it is selected or imported, and must be applied to every participating device to protect all transfer directions.
- TURN can be forced for diagnosis or hostile networks without making relay use the global default.
- Static and managed TURN credentials have separate, explicit responsibility boundaries.
- Browser-specific heuristics, automatic payload fallback, and low-level transport knobs remain out of scope.
- A reproducible self-hosted starter is available without making LiveSync responsible for a separate TURN image.
+2
View File
@@ -39,6 +39,8 @@ Try these in order:
TURN is a fallback for encrypted WebRTC traffic. It is different from the required signalling relay. The project does not operate an official TURN service. A TURN provider cannot read encrypted Vault contents, but it can observe connection metadata and traffic volume.
For a small self-hosted deployment, the repository includes an optional [Coturn Compose starter](../../docker/coturn/README.md). It uses static credentials and does not include TLS or a managed credential service; review its network and security boundaries before exposing it.
## A connected peer does not receive later edits
An open signalling connection does not automatically move every change.
@@ -4,7 +4,8 @@ import {
TweakValuesShouldMatchedTemplate,
TweakValuesTemplate,
IncompatibleChanges,
confName,
configurationNames,
statusDisplay,
type TweakValues,
type ObsidianLiveSyncSettings,
type RemoteDBSettings,
@@ -15,11 +16,21 @@ import {
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import { escapeMarkdownValue } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import { AbstractModule } from "@/modules/AbstractModule.ts";
import { $msg } from "@/common/translation";
import { $msg, translateIfAvailable } from "@/common/translation";
import type { InjectableServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableServiceHub";
import type { LiveSyncCore } from "@/main.ts";
import { REMOTE_P2P } from "@vrtmrz/livesync-commonlib/compat/common/models/setting.const";
/**
* Localised counterpart of Commonlib's `confName()`, which takes no translator.
* Same shape: label plus status suffix, and an empty string for an unknown key.
*/
function localisedConfName(key: keyof ObsidianLiveSyncSettings): string {
const info = configurationNames[key];
if (!info) return "";
return `${translateIfAvailable(info.name)}${statusDisplay(info.status)}`;
}
function valueToString(value: string | number | boolean | object | undefined): string {
if (typeof value === "boolean") {
return value ? "true" : "false";
@@ -158,7 +169,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
// table += `| ${confName(key)} | ${valueMine} | ${valuePreferred} | \n`;
tableRows.push(
$msg("TweakMismatchResolve.Table.Row", {
name: confName(key),
name: localisedConfName(key),
self: valueToString(valueMine),
remote: valueToString(valuePreferred),
})
@@ -342,7 +353,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
}
tableRows.push(
$msg("TweakMismatchResolve.Table.Row", {
name: confName(key),
name: localisedConfName(key),
self: currentValueForDisplay,
remote: remoteValueForDisplay,
})
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
DEFAULT_SETTINGS,
REMOTE_COUCHDB,
@@ -6,6 +6,7 @@ import {
type TweakValues,
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import { ModuleResolvingMismatchedTweaks } from "./ModuleResolveMismatchedTweaks";
import { setLang } from "@/common/translation";
function createModule(settingsOverride: Partial<typeof DEFAULT_SETTINGS> = {}) {
const askSelectStringDialogue = vi.fn(async (..._args: unknown[]): Promise<string | undefined> => undefined);
@@ -255,3 +256,43 @@ describe("ModuleResolvingMismatchedTweaks", () => {
expect(calls).toEqual(["save", "reinitialise", "set-preferred"]);
});
});
describe("ModuleResolvingMismatchedTweaks setting labels", () => {
afterEach(() => setLang("def"));
async function renderMismatchTable() {
const { module, askSelectStringDialogue } = createModule({
autoAcceptCompatibleTweak: true,
hashAlg: "xxhash64",
encrypt: false,
tweakModified: 100,
});
const preferred = {
...(DEFAULT_SETTINGS as unknown as TweakValues),
hashAlg: "xxhash32",
encrypt: true,
tweakModified: 200,
} as Partial<TweakValues>;
await module._checkAndAskResolvingMismatchedTweaks(preferred);
return String(askSelectStringDialogue.mock.calls[0]?.[0] ?? "");
}
it("localises the setting names and keeps the status suffix", async () => {
setLang("zh-tw");
const message = await renderMismatchTable();
expect(message).toContain("chunk ID 的雜湊演算法 (Experimental)");
expect(message).toContain("端對端加密");
expect(message).not.toContain("The Hash algorithm for chunk IDs");
});
it("leaves English unchanged", async () => {
const message = await renderMismatchTable();
expect(message).toContain("The Hash algorithm for chunk IDs (Experimental)");
expect(message).toContain("End-to-End Encryption");
});
});
@@ -1 +1,37 @@
export * from "@vrtmrz/livesync-commonlib/compat/common/settingConstants";
export {
AllSettingDefault,
OnDialogSettingsDefault,
SettingInformation,
} from "@vrtmrz/livesync-commonlib/compat/common/settingConstants";
export type {
AllSettings,
AllSettingItemKey,
AllStringItemKey,
AllNumericItemKey,
AllBooleanItemKey,
OnDialogSettings,
ValueOf,
} from "@vrtmrz/livesync-commonlib/compat/common/settingConstants";
import {
getConfig as getCommonlibConfig,
getConfName as getCommonlibConfName,
type AllSettingItemKey,
} from "@vrtmrz/livesync-commonlib/compat/common/settingConstants";
import type { MessageTranslator } from "@vrtmrz/livesync-commonlib/context";
import { translateLiveSyncMessage } from "@/common/translation";
// Commonlib defaults `translate` to its English-only translator, so every caller which omits
// it silently renders English regardless of `displayLanguage`. Default it to the LiveSync
// catalogue instead, and re-export these wrappers under the original names so that no call
// site has to remember the second argument.
/** `getConfig` with the LiveSync catalogue applied by default. */
export function getConfig(key: AllSettingItemKey, translate: MessageTranslator = translateLiveSyncMessage) {
return getCommonlibConfig(key, translate);
}
/** `getConfName` with the LiveSync catalogue applied by default. See `getConfig`. */
export function getConfName(key: AllSettingItemKey, translate: MessageTranslator = translateLiveSyncMessage) {
return getCommonlibConfName(key, translate);
}
@@ -0,0 +1,33 @@
import { afterEach, describe, expect, it } from "vitest";
import { setLang } from "@/common/translation";
import { getConfig, getConfName } from "./settingConstants";
describe("setting manifest labels", () => {
afterEach(() => setLang("def"));
it("renders names and descriptions in the selected display language", () => {
setLang("zh-tw");
expect(getConfName("liveSync")).toBe("同步模式");
expect(getConfig("couchDB_URI")).toMatchObject({ name: "伺服器 URI" });
expect(getConfig("encrypt")).toMatchObject({
name: "端對端加密",
desc: "加密遠端資料庫中的內容。如果你使用外掛的同步功能,建議啟用此選項。",
});
});
it("leaves English untouched, so that the catalogue key and its English value stay interchangeable", () => {
expect(getConfName("liveSync")).toBe("Sync Mode");
expect(getConfig("encrypt")).toMatchObject({
name: "End-to-End Encryption",
desc: "Encrypt contents on the remote database. If you use the plugin's synchronization feature, enabling this is recommended.",
});
});
it("passes through labels which Commonlib owns but the catalogue does not carry", () => {
setLang("zh-tw");
expect(getConfName("chunkSplitterVersion")).toBe("Chunk Splitter");
});
});
+8
View File
@@ -12,6 +12,14 @@ Earlier releases remain available in the 1.0 release history, the 1.0 preview hi
## Unreleased
### Peer-to-peer synchronisation
#### Improved
- An optional self-hosted Coturn Compose starter is now available for P2P deployments that need a TURN relay. It uses a pinned upstream image and documents its network, credential, security, and verification boundaries.
- The message-size and connection-path controls remain a separate implementation.
- Thank you to @andrewschreiber for the detailed fragmentation diagnosis and working 800-byte threshold in vrtmrz/livesync-commonlib#97, which informed this compatibility design.
## 1.0.16
19th August, 2026