Files
ARCG-Remote-Station-Software/plugins/rms.openwebrx.guard/index.js

163 lines
5.2 KiB
JavaScript

const crypto = require("crypto");
async function createPlugin(ctx) {
const tickets = new Map();
return {
async execute(action, input) {
if (action === "issueAccess") {
return issueAccess(ctx, tickets, input || {});
}
if (action === "verifyAccess") {
return verifyAccess(tickets, input || {});
}
if (action === "revokeOwner") {
return revokeOwner(tickets, input || {});
}
if (action === "serviceStart") {
return controlService(ctx, true);
}
if (action === "serviceStop") {
return controlService(ctx, false);
}
if (action === "ensureSdrPath") {
return ensureSdrPath(ctx);
}
throw new Error(`Unknown action: ${action}`);
},
async getStatus() {
pruneExpired(tickets);
return {
activeTickets: tickets.size
};
},
async health() {
return { ok: true };
}
};
}
function issueAccess(ctx, tickets, input) {
pruneExpired(tickets);
const userId = String(input.userId || "").trim();
const ownerUserId = String(input.ownerUserId || "").trim();
if (!userId || !ownerUserId || userId !== ownerUserId) {
throw new Error("OpenWebRX Zugriff nur fuer aktiven Besitzer");
}
const ttlSec = Number(ctx.getSetting("ticketTtlSec", ctx.env.OPENWEBRX_TICKET_TTL_SEC || 3600));
const ttlExpiresAtMs = Date.now() + Math.max(10, ttlSec) * 1000;
const stationEndsAtMs = Date.parse(String(input.stationEndsAt || ""));
const expiresAtMs = Number.isFinite(stationEndsAtMs)
? Math.min(ttlExpiresAtMs, stationEndsAtMs)
: ttlExpiresAtMs;
const ticket = crypto.randomBytes(24).toString("base64url");
tickets.set(ticket, {
userId,
ownerUserId,
createdAt: new Date().toISOString(),
expiresAtMs
});
const openWebRxPath = String(ctx.getSetting("upstreamPath", ctx.env.OPENWEBRX_PATH || "/openwebrx/")).trim() || "/openwebrx/";
const separator = openWebRxPath.includes("?") ? "&" : "?";
const iframeUrl = `${openWebRxPath}${separator}ticket=${encodeURIComponent(ticket)}`;
return {
ticket,
expiresAt: new Date(expiresAtMs).toISOString(),
iframeUrl,
userId
};
}
function verifyAccess(tickets, input) {
pruneExpired(tickets);
const ticket = String(input.ticket || "").trim();
if (!ticket || !tickets.has(ticket)) {
return { ok: false };
}
const entry = tickets.get(ticket);
if (!entry || entry.expiresAtMs <= Date.now()) {
tickets.delete(ticket);
return { ok: false };
}
return {
ok: true,
userId: entry.userId,
ownerUserId: entry.ownerUserId,
expiresAt: new Date(entry.expiresAtMs).toISOString()
};
}
function revokeOwner(tickets, input) {
const ownerUserId = String(input.ownerUserId || "").trim();
if (!ownerUserId) {
return { ok: true, revoked: 0 };
}
let revoked = 0;
for (const [ticket, entry] of tickets.entries()) {
if (entry.ownerUserId === ownerUserId) {
tickets.delete(ticket);
revoked += 1;
}
}
return { ok: true, revoked };
}
async function controlService(ctx, start) {
const command = String(ctx.getSetting(start ? "startCommand" : "stopCommand", start ? (ctx.env.OPENWEBRX_START_CMD || "") : (ctx.env.OPENWEBRX_STOP_CMD || ""))).trim();
if (!command) {
return { ok: true, skipped: true, message: "Kein OpenWebRX Service-Kommando gesetzt" };
}
const simulate = typeof ctx.simulateHardware === "boolean"
? ctx.simulateHardware
: !(process.platform === "linux" || String(ctx.env.ALLOW_NON_LINUX_CMDS || "false") === "true");
if (simulate) {
return {
ok: true,
skipped: true,
message: `OpenWebRX ${start ? "start" : "stop"} simuliert (${ctx.execMode || "dev"})`
};
}
const timeoutMs = Number(ctx.getSetting("timeoutMs", ctx.env.OPENWEBRX_CTRL_TIMEOUT_MS || 20000));
const result = await ctx.commandRunner(command, { timeoutMs });
if (!result.ok) {
throw new Error(result.stderr || result.error || `OpenWebRX ${start ? "start" : "stop"} failed`);
}
return { ok: true, message: `OpenWebRX ${start ? "gestartet" : "gestoppt"}` };
}
async function ensureSdrPath(ctx) {
const command = String(ctx.getSetting("ensureSdrCommand", ctx.env.OPENWEBRX_ENSURE_SDR_CMD || "")).trim();
if (!command) {
return { ok: true, skipped: true, message: "Kein OpenWebRX SDR-Pfad-Kommando gesetzt" };
}
const simulate = typeof ctx.simulateHardware === "boolean"
? ctx.simulateHardware
: !(process.platform === "linux" || String(ctx.env.ALLOW_NON_LINUX_CMDS || "false") === "true");
if (simulate) {
return {
ok: true,
skipped: true,
message: `OpenWebRX SDR-Pfad nur simuliert (${ctx.execMode || "dev"})`
};
}
const timeoutMs = Number(ctx.getSetting("timeoutMs", ctx.env.OPENWEBRX_CTRL_TIMEOUT_MS || 20000));
const result = await ctx.commandRunner(command, { timeoutMs });
if (!result.ok) {
throw new Error(result.stderr || result.error || "OpenWebRX SDR-Pfad setzen fehlgeschlagen");
}
return { ok: true, message: "OpenWebRX SDR-Pfad auf SDR gesetzt" };
}
function pruneExpired(tickets) {
const now = Date.now();
for (const [ticket, entry] of tickets.entries()) {
if (!entry || entry.expiresAtMs <= now) {
tickets.delete(ticket);
}
}
}
module.exports = {
createPlugin
};