From d49b1b8472d28a243222dcd96c4d28ba47dac7cf Mon Sep 17 00:00:00 2001 From: digua Date: Tue, 23 Jun 2026 21:59:25 +0800 Subject: [PATCH] fix(web): honor analytics opt-out state --- .../src/routes/web/telemetry.test.ts | 41 +++++++++++++++++++ .../http-routes/src/routes/web/telemetry.ts | 10 +++++ src/services/platform/web.test.ts | 35 ++++++++++++++++ src/services/platform/web.ts | 21 ++++++++-- 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 packages/http-routes/src/routes/web/telemetry.test.ts diff --git a/packages/http-routes/src/routes/web/telemetry.test.ts b/packages/http-routes/src/routes/web/telemetry.test.ts new file mode 100644 index 00000000..ad7f04ee --- /dev/null +++ b/packages/http-routes/src/routes/web/telemetry.test.ts @@ -0,0 +1,41 @@ +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import Fastify from 'fastify' +import type { HttpRouteContext } from '../../context' +import { registerTelemetryRoutes } from './telemetry' + +describe('telemetry routes', () => { + it('reads and writes analytics enabled state through the shared service', async () => { + let enabled = true + const app = Fastify() + registerTelemetryRoutes(app, { + analyticsService: { + getEnabled: () => enabled, + setEnabled: (next: boolean) => { + enabled = next + }, + }, + } as unknown as HttpRouteContext) + + await app.ready() + try { + const before = await app.inject({ method: 'GET', url: '/_web/telemetry/enabled' }) + assert.equal(before.statusCode, 200) + assert.deepEqual(before.json(), { enabled: true }) + + const update = await app.inject({ + method: 'POST', + url: '/_web/telemetry/enabled', + payload: { enabled: false }, + }) + assert.equal(update.statusCode, 200) + assert.deepEqual(update.json(), { success: true }) + + const after = await app.inject({ method: 'GET', url: '/_web/telemetry/enabled' }) + assert.equal(after.statusCode, 200) + assert.deepEqual(after.json(), { enabled: false }) + } finally { + await app.close() + } + }) +}) diff --git a/packages/http-routes/src/routes/web/telemetry.ts b/packages/http-routes/src/routes/web/telemetry.ts index 27f310a9..45821e2a 100644 --- a/packages/http-routes/src/routes/web/telemetry.ts +++ b/packages/http-routes/src/routes/web/telemetry.ts @@ -2,6 +2,16 @@ import type { FastifyInstance } from 'fastify' import type { HttpRouteContext } from '../../context' export function registerTelemetryRoutes(server: FastifyInstance, ctx: HttpRouteContext): void { + server.get('/_web/telemetry/enabled', async () => { + return { enabled: ctx.analyticsService?.getEnabled() ?? false } + }) + + server.post<{ Body: { enabled?: boolean } }>('/_web/telemetry/enabled', async (req) => { + if (!ctx.analyticsService || typeof req.body?.enabled !== 'boolean') return { success: false } + ctx.analyticsService.setEnabled(req.body.enabled) + return { success: true } + }) + server.post<{ Body: { eventName: string; properties?: Record } }>( '/_web/telemetry/track', async (req, reply) => { diff --git a/src/services/platform/web.test.ts b/src/services/platform/web.test.ts index d1f74ef0..b7bb6d6a 100644 --- a/src/services/platform/web.test.ts +++ b/src/services/platform/web.test.ts @@ -36,4 +36,39 @@ describe('WebPlatformAdapter', () => { globalThis.fetch = originalFetch } }) + + it('reads analytics enabled state from the CLI web backend', async () => { + const originalFetch = globalThis.fetch + const requestedUrls: string[] = [] + globalThis.fetch = ((input: RequestInfo | URL) => { + requestedUrls.push(String(input)) + return Promise.resolve(new Response(JSON.stringify({ enabled: true }))) + }) as typeof fetch + + try { + assert.equal(await new WebPlatformAdapter().getAnalyticsEnabled(), true) + assert.deepEqual(requestedUrls, ['/_web/telemetry/enabled']) + } finally { + globalThis.fetch = originalFetch + } + }) + + it('writes analytics enabled state to the CLI web backend', async () => { + const originalFetch = globalThis.fetch + const requests: Array<{ url: string; init?: RequestInit }> = [] + globalThis.fetch = ((input: RequestInfo | URL, init?: RequestInit) => { + requests.push({ url: String(input), init }) + return Promise.resolve(new Response(JSON.stringify({ success: true }))) + }) as typeof fetch + + try { + const result = await new WebPlatformAdapter().setAnalyticsEnabled(false) + assert.deepEqual(result, { success: true }) + assert.equal(requests[0].url, '/_web/telemetry/enabled') + assert.equal(requests[0].init?.method, 'POST') + assert.equal(requests[0].init?.body, JSON.stringify({ enabled: false })) + } finally { + globalThis.fetch = originalFetch + } + }) }) diff --git a/src/services/platform/web.ts b/src/services/platform/web.ts index e63c68b2..749adfd0 100644 --- a/src/services/platform/web.ts +++ b/src/services/platform/web.ts @@ -45,11 +45,26 @@ export class WebPlatformAdapter implements PlatformAdapter { } async getAnalyticsEnabled(): Promise { - return false + try { + const resp = await fetchWithAuth('/_web/telemetry/enabled') + const data = (await resp.json()) as { enabled?: boolean } + return data.enabled === true + } catch { + return false + } } - async setAnalyticsEnabled(_enabled: boolean): Promise<{ success: boolean }> { - return { success: false } + async setAnalyticsEnabled(enabled: boolean): Promise<{ success: boolean }> { + try { + const resp = await fetchWithAuth('/_web/telemetry/enabled', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ enabled }), + }) + return (await resp.json()) as { success: boolean } + } catch { + return { success: false } + } } async trackDailyActive(locale: string): Promise {