2025-11-05 17:04:23 -03:00

138 lines
3.9 KiB
JavaScript

import { xml, jid, Client } from "@xmpp/client-core";
import _reconnect from "@xmpp/reconnect";
import _websocket from "@xmpp/websocket";
import _tcp from "@xmpp/tcp";
import _tls from "@xmpp/tls";
import _middleware from "@xmpp/middleware";
import _streamFeatures from "@xmpp/stream-features";
import _iqCaller from "@xmpp/iq/caller.js";
import _iqCallee from "@xmpp/iq/callee.js";
import _resolve from "@xmpp/resolve";
import _starttls from "@xmpp/starttls";
import _sasl2 from "@xmpp/sasl2";
import _sasl from "@xmpp/sasl";
import _resourceBinding from "@xmpp/resource-binding";
import _streamManagement from "@xmpp/stream-management";
import _bind2 from "@xmpp/client-core/src/bind2/bind2.js";
import _fast from "@xmpp/client-core/src/fast/fast.js";
import SASLFactory from "saslmechanisms";
import scramsha1 from "@xmpp/sasl-scram-sha-1";
import plain from "@xmpp/sasl-plain";
import anonymous from "@xmpp/sasl-anonymous";
import htsha256none from "@xmpp/sasl-ht-sha-256-none";
import createOnAuthenticate from "./lib/createOnAuthenticate.js";
import getDomain from "./lib/getDomain.js";
function client(options = {}) {
let { resource, credentials, username, password, userAgent, ...params } =
options;
const { domain, service } = params;
if (!domain && service) {
params.domain = getDomain(service);
}
const entity = new Client(params);
if (username && params.domain) {
entity.jid = jid(username, params.domain);
}
const reconnect = _reconnect({ entity });
const websocket = _websocket({ entity });
const tcp = setupIfAvailable(_tcp, { entity });
const tls = setupIfAvailable(_tls, { entity });
const middleware = _middleware({ entity });
const streamFeatures = _streamFeatures({ middleware });
const iqCaller = _iqCaller({ middleware, entity });
const iqCallee = _iqCallee({ middleware, entity });
const resolve = _resolve({ entity });
// SASL mechanisms - order matters and define priority
const saslFactory = new SASLFactory();
const mechanisms = Object.entries({
...(typeof scramsha1 === "function" && { scramsha1 }),
plain,
anonymous,
}).map(([k, v]) => ({ [k]: v(saslFactory) }));
// eslint-disable-next-line n/no-unsupported-features/node-builtins
userAgent ??= xml("user-agent", { id: globalThis.crypto.randomUUID() });
// Stream features - order matters and define priority
const starttls = setupIfAvailable(_starttls, { streamFeatures });
const sasl2 = _sasl2(
{ streamFeatures, saslFactory },
createOnAuthenticate(credentials ?? { username, password }, userAgent),
);
const fast = _fast({
sasl2,
entity,
});
sasl2.setup({ fast });
// SASL2 inline features
const bind2 = _bind2({ sasl2, entity }, resource);
// FAST mechanisms - order matters and define priority
htsha256none(fast.saslFactory);
// Stream features - order matters and define priority
const sasl = _sasl(
{ streamFeatures, saslFactory },
createOnAuthenticate(credentials ?? { username, password }, userAgent),
);
const streamManagement = _streamManagement({
streamFeatures,
entity,
middleware,
bind2,
sasl2,
});
const resourceBinding = _resourceBinding(
{ iqCaller, streamFeatures },
resource,
);
iqCallee?.get("urn:xmpp:ping", "ping", () => {
return {};
});
return Object.assign(entity, {
entity,
reconnect,
tcp,
websocket,
tls,
middleware,
streamFeatures,
iqCaller,
iqCallee,
resolve,
starttls,
saslFactory,
sasl2,
sasl,
resourceBinding,
streamManagement,
mechanisms,
bind2,
fast,
});
}
// In browsers and react-native some packages are excluded
// see package.json and https://metrobundler.dev/docs/configuration/#resolvermainfields
// in which case the default import returns an empty object
function setupIfAvailable(module, ...args) {
if (typeof module !== "function") {
return undefined;
}
return module(...args);
}
export { xml, jid, client };