// SyncKit "Keys Secret" action for the dashboard app rows. // // The credential for POST /api/sync/keys/{claim,release,list}. Deliberately // separate from the app's API key: that key is compiled into shipped clients // and can be read out of a binary, so it cannot gate calls that spend the app's // key cap. This one belongs on a backend the developer controls. // // Registered through the dispatcher rather than added to the legacy // static/synckit-tabs.js globals, per the frontend_globals ratchet. import { register } from '../core/dispatch.ts'; import { csrfHeaders } from '../core/net.ts'; import { showToast } from '../core/toast.ts'; import { copyWithFeedback } from '../core/clipboard.ts'; /** A "Copy" button bound to `text`. Built in JS so the secret never lands in * markup the template rendered. */ function copyButton(text: string): HTMLButtonElement { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'btn-small btn-secondary'; btn.textContent = 'Copy'; btn.addEventListener('click', () => copyWithFeedback(btn, text, 'Copied')); return btn; } /** Replace the row's secret cell with the plaintext, a copy button, and the * save-it-now warning. The server returns the secret exactly once. */ function revealSecret(appId: string, secret: string): void { const cell = document.getElementById(`synckit-keys-secret-${appId}`); if (!cell) return; cell.replaceChildren(); const code = document.createElement('code'); code.textContent = secret; cell.appendChild(code); cell.appendChild(document.createTextNode(' ')); cell.appendChild(copyButton(secret)); const warn = document.createElement('div'); warn.className = 'form-hint'; warn.textContent = 'Save it now: it is not shown again. Keep it on a server, never in a shipped client.'; cell.appendChild(warn); } register('syncKitRegenKeysSecret', function (appId: string) { if (!appId) return; if ( !confirm( 'Generate a new keys secret? Any backend using the current one will stop working.', ) ) { return; } void fetch(`/api/sync/apps/${appId}/keys-secret`, { method: 'POST', credentials: 'same-origin', headers: csrfHeaders(), }) .then(async (res) => { if (!res.ok) { showToast('Failed to generate keys secret.'); return; } const data = (await res.json()) as { app_secret?: string }; if (!data.app_secret) { showToast('No secret returned.'); return; } revealSecret(appId, data.app_secret); showToast('Keys secret generated. Copy it now: it will not be shown again.'); }) .catch(() => { showToast('Network error. Please check your connection and try again.'); }); });