changeset 1:d6df73b466f6

Implement XEP-0156 to discover the right BOSH endpoint.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Fri, 21 Dec 2018 23:44:18 +0100
parents 2a8d4e8600d0
children db033e5eabcb
files avatar.js client.js index.xhtml nickname.js util.js
diffstat 5 files changed, 70 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/avatar.js
+++ b/avatar.js
@@ -148,19 +148,6 @@ function initAvatar(connection) {
         sha1_reader.readAsArrayBuffer(file);
     });
 
-    function nsResolver(prefix) {
-        return {
-            pubsub: 'http://jabber.org/protocol/pubsub',
-            avatar_metadata: 'urn:xmpp:avatar:metadata',
-            avatar_data: 'urn:xmpp:avatar:data',
-        }[prefix] || null;
-    }
-
-    function parseXPath(elem, xpath)
-    {
-        return elem.getRootNode().evaluate(xpath, elem, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
-    }
-
     function friendlyDataSize(bytes) {
         let unit = 'B'
         if (bytes >= 1024) {
--- a/client.js
+++ b/client.js
@@ -1,21 +1,7 @@
 'use strict';
 
-const BOSH_SERVICE = 'https://bosh.linkmauve.fr/';
-
-function rawInput(data)
-{
-    console.log('RECV', data);
-}
-
-function rawOutput(data)
-{
-    console.log('SENT', data);
-}
-
 document.addEventListener('DOMContentLoaded', function () {
-    const connection = new Strophe.Connection(BOSH_SERVICE);
-    connection.rawInput = rawInput;
-    connection.rawOutput = rawOutput;
+    let connection = null;
 
     const jid_element = document.getElementById('jid');
     const pass_element = document.getElementById('pass');
@@ -25,17 +11,62 @@ document.addEventListener('DOMContentLoa
 
     const avatar_img = document.getElementById('avatar');
 
+    function rawInput(data)
+    {
+        console.log('RECV', data);
+    }
+
+    function rawOutput(data)
+    {
+        console.log('SENT', data);
+    }
+
     connect_button.addEventListener('click', function (evt) {
         if (connect_button.value == 'connect') {
-            connection.connect(jid_element.value,
-                               pass_element.value,
-                               onConnect);
-        } else {
+            const jid = jid_element.value;
+            getBOSHService(jid).then((bosh_service) => {
+                connection = new Strophe.Connection(bosh_service);
+                connection.rawInput = rawInput;
+                connection.rawOutput = rawOutput;
+                connection.connect(jid,
+                                   pass_element.value,
+                                   onConnect);
+            });
+        } else if (connection != null) {
             connection.disconnect();
         }
         evt.preventDefault();
     });
 
+    function getBOSHService(jid)
+    {
+        return new Promise((resolve, reject) => {
+            const [nodepart, domainpart] = jid.split('@', 2);
+            //const url = 'https://' + domainpart + '/.well-known/host-meta';
+            const url = '/.well-known/host-meta';
+            const xhr = new XMLHttpRequest();
+            xhr.onload = function (evt) {
+                const xml = evt.target.responseXML;
+                const links = parseXPath(xml, './xrd:XRD/xrd:Link', XPathResult.ORDERED_NODE_ITERATOR_TYPE);
+                let bosh_service = null;
+                while (true) {
+                    const link = links.iterateNext();
+                    if (!link)
+                        break;
+                    if (link.getAttributeNS(null, 'rel') == 'urn:xmpp:alt-connections:xbosh') {
+                        bosh_service = link.getAttributeNS(null, 'href');
+                        break;
+                    }
+                    // TODO: also support WebSocket.
+                }
+                console.log('bosh_service', bosh_service);
+                resolve(bosh_service);
+            };
+            xhr.open('GET', url);
+            xhr.send();
+        });
+    }
+
     function onConnect(status)
     {
         if (status == Strophe.Status.CONNECTING) {
--- a/index.xhtml
+++ b/index.xhtml
@@ -89,6 +89,7 @@
   </dl>
 </footer>
 
+<script src="util.js"/>
 <script src="strophe.js"/>
 <script src="client.js"/>
 <script src="nickname.js"/>
--- a/nickname.js
+++ b/nickname.js
@@ -9,18 +9,6 @@ function initNickname(connection) {
             .c('items', {node: 'http://jabber.org/protocol/nick'});
     connection.sendIQ(iq, onNickname, onNicknameRetrievalError.bind(null, 'PubSub query failed.'));
 
-    function nsResolver(prefix) {
-        return {
-            pubsub: 'http://jabber.org/protocol/pubsub',
-            nickname: 'http://jabber.org/protocol/nick',
-        }[prefix] || null;
-    }
-
-    function parseXPath(elem, xpath)
-    {
-        return elem.getRootNode().evaluate(xpath, elem, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
-    }
-
     function onNickname(result_iq)
     {
         const item = parseXPath(result_iq, './pubsub:pubsub/pubsub:items/pubsub:item');
new file mode 100644
--- /dev/null
+++ b/util.js
@@ -0,0 +1,19 @@
+function nsResolver(prefix) {
+    return {
+        xrd: 'http://docs.oasis-open.org/ns/xri/xrd-1.0',
+        pubsub: 'http://jabber.org/protocol/pubsub',
+        avatar_metadata: 'urn:xmpp:avatar:metadata',
+        avatar_data: 'urn:xmpp:avatar:data',
+        nickname: 'http://jabber.org/protocol/nick',
+    }[prefix] || null;
+}
+
+function parseXPath(elem, xpath, result)
+{
+    if (result === undefined)
+        result = XPathResult.FIRST_ORDERED_NODE_TYPE;
+    const value = elem.getRootNode().evaluate(xpath, elem, nsResolver, result, null);
+    if (result == XPathResult.FIRST_ORDERED_NODE_TYPE)
+        return value.singleNodeValue;
+    return value;
+}