feat(sync): Delink and Disband with master state retention
CI / Build & Test (push) Successful in 4m4s
CI / Build & Test (push) Successful in 4m4s
- Delink: any peer can leave; removeWriter notifies others, local state reset to defaults. - Disband: master only; push syncGroupDisbanded so peers reset; master exports state to state.json, then reloads (cleanup + restorePersistedState + restorePersistedTunnels). - Skip handleDisbandFromMaster when receiver is master so master state is not reset. - Store syncMasterDeviceId in synced state so MASTER badge shows on all devices. - UI: show Delink for peers, Disband for master only; confirm modals for both.
This commit is contained in:
@@ -213,6 +213,8 @@ async function applyRemoteUpdate() {
|
|||||||
if (!str) return;
|
if (!str) return;
|
||||||
const snapshot = JSON.parse(str);
|
const snapshot = JSON.parse(str);
|
||||||
if (snapshot.syncGroupDisbanded === true) {
|
if (snapshot.syncGroupDisbanded === true) {
|
||||||
|
const identity = loadIdentity();
|
||||||
|
if (identity && identity.isMaster) return;
|
||||||
await handleDisbandFromMaster();
|
await handleDisbandFromMaster();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -693,8 +695,8 @@ async function delink() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disband the sync group (master only). Pushes state with syncGroupDisbanded: true so peers
|
* Disband the sync group (master only). Exports current state to state.json, pushes
|
||||||
* reset to defaults and remove their sync data. Master keeps local state but removes own sync data.
|
* syncGroupDisbanded so peers reset, then removes sync data and reloads state from state.json.
|
||||||
*/
|
*/
|
||||||
async function disband() {
|
async function disband() {
|
||||||
if (!storageDir || !holesailManager) return { ok: false, error: 'Storage path or holesail manager not set' };
|
if (!storageDir || !holesailManager) return { ok: false, error: 'Storage path or holesail manager not set' };
|
||||||
@@ -703,8 +705,23 @@ async function disband() {
|
|||||||
await ensureInitialized();
|
await ensureInitialized();
|
||||||
if (!pass) return { ok: false, error: 'Not linked' };
|
if (!pass) return { ok: false, error: 'Not linked' };
|
||||||
const setStateSaveSuppressed = holesailManager.setStateSaveSuppressed;
|
const setStateSaveSuppressed = holesailManager.setStateSaveSuppressed;
|
||||||
|
const setTunnelsRestoredPromiseFn = setTunnelsRestoredPromise;
|
||||||
|
const restorePersistedTunnelsFn = restorePersistedTunnels;
|
||||||
try {
|
try {
|
||||||
|
const statePath = getStateFilePath();
|
||||||
const snapshot = holesailManager.getStateSnapshot();
|
const snapshot = holesailManager.getStateSnapshot();
|
||||||
|
const deviceNames = readDeviceNamesFromState();
|
||||||
|
if (Object.keys(deviceNames).length > 0) snapshot.deviceNames = deviceNames;
|
||||||
|
delete snapshot.syncGroupDisbanded;
|
||||||
|
if (statePath) {
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(statePath, JSON.stringify(snapshot, null, 2), 'utf8');
|
||||||
|
log('Sync: state exported to state.json');
|
||||||
|
} catch (e) {
|
||||||
|
if (process.stderr) process.stderr.write('[sync-manager] disband export failed: ' + e.message + '\n');
|
||||||
|
return { ok: false, error: e.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
snapshot.syncGroupDisbanded = true;
|
snapshot.syncGroupDisbanded = true;
|
||||||
if (typeof setStateSaveSuppressed === 'function') setStateSaveSuppressed(true);
|
if (typeof setStateSaveSuppressed === 'function') setStateSaveSuppressed(true);
|
||||||
try {
|
try {
|
||||||
@@ -716,8 +733,18 @@ async function disband() {
|
|||||||
await closePass();
|
await closePass();
|
||||||
removeAutopassDir();
|
removeAutopassDir();
|
||||||
clearIdentity();
|
clearIdentity();
|
||||||
|
initPromise = null;
|
||||||
lastSyncedAt = null;
|
lastSyncedAt = null;
|
||||||
log('Sync: group disbanded by master');
|
if (setTunnelsRestoredPromiseFn && restorePersistedTunnelsFn) {
|
||||||
|
await holesailManager.cleanup().catch(() => {});
|
||||||
|
const restored = holesailManager.restorePersistedState();
|
||||||
|
const restorePromise = restorePersistedTunnelsFn(holesailManager, restored).catch((e) =>
|
||||||
|
log('Sync: post-disband restore failed:', e.message)
|
||||||
|
);
|
||||||
|
setTunnelsRestoredPromiseFn(restorePromise);
|
||||||
|
await restorePromise;
|
||||||
|
}
|
||||||
|
log('Sync: group disbanded by master; state reloaded from state.json');
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (typeof setStateSaveSuppressed === 'function') setStateSaveSuppressed(false);
|
if (typeof setStateSaveSuppressed === 'function') setStateSaveSuppressed(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user