diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7ab94415..91a63af6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,7 +37,13 @@ make ci Good luck and feel free to ask for help in https://github.com/xmppjs/xmpp.js/discussions -# Maintenance +## Design philosophy + +xmpp.js is a high level XMPP library. Learning about XMPP is required to use it. While it provides helpers for complex mechanisms such as authentication or transports, it doesn't attempt to abstract XMPP or XML. + +As such, simple XMPP semantics shouldn't be replaced with JavaScript APIs when a simple XML element can express them. + +## Maintenance ## Release a new version diff --git a/README.md b/README.md index 906b6906..4bd5dc95 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # xmpp.js -> XMPP is an open technology for real-time communication, which powers a wide range of applications including instant messaging, presence, multi-party chat, voice and video calls, collaboration, lightweight middleware, content syndication, and generalized routing of XML data. +> XMPP is the Extensible Messaging and Presence Protocol, a set of open technologies for instant messaging, presence, multi-party chat, voice and video calls, collaboration, lightweight middleware, content syndication, and generalized routing of XML data. -> [xmpp.org/about-xmpp/technology-overview/](https://xmpp.org/about/technology-overview.html) +[xmpp.org/about-xmpp/technology-overview/](https://xmpp.org/about/technology-overview.html) **xmpp.js** is a JavaScript library for [XMPP](http://xmpp.org/). @@ -60,7 +60,7 @@ Do you need help with working with xmpp.js? Please reach out to our community by - [Logitech Harmony Hub library](https://github.com/AirBorne04/harmonyhub) - [gx-twilio: Bridge between Twilio SMS and XMPP](https://github.com/pesvut/sgx-twilio) - Feel free to send a PR to add your project or organization to this list. +Feel free to send a PR to add your project or organization to this list. ## credits diff --git a/packages/client/index.js b/packages/client/index.js index aac728be..6b52adab 100644 --- a/packages/client/index.js +++ b/packages/client/index.js @@ -23,20 +23,9 @@ import scramsha1 from "@xmpp/sasl-scram-sha-1"; import plain from "@xmpp/sasl-plain"; import anonymous from "@xmpp/sasl-anonymous"; -// 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); -} - function client(options = {}) { - const { resource, credentials, username, password, ...params } = options; - const { clientId, software, device } = params; + const { resource, credentials, username, password, userAgent, ...params } = + options; const { domain, service } = params; if (!domain && service) { @@ -68,12 +57,12 @@ function client(options = {}) { const starttls = setupIfAvailable(_starttls, { streamFeatures }); const sasl2 = _sasl2( { streamFeatures, saslFactory }, - createOnAuthenticate(credentials ?? { username, password }), - { clientId, software, device }, + createOnAuthenticate(credentials ?? { username, password }, userAgent), ); + service; const sasl = _sasl( { streamFeatures, saslFactory }, - createOnAuthenticate(credentials ?? { username, password }), + createOnAuthenticate(credentials ?? { username, password }, userAgent), ); const streamManagement = _streamManagement({ streamFeatures, @@ -111,4 +100,15 @@ function client(options = {}) { }); } +// 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 }; diff --git a/packages/client/lib/createOnAuthenticate.js b/packages/client/lib/createOnAuthenticate.js index d8b142f3..28eb797f 100644 --- a/packages/client/lib/createOnAuthenticate.js +++ b/packages/client/lib/createOnAuthenticate.js @@ -1,6 +1,6 @@ const ANONYMOUS = "ANONYMOUS"; -export default function createOnAuthenticate(credentials) { +export default function createOnAuthenticate(credentials, userAgent) { return async function onAuthenticate(authenticate, mechanisms) { if (typeof credentials === "function") { await credentials(authenticate, mechanisms); @@ -12,10 +12,10 @@ export default function createOnAuthenticate(credentials) { !credentials?.password && mechanisms.includes(ANONYMOUS) ) { - await authenticate(credentials, ANONYMOUS); + await authenticate(credentials, ANONYMOUS, userAgent); return; } - await authenticate(credentials, mechanisms[0]); + await authenticate(credentials, mechanisms[0], userAgent); }; } diff --git a/packages/sasl/README.md b/packages/sasl/README.md index 41a5a6de..f4a8fbcd 100644 --- a/packages/sasl/README.md +++ b/packages/sasl/README.md @@ -43,13 +43,8 @@ async function onAuthenticate(authenticate, mechanisms) { password: await prompt("enter password"), }; console.debug("authenticating"); - try { - await authenticate(credentials, mechanisms[0]); - console.debug("authenticated"); - } catch (err) { - console.error(err); - throw err; - } + await authenticate(credentials, mechanisms[0]); + console.debug("authenticated"); } ``` diff --git a/packages/sasl2/README.md b/packages/sasl2/README.md index 30112b9f..65b91c53 100644 --- a/packages/sasl2/README.md +++ b/packages/sasl2/README.md @@ -15,9 +15,6 @@ const client = xmpp({ credentials: { username: "foo", password: "bar", - clientId: "Some UUID for this client/server pair (optional)", - software: "Name of this software (optional)", - device: "Description of this device (optional)", }, }); ``` @@ -31,30 +28,43 @@ Uses cases: - Have the user enter the password every time - Do not ask for password before connection is made - Debug authentication -- Using a SASL mechanism with specific requirements (such as FAST) -- Perform an asynchronous operation to get credentials +- Using a SASL mechanism with specific requirements +- Fetch credentials from a secure database ```js import { xmpp } from "@xmpp/client"; const client = xmpp({ credentials: authenticate, - clientId: "Some UUID for this client/server pair (optional)", - software: "Name of this software (optional)", - device: "Description of this device (optional)", }); -async function authenticate(callback, mechanisms) { - const fast = mechanisms.find((mech) => mech.canFast)?.name; - const mech = mechanisms.find((mech) => mech.canOther)?.name; - - return callback( - { - username: await prompt("enter username"), - password: await prompt("enter password"), - requestToken: fast, - }, - mech, +async function onAuthenticate(authenticate, mechanisms) { + console.debug("authenticate", mechanisms); + const credentials = { + username: await prompt("enter username"), + password: await prompt("enter password"), + }; + console.debug("authenticating"); + + // userAgent is optional + const userAgent = await getUserAgent(); + + await authenticate(credentials, mechanisms[0], userAgent); + console.debug("authenticated"); +} + +async function getUserAgent() { + let id = localStorage.get("user-agent-id"); + if (!id) { + id = await crypto.randomUUID(); + localStorage.set("user-agent-id", id); + } + return ( + // https://xmpp.org/extensions/xep-0388.html#initiation + + xmpp.js + Sonny's Laptop + ); } ``` diff --git a/packages/sasl2/index.js b/packages/sasl2/index.js index 1348f81a..10a01e62 100644 --- a/packages/sasl2/index.js +++ b/packages/sasl2/index.js @@ -95,15 +95,7 @@ async function authenticate({ xml("authenticate", { xmlns: NS, mechanism: mech.name }, [ mech.clientFirst && xml("initial-response", {}, encode(mech.response(creds))), - (userAgent?.clientId || userAgent?.software || userAgent?.device) && - xml( - "user-agent", - userAgent.clientId ? { id: userAgent.clientId } : {}, - [ - userAgent.software && xml("software", {}, userAgent.software), - userAgent.device && xml("device", {}, userAgent.device), - ], - ), + userAgent, ]), ); @@ -111,11 +103,7 @@ async function authenticate({ }); } -export default function sasl2( - { streamFeatures, saslFactory }, - onAuthenticate, - // userAgent, -) { +export default function sasl2({ streamFeatures, saslFactory }, onAuthenticate) { streamFeatures.use("authentication", NS, async ({ stanza, entity }) => { const offered = getMechanismNames(stanza); const supported = saslFactory._mechs.map(({ name }) => name); @@ -125,12 +113,13 @@ export default function sasl2( throw new SASLError("SASL: No compatible mechanism available."); } - async function done(credentials, mechanism) { + async function done(credentials, mechanism, userAgent) { await authenticate({ saslFactory, entity, mechanism, credentials, + userAgent, }); } diff --git a/packages/sasl2/test.js b/packages/sasl2/test.js index 43445f82..0be1f91a 100644 --- a/packages/sasl2/test.js +++ b/packages/sasl2/test.js @@ -45,13 +45,19 @@ test("with object credentials", async () => { test("with function credentials", async () => { const mech = "PLAIN"; + const userAgent = ( + + xmpp.js + Sonny's Laptop + + ); - function authenticate(auth, mechanisms) { + function onAuthenticate(authenticate, mechanisms) { expect(mechanisms).toEqual([mech]); - return auth(credentials, mech); + return authenticate(credentials, mech, userAgent); } - const { entity } = mockClient({ credentials: authenticate }); + const { entity } = mockClient({ credentials: onAuthenticate }); entity.mockInput( @@ -64,6 +70,7 @@ test("with function credentials", async () => { expect(await promise(entity, "send")).toEqual( AGZvbwBiYXI= + {userAgent} , );