114 lines
2.5 KiB
JavaScript
114 lines
2.5 KiB
JavaScript
import xml from "@xmpp/xml";
|
|
|
|
/**
|
|
* References
|
|
* https://xmpp.org/rfcs/rfc6120.html#stanzas-semantics-iq
|
|
* https://xmpp.org/rfcs/rfc6120.html#stanzas-error
|
|
*/
|
|
|
|
const NS_STANZA = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
|
|
|
function isQuery({ name, type }) {
|
|
if (name !== "iq") return false;
|
|
if (type === "error" || type === "result") return false;
|
|
return true;
|
|
}
|
|
|
|
function isValidQuery({ type }, children, child) {
|
|
if (type !== "get" && type !== "set") return false;
|
|
if (children.length !== 1) return false;
|
|
if (!child) return false;
|
|
return true;
|
|
}
|
|
|
|
function buildReply({ stanza }) {
|
|
return xml("iq", {
|
|
to: stanza.attrs.from,
|
|
from: stanza.attrs.to,
|
|
id: stanza.attrs.id,
|
|
});
|
|
}
|
|
|
|
function buildReplyResult(ctx, child) {
|
|
const reply = buildReply(ctx);
|
|
reply.attrs.type = "result";
|
|
if (child) {
|
|
reply.append(child);
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
|
|
function buildReplyError(ctx, error, child) {
|
|
const reply = buildReply(ctx);
|
|
reply.attrs.type = "error";
|
|
if (child) {
|
|
reply.append(child);
|
|
}
|
|
|
|
reply.append(error);
|
|
return reply;
|
|
}
|
|
|
|
function buildError(type, condition) {
|
|
return xml("error", { type }, xml(condition, NS_STANZA));
|
|
}
|
|
|
|
function iqHandler(entity) {
|
|
return async function iqHandler(ctx, next) {
|
|
if (!isQuery(ctx)) return next();
|
|
|
|
const { stanza } = ctx;
|
|
const children = stanza.getChildElements();
|
|
const [child] = children;
|
|
|
|
if (!isValidQuery(ctx, children, child)) {
|
|
return buildReplyError(ctx, buildError("modify", "bad-request"), child);
|
|
}
|
|
|
|
ctx.element = child;
|
|
|
|
let reply;
|
|
try {
|
|
reply = await next();
|
|
} catch (err) {
|
|
entity.emit("error", err);
|
|
reply = buildError("cancel", "internal-server-error");
|
|
}
|
|
|
|
if (!reply) {
|
|
reply = buildError("cancel", "service-unavailable");
|
|
}
|
|
|
|
if (reply instanceof xml.Element && reply.is("error")) {
|
|
return buildReplyError(ctx, reply, child);
|
|
}
|
|
|
|
return buildReplyResult(
|
|
ctx,
|
|
reply instanceof xml.Element ? reply : undefined,
|
|
);
|
|
};
|
|
}
|
|
|
|
function route(type, ns, name, handler) {
|
|
return (ctx, next) => {
|
|
if ((ctx.type !== type) | !ctx.element || !ctx.element.is(name, ns))
|
|
return next();
|
|
return handler(ctx, next);
|
|
};
|
|
}
|
|
|
|
export default function iqCallee({ middleware, entity }) {
|
|
middleware.use(iqHandler(entity));
|
|
|
|
return {
|
|
get(ns, name, handler) {
|
|
middleware.use(route("get", ns, name, handler));
|
|
},
|
|
set(ns, name, handler) {
|
|
middleware.use(route("set", ns, name, handler));
|
|
},
|
|
};
|
|
}
|