initial commit
This commit is contained in:
178
fluxer_app/src/utils/ClientInfoUtils.ts
Normal file
178
fluxer_app/src/utils/ClientInfoUtils.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {UAParser} from 'ua-parser-js';
|
||||
import Config from '~/Config';
|
||||
import {getElectronAPI, isDesktop} from '~/utils/NativeUtils';
|
||||
|
||||
interface ClientInfo {
|
||||
browserName?: string;
|
||||
browserVersion?: string;
|
||||
osName?: string;
|
||||
osVersion?: string;
|
||||
arch?: string;
|
||||
desktopVersion?: string;
|
||||
desktopChannel?: string;
|
||||
desktopArch?: string;
|
||||
desktopOS?: string;
|
||||
}
|
||||
|
||||
const normalize = <T>(value: T | null | undefined): T | undefined => value ?? undefined;
|
||||
|
||||
let cachedClientInfo: ClientInfo | null = null;
|
||||
let preloadPromise: Promise<ClientInfo> | null = null;
|
||||
|
||||
const parseUserAgent = (): ClientInfo => {
|
||||
const parser = new UAParser();
|
||||
const result = parser.getResult();
|
||||
return {
|
||||
browserName: normalize(result.browser.name),
|
||||
browserVersion: normalize(result.browser.version),
|
||||
osName: normalize(result.os.name),
|
||||
osVersion: normalize(result.os.version),
|
||||
arch: normalize(result.cpu.architecture),
|
||||
};
|
||||
};
|
||||
|
||||
export const getClientInfoSync = (): ClientInfo => {
|
||||
if (cachedClientInfo) {
|
||||
return cachedClientInfo;
|
||||
}
|
||||
try {
|
||||
return parseUserAgent();
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const preloadClientInfo = (): Promise<ClientInfo> => {
|
||||
if (cachedClientInfo) {
|
||||
return Promise.resolve(cachedClientInfo);
|
||||
}
|
||||
if (preloadPromise) {
|
||||
return preloadPromise;
|
||||
}
|
||||
preloadPromise = getClientInfo().then((info) => {
|
||||
cachedClientInfo = info;
|
||||
return info;
|
||||
});
|
||||
return preloadPromise;
|
||||
};
|
||||
|
||||
async function getDesktopContext(): Promise<Partial<ClientInfo>> {
|
||||
const electronApi = getElectronAPI();
|
||||
if (electronApi) {
|
||||
try {
|
||||
const desktopInfo = await electronApi.getDesktopInfo();
|
||||
return {
|
||||
desktopVersion: normalize(desktopInfo.version),
|
||||
desktopChannel: normalize(desktopInfo.channel),
|
||||
desktopArch: normalize(desktopInfo.arch),
|
||||
desktopOS: normalize(desktopInfo.os),
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn('[ClientInfo] Failed to load desktop context', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
function getWindowsVersionName(osVersion: string): string {
|
||||
const parts = osVersion.split('.');
|
||||
const majorVersion = parseInt(parts[0], 10);
|
||||
const buildNumber = parseInt(parts[2], 10);
|
||||
|
||||
if (majorVersion === 10) {
|
||||
if (buildNumber >= 22000) {
|
||||
return 'Windows 11';
|
||||
}
|
||||
return 'Windows 10';
|
||||
}
|
||||
|
||||
return 'Windows';
|
||||
}
|
||||
|
||||
async function getOsContext(): Promise<Partial<ClientInfo>> {
|
||||
const electronApi = getElectronAPI();
|
||||
if (electronApi) {
|
||||
try {
|
||||
const desktopInfo = await electronApi.getDesktopInfo();
|
||||
let osName: string | undefined;
|
||||
let osVersion: string | undefined;
|
||||
|
||||
switch (desktopInfo.os) {
|
||||
case 'darwin':
|
||||
osName = 'macOS';
|
||||
break;
|
||||
case 'win32':
|
||||
osName = getWindowsVersionName(desktopInfo.osVersion);
|
||||
osVersion = desktopInfo.osVersion;
|
||||
break;
|
||||
case 'linux':
|
||||
osName = 'Linux';
|
||||
break;
|
||||
default:
|
||||
osName = desktopInfo.os;
|
||||
}
|
||||
return {
|
||||
osName,
|
||||
osVersion,
|
||||
arch: normalize(desktopInfo.arch),
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn('[ClientInfo] Failed to load OS context', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export const getClientInfo = async (): Promise<ClientInfo> => {
|
||||
const base = getClientInfoSync();
|
||||
if (!isDesktop()) return base;
|
||||
|
||||
const [osContext, desktop] = await Promise.all([getOsContext(), getDesktopContext()]);
|
||||
return {...base, ...osContext, ...desktop};
|
||||
};
|
||||
|
||||
export const getGatewayClientProperties = async (geo?: {latitude?: string | null; longitude?: string | null}) => {
|
||||
const info = await getClientInfo();
|
||||
return {
|
||||
os: info.osName ?? 'Unknown',
|
||||
os_version: info.osVersion ?? '',
|
||||
browser: info.browserName ?? 'Unknown',
|
||||
browser_version: info.browserVersion ?? '',
|
||||
device: info.arch ?? 'unknown',
|
||||
system_locale: navigator.language,
|
||||
locale: navigator.language,
|
||||
user_agent: navigator.userAgent,
|
||||
build_timestamp: Config.PUBLIC_BUILD_TIMESTAMP != null ? String(Config.PUBLIC_BUILD_TIMESTAMP) : '',
|
||||
build_sha: Config.PUBLIC_BUILD_SHA ?? '',
|
||||
build_number: Config.PUBLIC_BUILD_NUMBER != null ? Config.PUBLIC_BUILD_NUMBER : null,
|
||||
desktop_app_version: info.desktopVersion ?? null,
|
||||
desktop_app_channel: info.desktopChannel ?? null,
|
||||
desktop_arch: info.desktopArch ?? info.arch ?? null,
|
||||
desktop_os: info.desktopOS ?? info.osName ?? null,
|
||||
...(geo?.latitude ? {latitude: geo.latitude} : {}),
|
||||
...(geo?.longitude ? {longitude: geo.longitude} : {}),
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user