@ked3/ktrace
Version:
跨平台埋点分析SDK
165 lines (140 loc) • 5.13 kB
text/typescript
import { v4 as uuidv4 } from "uuid";
/**
* 生成极短唯一ID(8-16字符)
* 基于62进制编码,结合时间戳、随机数和实例标识
*/
export function generateUltraShortId(): string {
// 62进制编码表(避免特殊字符,兼容URL)
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
// 1. 时间戳(取最近28位毫秒级时间,约可表示17年)
const timestamp = Date.now() - 1700000000000; // 减去起始时间,缩小数值范围
const timeStr = encodeToBase62(timestamp, chars, 6); // 固定6字符(约2^36范围)
// 2. 随机数(32位,约5.3字符)
const random = Math.floor(Math.random() * 0xFFFFFFFF); // 32位随机数
const randomStr = encodeToBase62(random, chars, 5); // 固定5字符
// 3. 拼接后共11字符(6+5)
return timeStr + randomStr;
}
/**
* 62进制编码工具函数
* @param num 待编码的数字
* @param chars 编码表
* @param minLength 最小长度(不足补前导字符)
*/
function encodeToBase62(num: number, chars: string, minLength: number): string {
if (num === 0) return chars[0].repeat(minLength);
let result = '';
const base = chars.length;
while (num > 0) {
result = chars[num % base] + result;
num = Math.floor(num / base);
}
// 不足最小长度时补前导字符(确保固定长度)
return result.padStart(minLength, chars[0]);
}
/**
* 获取会话ID
*/
export function getSessionId(): string {
let sessionId = sessionStorage.getItem("k_track_session_id") || '';
if (!sessionId) {
sessionId = generateUltraShortId();
sessionStorage.setItem("k_track_session_id", sessionId);
}
return sessionId;
}
/**
* 简单哈希函数,用于生成设备ID
*/
export function simpleHash(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // 转换为32位整数
}
return Math.abs(hash).toString(36) + Date.now().toString(36);
}
/**
* 获取设备ID,兼容electron和web环境
*/
export function getDeviceId(): string {
// 尝试从localStorage获取已存储的设备ID
let deviceId = '';
try {
deviceId = localStorage.getItem("k_track_device_id") || '';
} catch (e) {
// 如果localStorage不可用,从sessionStorage获取
try {
deviceId = sessionStorage.getItem("k_track_device_id") || '';
} catch (e2) {
// 如果都不可用,则生成新的
}
}
if (!deviceId) {
// 生成基于多种因素的设备ID
const factors = [];
// 检查是否为electron环境
const isElectron = typeof window !== 'undefined' &&
(window as any).process &&
(window as any).process.type === 'renderer';
if (isElectron) {
// Electron环境下的设备标识
try {
// 尝试获取electron特有信息
const { remote } = (window as any).require('electron');
if (remote) {
factors.push(`electron_${remote.app.getVersion()}`);
}
} catch (e) {
// 如果无法获取electron信息,使用通用方法
}
}
// 通用设备信息收集
if (typeof navigator !== 'undefined') {
factors.push(navigator.userAgent);
factors.push(navigator.language);
factors.push(navigator.platform);
if (navigator.hardwareConcurrency) {
factors.push(navigator.hardwareConcurrency.toString());
}
}
if (typeof screen !== 'undefined') {
factors.push(`${screen.width}x${screen.height}`);
factors.push(screen.colorDepth.toString());
}
// 时区信息
factors.push(Intl.DateTimeFormat().resolvedOptions().timeZone);
// 如果没有足够的因素,使用随机UUID
if (factors.length === 0) {
deviceId = uuidv4();
} else {
// 基于收集的信息生成稳定的设备ID
const combined = factors.join('|');
deviceId = simpleHash(combined);
}
// 尝试存储设备ID
try {
localStorage.setItem("k_track_device_id", deviceId);
} catch (e) {
try {
sessionStorage.setItem("k_track_device_id", deviceId);
} catch (e2) {
// 如果都不能存储,则每次都会重新生成
}
}
}
return deviceId;
}
/**
* 格式化日期为 yyyy-MM-dd HH:mm:ss 格式
*/
export function formatDateTime(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).length === 1 ? '0' + String(date.getMonth() + 1) : String(date.getMonth() + 1);
const day = String(date.getDate()).length === 1 ? '0' + String(date.getDate()) : String(date.getDate());
const hours = String(date.getHours()).length === 1 ? '0' + String(date.getHours()) : String(date.getHours());
const minutes = String(date.getMinutes()).length === 1 ? '0' + String(date.getMinutes()) : String(date.getMinutes());
const seconds = String(date.getSeconds()).length === 1 ? '0' + String(date.getSeconds()) : String(date.getSeconds());
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}