diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 251742019..2f1042a5e 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -192,6 +192,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VITE_VARIANT: full VITE_DESKTOP_RUNTIME: '1' + CONVEX_URL: ${{ secrets.CONVEX_URL }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }} @@ -215,6 +216,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VITE_VARIANT: full VITE_DESKTOP_RUNTIME: '1' + CONVEX_URL: ${{ secrets.CONVEX_URL }} with: tagName: v__VERSION__ releaseName: 'World Monitor v__VERSION__' @@ -232,6 +234,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VITE_VARIANT: tech VITE_DESKTOP_RUNTIME: '1' + CONVEX_URL: ${{ secrets.CONVEX_URL }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }} @@ -256,6 +259,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VITE_VARIANT: tech VITE_DESKTOP_RUNTIME: '1' + CONVEX_URL: ${{ secrets.CONVEX_URL }} with: tagName: v__VERSION__-tech releaseName: 'Tech Monitor v__VERSION__' diff --git a/src-tauri/sidecar/local-api-server.mjs b/src-tauri/sidecar/local-api-server.mjs index 9b1438242..7cc4e1ad4 100644 --- a/src-tauri/sidecar/local-api-server.mjs +++ b/src-tauri/sidecar/local-api-server.mjs @@ -796,8 +796,12 @@ async function dispatch(requestUrl, req, routes, context) { } return json({ verboseMode }); } - // Registration proxy — forward to cloud (bypasses Vercel bot protection) + // Registration — call Convex directly (Vercel Attack Challenge Mode blocks server-side) if (requestUrl.pathname === '/api/register-interest' && req.method === 'POST') { + const convexUrl = process.env.CONVEX_URL; + if (!convexUrl) { + return json({ error: 'Registration service not configured' }, 503); + } try { const body = await new Promise((resolve, reject) => { const chunks = []; @@ -805,21 +809,29 @@ async function dispatch(requestUrl, req, routes, context) { req.on('end', () => resolve(Buffer.concat(chunks).toString())); req.on('error', reject); }); - const response = await fetchWithTimeout('https://worldmonitor.app/api/register-interest', { + const parsed = JSON.parse(body); + const email = parsed.email; + if (!email || typeof email !== 'string' || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { + return json({ error: 'Invalid email address' }, 400); + } + const response = await fetchWithTimeout(`${convexUrl}/api/mutation`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Origin': 'https://worldmonitor.app', - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - }, - body, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + path: 'registerInterest:register', + args: { email, source: parsed.source || 'desktop', appVersion: parsed.appVersion || 'unknown' }, + format: 'json', + }), }, 15000); const responseBody = await response.text(); - return new Response(responseBody || '{}', { - status: response.status, - headers: { 'content-type': 'application/json' }, - }); + let result; + try { result = JSON.parse(responseBody); } catch { result = { status: 'registered' }; } + if (result.status === 'error') { + return json({ error: result.errorMessage || 'Registration failed' }, 500); + } + return json(result.value || result); } catch (e) { + logVerbose(`[register-interest] error: ${e.message}`); return json({ error: 'Registration service unreachable' }, 502); } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 69b659826..107750341 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -733,6 +733,13 @@ fn start_local_api(app: &AppHandle) -> Result<(), String> { } append_desktop_log(app, "INFO", &format!("injected {secret_count} keychain secrets into sidecar env")); + // Inject build-time secrets (CI) with runtime env fallback (dev) + if let Some(url) = option_env!("CONVEX_URL") { + cmd.env("CONVEX_URL", url); + } else if let Ok(url) = std::env::var("CONVEX_URL") { + cmd.env("CONVEX_URL", url); + } + let child = cmd .spawn() .map_err(|e| format!("Failed to launch local API: {e}"))?; diff --git a/src/styles/settings-window.css b/src/styles/settings-window.css index 4cc9ae5c0..7baba802c 100644 --- a/src/styles/settings-window.css +++ b/src/styles/settings-window.css @@ -605,28 +605,27 @@ tr.diag-err td { color: var(--settings-red); } /* Hero banner */ .wm-hero { text-align: center; - padding: 28px 24px; - margin-bottom: 28px; + padding: 18px 20px; + margin-bottom: 16px; border: 1px solid rgba(52, 211, 153, 0.15); border-radius: 12px; background: linear-gradient(180deg, rgba(52, 211, 153, 0.04) 0%, transparent 100%); } .wm-hero-title { - margin: 0 0 12px; - font-size: 24px; + margin: 0 0 8px; + font-size: 22px; font-weight: 700; color: var(--settings-text); letter-spacing: -0.01em; } .wm-hero-desc { - margin: 0; - font-size: 14px; - color: var(--settings-text-secondary); - line-height: 1.6; - max-width: 480px; margin: 0 auto; + font-size: 13px; + color: var(--settings-text-secondary); + line-height: 1.5; + max-width: 480px; } /* OR divider */ @@ -634,7 +633,7 @@ tr.diag-err td { color: var(--settings-red); } display: flex; align-items: center; gap: 16px; - margin: 24px 0; + margin: 14px 0; color: var(--settings-text-secondary); font-size: 13px; font-weight: 500; @@ -650,43 +649,43 @@ tr.diag-err td { color: var(--settings-red); } /* BYOK footer */ .wm-byok { - margin-top: 28px; - padding: 16px 20px; + margin-top: 14px; + padding: 12px 16px; background: var(--settings-surface); border-radius: 8px; border: 1px solid var(--settings-border); } .wm-byok-title { - margin: 0 0 4px; - font-size: 14px; + margin: 0 0 2px; + font-size: 13px; font-weight: 600; color: var(--settings-text); } .wm-byok-desc { margin: 0; - font-size: 13px; + font-size: 12px; color: var(--settings-text-secondary); - line-height: 1.5; + line-height: 1.4; } .wm-section { - margin-bottom: 24px; + margin-bottom: 12px; } .wm-section-title { - margin: 0 0 4px; - font-size: 15px; + margin: 0 0 2px; + font-size: 14px; font-weight: 600; color: var(--settings-text); } .wm-section-desc { - margin: 0 0 12px; - font-size: 13px; + margin: 0 0 8px; + font-size: 12px; color: var(--settings-text-secondary); - line-height: 1.5; + line-height: 1.4; } .wm-key-row {