homeio
PublikFork dari doctor-io/homeio
protocol.mjs
const ALLOWED_METHODS = new Set([
"network.getStatus",
"network.scan",
"network.connect",
"network.disconnect",
]);
class HelperProtocolError extends Error {
constructor(message, options = {}) {
super(message, {
cause: options.cause,
});
this.name = "HelperProtocolError";
this.code = options.code ?? "invalid_request";
this.statusCode = options.statusCode ?? 400;
}
}
function isRecord(value) {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function asOptionalString(value) {
if (typeof value !== "string") return null;
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : null;
}
function validateConnectParams(params) {
if (!isRecord(params)) {
throw new HelperProtocolError("Invalid connect parameters");
}
const ssid = asOptionalString(params.ssid);
if (!ssid || ssid.length > 128) {
throw new HelperProtocolError("Invalid ssid");
}
const password = params.password;
if (password !== undefined && (typeof password !== "string" || password.length < 1 || password.length > 128)) {
throw new HelperProtocolError("Invalid password");
}
return {
ssid,
password,
};
}
function validateDisconnectParams(params) {
if (params === undefined || params === null) {
return {};
}
if (!isRecord(params)) {
throw new HelperProtocolError("Invalid disconnect parameters");
}
const iface = asOptionalString(params.iface);
if (params.iface !== undefined && (!iface || iface.length > 64)) {
throw new HelperProtocolError("Invalid iface");
}
return iface ? { iface } : {};
}
function validateMethodParams(method, params) {
if (method === "network.getStatus" || method === "network.scan") {
return {};
}
if (method === "network.connect") {
return validateConnectParams(params);
}
if (method === "network.disconnect") {
return validateDisconnectParams(params);
}
throw new HelperProtocolError("Method not allowed");
}
function parseRpcRequest(line) {
let parsed;
try {
parsed = JSON.parse(line);
} catch {
throw new HelperProtocolError("Invalid JSON request");
}
if (!isRecord(parsed)) {
throw new HelperProtocolError("Request payload must be an object");
}
const id = asOptionalString(parsed.id);
const method = asOptionalString(parsed.method);
const requestId = asOptionalString(parsed.requestId);
if (!id || id.length > 128) {
throw new HelperProtocolError("Missing or invalid request id");
}
if (!method || !ALLOWED_METHODS.has(method)) {
throw new HelperProtocolError("Method not allowed");
}
return {
id,
method,
requestId,
params: validateMethodParams(method, parsed.params),
};
}
function toErrorPayload(error) {
if (
error &&
typeof error === "object" &&
"code" in error &&
"message" in error &&
typeof error.code === "string" &&
typeof error.message === "string"
) {
return {
code: error.code,
message: error.message,
};
}
if (error instanceof HelperProtocolError) {
return {
code: error.code,
message: error.message,
};
}
if (error instanceof Error) {
return {
code: "internal_error",
message: error.message,
};
}
return {
code: "internal_error",
message: "Unknown error",
};
}
export { ALLOWED_METHODS, HelperProtocolError, parseRpcRequest, toErrorPayload };