mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
111 lines
4.1 KiB
TypeScript
111 lines
4.1 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
import {readFileSync, renameSync, statSync, writeFileSync} from 'node:fs';
|
|
import path from 'node:path';
|
|
import {createChildLogger} from '@electron/common/Logger';
|
|
|
|
const DESKTOP_ACCOUNT_STORE_TELEMETRY_FILE_NAME = 'account-store-telemetry-v1.json';
|
|
const DESKTOP_ACCOUNT_STORE_TELEMETRY_VERSION = 1;
|
|
|
|
export const DesktopAccountStoreEvent = Object.freeze({
|
|
MIGRATED: 'desktop.account_store.migrated',
|
|
QUARANTINED: 'desktop.account_store.quarantined',
|
|
FALLBACK_TO_WEB: 'desktop.account_store.fallback_to_web',
|
|
PERMISSION_HARDENING_FAILED: 'desktop.account_store.permission_hardening_failed',
|
|
} as const);
|
|
|
|
export type DesktopAccountStoreEvent = (typeof DesktopAccountStoreEvent)[keyof typeof DesktopAccountStoreEvent];
|
|
|
|
export type DesktopAccountStoreCounts = Readonly<Record<string, number>>;
|
|
|
|
export const DesktopStorePermissionState = Object.freeze({
|
|
HARDENED: 'hardened',
|
|
WIDENED: 'widened',
|
|
UNSUPPORTED: 'unsupported',
|
|
UNKNOWN: 'unknown',
|
|
} as const);
|
|
|
|
export type DesktopStorePermissionState =
|
|
(typeof DesktopStorePermissionState)[keyof typeof DesktopStorePermissionState];
|
|
|
|
const GROUP_AND_WORLD_BITS = 0o077;
|
|
const logger = createChildLogger('DesktopAccountStoreTelemetry');
|
|
|
|
function isFileNotFoundError(error: unknown): boolean {
|
|
return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
|
|
}
|
|
|
|
function desktopAccountStoreTelemetryFile(userDataPath: string): string {
|
|
return path.join(userDataPath, DESKTOP_ACCOUNT_STORE_TELEMETRY_FILE_NAME);
|
|
}
|
|
|
|
export function readDesktopAccountStoreTelemetry(userDataPath: string): DesktopAccountStoreCounts {
|
|
let parsed: unknown;
|
|
try {
|
|
parsed = JSON.parse(readFileSync(desktopAccountStoreTelemetryFile(userDataPath), 'utf8'));
|
|
} catch (error) {
|
|
if (!isFileNotFoundError(error)) {
|
|
logger.warn('Failed to read account store telemetry, using empty counts', error);
|
|
}
|
|
return {};
|
|
}
|
|
if (parsed == null || typeof parsed !== 'object') {
|
|
return {};
|
|
}
|
|
const {version, counts} = parsed as {version?: unknown; counts?: unknown};
|
|
if (version !== DESKTOP_ACCOUNT_STORE_TELEMETRY_VERSION || counts == null || typeof counts !== 'object') {
|
|
return {};
|
|
}
|
|
const known = new Set<string>(Object.values(DesktopAccountStoreEvent));
|
|
const result: Record<string, number> = {};
|
|
for (const [event, value] of Object.entries(counts as Record<string, unknown>)) {
|
|
if (known.has(event) && typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
result[event] = Math.floor(value);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function recordDesktopAccountStoreEvent(
|
|
userDataPath: string,
|
|
event: DesktopAccountStoreEvent,
|
|
): DesktopAccountStoreCounts {
|
|
const counts = {...readDesktopAccountStoreTelemetry(userDataPath)};
|
|
counts[event] = (counts[event] ?? 0) + 1;
|
|
const file = desktopAccountStoreTelemetryFile(userDataPath);
|
|
const pending = `${file}.tmp`;
|
|
try {
|
|
writeFileSync(
|
|
pending,
|
|
`${JSON.stringify({version: DESKTOP_ACCOUNT_STORE_TELEMETRY_VERSION, counts, updatedAt: Date.now()})}\n`,
|
|
{encoding: 'utf8', mode: 0o600},
|
|
);
|
|
renameSync(pending, file);
|
|
} catch (error) {
|
|
logger.warn('Failed to persist account store telemetry', {file, error});
|
|
}
|
|
return counts;
|
|
}
|
|
|
|
export function probeDesktopStorePermissions(storeFile: string): DesktopStorePermissionState {
|
|
if (process.platform === 'win32') {
|
|
return DesktopStorePermissionState.UNSUPPORTED;
|
|
}
|
|
try {
|
|
const file = statSync(storeFile);
|
|
const directory = statSync(path.dirname(storeFile));
|
|
const widened = (file.mode & GROUP_AND_WORLD_BITS) !== 0 || (directory.mode & GROUP_AND_WORLD_BITS) !== 0;
|
|
return widened ? DesktopStorePermissionState.WIDENED : DesktopStorePermissionState.HARDENED;
|
|
} catch (error) {
|
|
if (!isFileNotFoundError(error)) {
|
|
logger.warn('Failed to inspect account store permissions', {storeFile, error});
|
|
}
|
|
return DesktopStorePermissionState.UNKNOWN;
|
|
}
|
|
}
|
|
|
|
export function formatDesktopAccountStoreTelemetry(counts: DesktopAccountStoreCounts): string {
|
|
const entries = Object.values(DesktopAccountStoreEvent).map((event) => `${event}=${counts[event] ?? 0}`);
|
|
return entries.join(', ');
|
|
}
|