feat: Add opt-out setting for anonymous usage reporting (#743)
* refactor: align VS Code update checks with web runtime parity - VS Code now uses file-based installId (shared with web server) - Accepts platform/arch from webview for consistent behavior - Usage data collection now matches web implementation * feat: Add opt-out setting for anonymous usage reporting in Appearance - Add privacy control in Appearance settings to opt-out of anonymous usage reports - Usage data includes only app version, platform, and runtime - no personal data or code collected - Setting persists across all runtimes and controls the reportUsage parameter in update checks
This commit is contained in:
committed by
GitHub
parent
53c2a0d919
commit
64b55025e1
@@ -113,9 +113,39 @@ const execFileAsync = promisify(execFile);
|
||||
const gpgconfCandidates = ['gpgconf', '/opt/homebrew/bin/gpgconf', '/usr/local/bin/gpgconf'];
|
||||
|
||||
const OPENCHAMBER_SHARED_SETTINGS_PATH = path.join(os.homedir(), '.config', 'openchamber', 'settings.json');
|
||||
const VSCODE_INSTALL_ID_KEY = 'openchamber.installId.vscode';
|
||||
const UPDATE_CHECK_URL = process.env.OPENCHAMBER_UPDATE_API_URL || 'https://api.openchamber.dev/v1/update/check';
|
||||
|
||||
const getOpenChamberConfigDir = (): string => {
|
||||
if (process.platform === 'win32') {
|
||||
const appData = process.env.APPDATA;
|
||||
if (appData) return path.join(appData, 'openchamber');
|
||||
}
|
||||
return path.join(os.homedir(), '.config', 'openchamber');
|
||||
};
|
||||
|
||||
const sanitizeInstallScope = (scope: string): 'desktop-tauri' | 'vscode' | 'web' => {
|
||||
if (scope === 'desktop-tauri' || scope === 'vscode' || scope === 'web') return scope;
|
||||
return 'web';
|
||||
};
|
||||
|
||||
const getOrCreateInstallId = (scope: string): string => {
|
||||
const configDir = getOpenChamberConfigDir();
|
||||
const normalizedScope = sanitizeInstallScope(scope);
|
||||
const idPath = path.join(configDir, `install-id-${normalizedScope}`);
|
||||
|
||||
try {
|
||||
const existing = fs.readFileSync(idPath, 'utf8').trim();
|
||||
if (existing) return existing;
|
||||
} catch {
|
||||
// Generate new id.
|
||||
}
|
||||
|
||||
const installId = randomUUID();
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
fs.writeFileSync(idPath, `${installId}\n`, { encoding: 'utf8', mode: 0o600 });
|
||||
return installId;
|
||||
};
|
||||
|
||||
const mapNodePlatformToApiPlatform = (value: string): 'macos' | 'windows' | 'linux' | 'web' => {
|
||||
if (value === 'darwin') return 'macos';
|
||||
if (value === 'win32') return 'windows';
|
||||
@@ -129,22 +159,6 @@ const mapNodeArchToApiArch = (value: string): 'arm64' | 'x64' | 'unknown' => {
|
||||
return 'unknown';
|
||||
};
|
||||
|
||||
const getOrCreateVSCodeInstallId = async (ctx?: BridgeContext): Promise<string> => {
|
||||
const state = ctx?.context?.globalState;
|
||||
if (state) {
|
||||
const existing = state.get<string>(VSCODE_INSTALL_ID_KEY);
|
||||
if (typeof existing === 'string' && existing.trim().length > 0) {
|
||||
return existing.trim();
|
||||
}
|
||||
}
|
||||
|
||||
const generated = randomUUID();
|
||||
if (state) {
|
||||
await state.update(VSCODE_INSTALL_ID_KEY, generated);
|
||||
}
|
||||
return generated;
|
||||
};
|
||||
|
||||
const guessMimeTypeFromExtension = (ext: string) => {
|
||||
switch (ext) {
|
||||
case '.png':
|
||||
@@ -2935,14 +2949,20 @@ export async function handleBridgeMessage(message: BridgeRequest, ctx?: BridgeCo
|
||||
const deviceClass = typeof body.deviceClass === 'string' && body.deviceClass.trim().length > 0
|
||||
? body.deviceClass.trim()
|
||||
: 'desktop';
|
||||
const platformRaw = typeof body.platform === 'string' && body.platform.trim().length > 0
|
||||
? body.platform.trim()
|
||||
: os.platform();
|
||||
const archRaw = typeof body.arch === 'string' && body.arch.trim().length > 0
|
||||
? body.arch.trim()
|
||||
: os.arch();
|
||||
const reportUsage = body.reportUsage !== false;
|
||||
|
||||
const installId = await getOrCreateVSCodeInstallId(ctx);
|
||||
const installId = getOrCreateInstallId('vscode');
|
||||
const requestBody = {
|
||||
appType: 'vscode',
|
||||
deviceClass,
|
||||
platform: mapNodePlatformToApiPlatform(os.platform()),
|
||||
arch: mapNodeArchToApiArch(os.arch()),
|
||||
platform: mapNodePlatformToApiPlatform(platformRaw),
|
||||
arch: mapNodeArchToApiArch(archRaw),
|
||||
channel: 'stable',
|
||||
currentVersion,
|
||||
installId,
|
||||
|
||||
@@ -754,12 +754,16 @@ const handleLocalApiRequest = async (url: URL, init?: RequestInit) => {
|
||||
const currentVersion = url.searchParams.get('currentVersion') || undefined;
|
||||
const instanceMode = url.searchParams.get('instanceMode') || 'local';
|
||||
const deviceClass = url.searchParams.get('deviceClass') || 'desktop';
|
||||
const platform = url.searchParams.get('platform') || undefined;
|
||||
const arch = url.searchParams.get('arch') || undefined;
|
||||
const reportUsageRaw = (url.searchParams.get('reportUsage') || 'true').toLowerCase();
|
||||
const reportUsage = !(reportUsageRaw === 'false' || reportUsageRaw === '0' || reportUsageRaw === 'no');
|
||||
const data = await sendBridgeMessage('api:openchamber:update-check', {
|
||||
currentVersion,
|
||||
instanceMode,
|
||||
deviceClass,
|
||||
platform,
|
||||
arch,
|
||||
reportUsage,
|
||||
});
|
||||
return new Response(JSON.stringify(data), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
||||
|
||||
Reference in New Issue
Block a user