fix(sync): stop sync loop; add link warning and linked-devices table
CI / Build & Test (push) Successful in 4m22s
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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user