163 lines
5.2 KiB
JavaScript
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
|
|
};
|