feat(settings): add Sync Servers toggle (off by default)
CI / Build & Test (push) Successful in 4m29s
CI / Build & Test (push) Successful in 4m29s
- Add syncServers setting (default false) to extension and native host state - Add "Sync Servers" toggle under Settings → Sync; when disabled, server tunnels stay local and are not synced across linked devices - On push: omit servers from snapshot when syncServers is false - On apply: keep local servers and nextServerId when syncServers is false (incremental and full restore)
This commit is contained in:
@@ -14,7 +14,8 @@ const SETTINGS_DEFAULTS = {
|
||||
tunnelAutoReconnect: true,
|
||||
latencyPingEnabled: false,
|
||||
latencyPingIntervalMs: 5000,
|
||||
dashboardRefreshIntervalMs: 2000
|
||||
dashboardRefreshIntervalMs: 2000,
|
||||
syncServers: false
|
||||
};
|
||||
|
||||
let currentState = null;
|
||||
|
||||
@@ -688,6 +688,18 @@
|
||||
<div class="toggle" id="toggleTunnelAutoReconnect" data-setting="tunnelAutoReconnect"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-heading">Sync</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">Sync Servers</div>
|
||||
<div class="setting-desc">When enabled, server tunnels are synced across linked devices. When disabled (default), server tunnels stay local to this device.</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<div class="toggle" id="toggleSyncServers" data-setting="syncServers"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">Debug Mode</div>
|
||||
|
||||
@@ -26,6 +26,7 @@ function updateSettingsUI() {
|
||||
$('toggleNotify')?.classList.toggle('active', settings.notifyOnDisconnect === true);
|
||||
$('toggleNotifyTunnelError')?.classList.toggle('active', settings.notifyOnTunnelError !== false);
|
||||
$('toggleTunnelAutoReconnect')?.classList.toggle('active', settings.tunnelAutoReconnect === true);
|
||||
$('toggleSyncServers')?.classList.toggle('active', settings.syncServers === true);
|
||||
$('toggleDebug')?.classList.toggle('active', settings.debug === true);
|
||||
$('toggleDisableFileUrls')?.classList.toggle('active', settings.disableOnFileUrls === true);
|
||||
$('toggleTheme')?.classList.toggle('active', document.documentElement.getAttribute('data-theme') === 'light');
|
||||
@@ -48,6 +49,7 @@ function saveSettings() {
|
||||
settings.notifyOnDisconnect = $('toggleNotify')?.classList.contains('active') ?? SETTINGS_DEFAULTS.notifyOnDisconnect;
|
||||
settings.notifyOnTunnelError = $('toggleNotifyTunnelError')?.classList.contains('active') ?? SETTINGS_DEFAULTS.notifyOnTunnelError;
|
||||
settings.tunnelAutoReconnect = $('toggleTunnelAutoReconnect')?.classList.contains('active') ?? SETTINGS_DEFAULTS.tunnelAutoReconnect;
|
||||
settings.syncServers = $('toggleSyncServers')?.classList.contains('active') ?? SETTINGS_DEFAULTS.syncServers;
|
||||
settings.debug = $('toggleDebug')?.classList.contains('active') ?? SETTINGS_DEFAULTS.debug;
|
||||
settings.disableOnFileUrls = $('toggleDisableFileUrls')?.classList.contains('active') ?? SETTINGS_DEFAULTS.disableOnFileUrls;
|
||||
settings.latencyPingEnabled = $('toggleLatencyPing')?.classList.contains('active') ?? SETTINGS_DEFAULTS.latencyPingEnabled;
|
||||
|
||||
@@ -47,6 +47,7 @@ function updateSettings(patch) {
|
||||
if (typeof patch.dashboardRefreshIntervalMs === 'number' && patch.dashboardRefreshIntervalMs >= 0 && [0, 2000, 5000, 10000].includes(patch.dashboardRefreshIntervalMs)) {
|
||||
currentSettings.dashboardRefreshIntervalMs = patch.dashboardRefreshIntervalMs;
|
||||
}
|
||||
if (typeof patch.syncServers === 'boolean') currentSettings.syncServers = patch.syncServers;
|
||||
if (_saveState) _saveState();
|
||||
return { requiresRestart };
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ const SETTINGS_DEFAULTS = {
|
||||
tunnelAutoReconnect: true,
|
||||
latencyPingEnabled: false,
|
||||
latencyPingIntervalMs: 5000,
|
||||
dashboardRefreshIntervalMs: 2000
|
||||
dashboardRefreshIntervalMs: 2000,
|
||||
syncServers: false
|
||||
};
|
||||
|
||||
const DEBUG = process.env.HOLESAIL_DEBUG === '1' || process.env.HOLESAIL_DEBUG === 'true';
|
||||
|
||||
@@ -315,7 +315,9 @@ async function applySyncedStateIncremental(snapshot) {
|
||||
if (myId) snapshot.deviceNames[myId] = typeof os.hostname === 'function' ? os.hostname() : 'device';
|
||||
}
|
||||
const current = getStateSnapshot();
|
||||
const snapServers = Array.isArray(snapshot.servers) ? snapshot.servers : [];
|
||||
const settings = holesailManager.getSettings ? holesailManager.getSettings() : {};
|
||||
const syncServers = settings.syncServers === true;
|
||||
const snapServers = syncServers && Array.isArray(snapshot.servers) ? snapshot.servers : [];
|
||||
const snapVhosts = Array.isArray(snapshot.virtualHosts) ? snapshot.virtualHosts : [];
|
||||
const snapSvc = Array.isArray(snapshot.serviceTunnels) ? snapshot.serviceTunnels : [];
|
||||
const curVhosts = new Map((current.virtualHosts || []).map(v => [String(v.hostname || '').toLowerCase(), v]));
|
||||
@@ -325,7 +327,8 @@ async function applySyncedStateIncremental(snapshot) {
|
||||
const snapServersById = new Map(snapServers.map(s => [s.id, s]));
|
||||
const snapSvcById = new Map(snapSvc.map(t => [t.id, t]));
|
||||
|
||||
applySnapshotData(snapshot);
|
||||
const snapshotToApply = syncServers ? snapshot : { ...snapshot, servers: [], nextServerId: current.nextServerId };
|
||||
applySnapshotData(snapshotToApply);
|
||||
|
||||
for (const [hostname] of curVhosts) {
|
||||
if (!snapVhostsByHost.has(hostname)) {
|
||||
@@ -378,7 +381,8 @@ async function applySyncedStateIncremental(snapshot) {
|
||||
|
||||
const dir = path.dirname(statePath);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(statePath, JSON.stringify(snapshot, null, 2), 'utf8');
|
||||
const toWrite = syncServers ? snapshot : { ...snapshot, servers: current.servers || [], nextServerId: current.nextServerId };
|
||||
fs.writeFileSync(statePath, JSON.stringify(toWrite, null, 2), 'utf8');
|
||||
} finally {
|
||||
setStateSaveSuppressed(false);
|
||||
applyingSync = false;
|
||||
@@ -399,6 +403,12 @@ async function applySyncedStateFull(snapshot) {
|
||||
const myId = shortId(pass.writerKey);
|
||||
if (myId) snapshot.deviceNames[myId] = typeof os.hostname === 'function' ? os.hostname() : 'device';
|
||||
}
|
||||
const settings = holesailManager.getSettings ? holesailManager.getSettings() : {};
|
||||
if (settings.syncServers !== true) {
|
||||
const current = holesailManager.getStateSnapshot ? holesailManager.getStateSnapshot() : {};
|
||||
snapshot.servers = Array.isArray(current.servers) ? current.servers : [];
|
||||
snapshot.nextServerId = typeof current.nextServerId === 'number' ? current.nextServerId : 0;
|
||||
}
|
||||
const dir = path.dirname(statePath);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
const json = JSON.stringify(snapshot, null, 2);
|
||||
@@ -430,6 +440,9 @@ function onStateSaved(snapshot) {
|
||||
if (applyingSync || !pass) return;
|
||||
(async () => {
|
||||
try {
|
||||
if (holesailManager && typeof holesailManager.getSettings === 'function' && !holesailManager.getSettings().syncServers) {
|
||||
snapshot.servers = [];
|
||||
}
|
||||
const myId = shortId(pass.writerKey);
|
||||
if (myId) {
|
||||
snapshot.deviceNames = snapshot.deviceNames || {};
|
||||
|
||||
Reference in New Issue
Block a user