fix(sync): stop sync loop; add link warning and linked-devices table
CI / Build & Test (push) Successful in 4m22s

- Await restorePersistedTunnels so applyingSync stays true for full restore,
  preventing saveState() during tunnel startup from pushing back and looping
- Debounce onRemoteUpdate (600ms) and skip apply when state unchanged
  (canonical fingerprint) to avoid redundant restarts
- Add confirm modal before "Link device": warns that current state will be
  replaced and suggests creating a backup first
- Add Linked devices table when linked: show This device ID (writerKey) and
  Sync group ID (discoveryKey); getSyncStatus returns deviceId and syncGroupId
This commit is contained in:
Raven Scott
2026-03-15 01:47:34 -04:00
parent 33ea92cd43
commit 925376a6bd
3 changed files with 87 additions and 7 deletions
+29 -6
View File
@@ -1,6 +1,6 @@
/**
* Sync page — device linking via autopass; create invite, pair with invite, show status.
* Depends: core/utils.js ($), core/messaging.js (sendToNative), ui/toast.js (showToast)
* Depends: core/utils.js ($), ui/modal.js (openModal, closeModal, showModalError), ui/toast.js (showToast)
*/
/**
@@ -41,12 +41,25 @@ function updateSyncStatus() {
if (btnCopy) btnCopy.style.display = 'none';
if (inviteDisplay) inviteDisplay.style.display = 'none';
}
const card = $('syncLinkedDevicesCard');
const tbody = $('syncLinkedDevicesTable');
if (card) card.style.display = 'block';
if (tbody) {
const deviceId = response.deviceId || '—';
const syncGroupId = response.syncGroupId || '—';
tbody.innerHTML = '<tr><td>This device</td><td class="mono" style="font-size:12px;">' + escapeHtml(deviceId) + '</td></tr>' +
'<tr><td>Sync group</td><td class="mono" style="font-size:12px;">' + escapeHtml(syncGroupId) + '</td></tr>';
}
} else {
if (dot) dot.style.background = 'var(--text4)';
text.textContent = 'Not linked';
if (lastSynced) lastSynced.textContent = '';
if (btnCopy) btnCopy.style.display = 'none';
if (inviteDisplay) inviteDisplay.style.display = 'none';
const card = $('syncLinkedDevicesCard');
const tbody = $('syncLinkedDevicesTable');
if (card) card.style.display = 'none';
if (tbody) tbody.innerHTML = '<tr><td colspan="2" class="empty-cell">Not linked</td></tr>';
}
}
);
@@ -103,14 +116,24 @@ function setupSyncEvents() {
showToast('Paste an invite first', 'error');
return;
}
if (btnPair.disabled) return;
btnPair.disabled = true;
btnPair.textContent = 'Linking…';
const errEl = $('linkDeviceError');
if (errEl) { errEl.style.display = 'none'; errEl.textContent = ''; }
openModal('modal-linkDevice');
});
$('linkDeviceConfirm')?.addEventListener('click', () => {
const invite = inviteInput?.value?.trim();
const errEl = $('linkDeviceError');
if (!invite) {
if (errEl) { errEl.textContent = 'Paste an invite in the field below first.'; errEl.style.display = 'block'; }
return;
}
closeModal('modal-linkDevice');
if (btnPair) { btnPair.disabled = true; btnPair.textContent = 'Linking…'; }
chrome.runtime.sendMessage(
{ target: 'holesail-native', action: 'send', payload: { type: 'pairWithInvite', payload: { invite } } },
(response) => {
btnPair.disabled = false;
btnPair.textContent = 'Link device';
if (btnPair) { btnPair.disabled = false; btnPair.textContent = 'Link device'; }
if (chrome.runtime.lastError) {
showToast('Failed: ' + (chrome.runtime.lastError.message || 'unknown'), 'error');
return;