/** * Sync page — device linking via autopass; create invite, pair with invite, show status. * Depends: core/utils.js ($), ui/modal.js (openModal, closeModal, showModalError), ui/toast.js (showToast) */ /** * Update the sync status UI from the native host. */ function updateSyncStatus() { chrome.runtime.sendMessage( { target: 'holesail-native', action: 'send', payload: { type: 'getSyncStatus' } }, (response) => { if (chrome.runtime.lastError) return; const dot = $('syncStatusDot'); const text = $('syncStatusText'); const lastSynced = $('syncLastSynced'); const btnCopy = $('btnCopyInvite'); const inviteDisplay = $('syncInviteDisplay'); if (!text) return; if (!response || response.error) { 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'; return; } if (response.linked) { if (dot) dot.style.background = 'var(--green)'; text.textContent = 'Linked'; if (response.lastSyncedAt) { if (lastSynced) lastSynced.textContent = 'Last synced ' + timeAgo(response.lastSyncedAt); } else if (lastSynced) lastSynced.textContent = ''; if (response.invite) { if (btnCopy) btnCopy.style.display = ''; if (inviteDisplay) { inviteDisplay.textContent = response.invite; inviteDisplay.style.display = 'block'; } } else { 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 syncGroupId = response.syncGroupId || '—'; const linkedDevices = Array.isArray(response.linkedDevices) ? response.linkedDevices : []; if (linkedDevices.length > 0) linkedDevices.sort((a, b) => (a.id || '').localeCompare(b.id || '')); let rows = ''; if (linkedDevices.length > 0) { linkedDevices.forEach((d, i) => { const label = d.name || (d.isCurrent ? (response.deviceName || 'This device') : ('Device ' + (i + 1))); const masterBadge = (d.isCurrent && response.isMaster) ? ' MASTER' : ''; rows += '' + escapeHtml(label) + masterBadge + '' + escapeHtml(d.id || '—') + ''; }); } else { const curName = response.deviceName || 'This device'; const masterBadge = response.isMaster ? ' MASTER' : ''; rows = '' + escapeHtml(curName) + masterBadge + '' + escapeHtml(response.deviceId || '—') + ''; } rows += 'Sync group' + escapeHtml(syncGroupId) + ''; tbody.innerHTML = rows; } } 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 = 'Not linked'; } } ); } /** * Attach event listeners for the Sync page. */ function setupSyncEvents() { const btnCreate = $('btnCreateSyncInvite'); const btnCopy = $('btnCopyInvite'); const btnPair = $('btnPairWithInvite'); const inviteInput = $('syncInviteInput'); const inviteDisplay = $('syncInviteDisplay'); btnCreate?.addEventListener('click', () => { if (btnCreate.disabled) return; btnCreate.disabled = true; btnCreate.textContent = 'Creating…'; chrome.runtime.sendMessage( { target: 'holesail-native', action: 'send', payload: { type: 'createSyncInvite' } }, (response) => { btnCreate.disabled = false; btnCreate.textContent = 'Create invite'; if (chrome.runtime.lastError) { showToast('Failed: ' + (chrome.runtime.lastError.message || 'unknown'), 'error'); return; } if (response && response.ok && response.invite) { inviteDisplay.style.display = 'block'; inviteDisplay.textContent = response.invite; btnCopy.style.display = ''; showToast('Invite created. Share it with the other device.', 'success'); updateSyncStatus(); } else { showToast(response && response.error ? response.error : 'Failed to create invite', 'error'); } } ); }); btnCopy?.addEventListener('click', () => { if (!inviteDisplay || !inviteDisplay.textContent) return; navigator.clipboard.writeText(inviteDisplay.textContent).then(() => { showToast('Invite copied to clipboard', 'success'); }).catch(() => { showToast('Copy failed', 'error'); }); }); btnPair?.addEventListener('click', () => { const invite = inviteInput?.value?.trim(); if (!invite) { showToast('Paste an invite first', 'error'); return; } 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) => { if (btnPair) { btnPair.disabled = false; btnPair.textContent = 'Link device'; } if (chrome.runtime.lastError) { showToast('Failed: ' + (chrome.runtime.lastError.message || 'unknown'), 'error'); return; } if (response && response.ok) { showToast('Device linked. State has been synced.', 'success'); if (inviteInput) inviteInput.value = ''; updateSyncStatus(); if (typeof refresh === 'function') refresh(); } else { showToast(response && response.error ? response.error : 'Linking failed', 'error'); } } ); }); }