소스 검색

Fixes #3123: Contacts do not show up online until chat is opened with them.

The issue was that nothing was listening to the new `presenceChanged` event.
JC Brand 2 년 전
부모
커밋
cb1f929045

+ 1 - 0
CHANGES.md

@@ -9,6 +9,7 @@
 - Generate TypeScript declaration files into `dist/types`
 - Removed documentation about the no longer implemented `fullname` option.
 - Updated translations
+- #3123: Contacts do not show up online until chat is opened with them.
 - #3156: Add function to prevent drag stutter effect over iframes when resize is called in overlay mode
 - #3165: Use configured nickname in profile view in the control box
 

+ 4 - 0
Makefile

@@ -58,6 +58,10 @@ serve: node_modules dist
 serve_bg: node_modules
 	$(HTTPSERVE) -p $(HTTPSERVE_PORT) -c-1 -s &
 
+certs:
+	mkdir certs
+	cd certs && openssl req -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out chat.example.org.crt -keyout chat.example.org.key
+
 ########################################################################
 ## Translation machinery
 

+ 4 - 6
src/headless/plugins/roster/contacts.js

@@ -377,9 +377,7 @@ const RosterContacts = Collection.extend({
             _converse.xmppstatus.save({'status': show}, {'silent': true});
 
             const status_message = presence.querySelector('status')?.textContent;
-            if (status_message) {
-                _converse.xmppstatus.save({'status_message': status_message});
-            }
+            if (status_message) _converse.xmppstatus.save({ status_message });
         }
         if (_converse.jid === jid && presence_type === 'unavailable') {
             // XXX: We've received an "unavailable" presence from our
@@ -412,11 +410,11 @@ const RosterContacts = Collection.extend({
             return; // Ignore MUC
         }
 
-        const status_message = presence.querySelector('status')?.textContent;
         const contact = this.get(bare_jid);
 
-        if (contact && (status_message !== contact.get('status'))) {
-            contact.save({'status': status_message});
+        if (contact) {
+            const status = presence.querySelector('status')?.textContent;
+            if (contact.get('status') !== status) contact.save({status});
         }
 
         if (presence_type === 'subscribed' && contact) {

+ 13 - 15
src/headless/plugins/roster/presence.js

@@ -30,7 +30,7 @@ export const Presence = Model.extend({
         const hpr = this.getHighestPriorityResource();
         const show = hpr?.attributes?.show || 'offline';
         if (this.get('show') !== show) {
-            this.save({'show': show});
+            this.save({ show });
         }
     },
 
@@ -51,17 +51,17 @@ export const Presence = Model.extend({
      * @param { Element } presence: The presence stanza
      */
     addResource (presence) {
-        const jid = presence.getAttribute('from'),
-                name = Strophe.getResourceFromJid(jid),
-                delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, presence).pop(),
-                priority = presence.querySelector('priority')?.textContent ?? 0,
-                resource = this.resources.get(name),
-                settings = {
-                    'name': name,
-                    'priority': isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10),
-                    'show': presence.querySelector('show')?.textContent ?? 'online',
-                    'timestamp': delay ? dayjs(delay.getAttribute('stamp')).toISOString() : (new Date()).toISOString()
-                };
+        const jid = presence.getAttribute('from');
+        const name = Strophe.getResourceFromJid(jid);
+        const delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, presence).pop();
+        const priority = presence.querySelector('priority')?.textContent;
+        const resource = this.resources.get(name);
+        const settings = {
+            name,
+            'priority': isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10),
+            'show': presence.querySelector('show')?.textContent ?? 'online',
+            'timestamp': delay ? dayjs(delay.getAttribute('stamp')).toISOString() : (new Date()).toISOString()
+        };
         if (resource) {
             resource.save(settings);
         } else {
@@ -78,9 +78,7 @@ export const Presence = Model.extend({
      */
     removeResource (name) {
         const resource = this.resources.get(name);
-        if (resource) {
-            resource.destroy();
-        }
+        resource?.destroy();
     }
 });
 

+ 2 - 2
src/headless/shared/api/presence.js

@@ -10,8 +10,8 @@ export default {
         /**
          * Send out a presence stanza
          * @method _converse.api.user.presence.send
-         * @param { String } type
-         * @param { String } to
+         * @param { String } [type]
+         * @param { String } [to]
          * @param { String } [status] - An optional status message
          * @param { Array<Element>|Array<Strophe.Builder>|Element|Strophe.Builder } [child_nodes]
          *  Nodes(s) to be added as child nodes of the `presence` XML element.

+ 9 - 2
src/headless/utils/init.js

@@ -357,7 +357,7 @@ async function getLoginCredentialsFromBrowser () {
     if (!jid) return null;
 
     try {
-        const creds = await navigator.credentials.get({'password': true});
+        const creds = await navigator.credentials.get({ password: true});
         if (creds && creds.type == 'password' && isValidJID(creds.id)) {
             // XXX: We don't actually compare `creds.id` with `jid` because
             // the user might have been presented a list of credentials with
@@ -431,7 +431,7 @@ export async function attemptNonPreboundSession (credentials, automatic) {
  * The user's plaintext password is not stored, nor any material from which
  * the user's plaintext password could be recovered.
  *
- * @param { String } JID - The XMPP address for which to fetch the SCRAM keys
+ * @param { String } jid - The XMPP address for which to fetch the SCRAM keys
  * @returns { Promise } A promise which resolves once we've fetched the previously
  *  used login keys.
  */
@@ -444,6 +444,13 @@ export async function savedLoginInfo (jid) {
 }
 
 
+/**
+ * @param { Object } [credentials]
+ * @param { string } credentials.password
+ * @param { Object } credentials.password
+ * @param { string } credentials.password.ck
+ * @returns { Promise<void> }
+ */
 async function connect (credentials) {
     const { api } = _converse;
     if ([ANONYMOUS, EXTERNAL].includes(api.settings.get("authentication"))) {

+ 3 - 2
src/plugins/rosterview/contactview.js

@@ -15,10 +15,11 @@ export default class RosterContact extends CustomElement {
     }
 
     initialize () {
-        this.listenTo(this.model, "change", () => this.requestUpdate());
-        this.listenTo(this.model, "highlight", () => this.requestUpdate());
+        this.listenTo(this.model, 'change', () => this.requestUpdate());
+        this.listenTo(this.model, 'highlight', () => this.requestUpdate());
         this.listenTo(this.model, 'vcard:add', () => this.requestUpdate());
         this.listenTo(this.model, 'vcard:change', () => this.requestUpdate());
+        this.listenTo(this.model, 'presenceChanged', () => this.requestUpdate());
     }
 
     render () {

+ 31 - 0
src/plugins/rosterview/tests/roster.js

@@ -812,6 +812,37 @@ describe("The Contacts Roster", function () {
             expect(true).toBe(true);
         }));
 
+        it("will have their online statuses shown correctly",
+            mock.initConverse(
+                [], {},
+                async function (_converse) {
+
+            await mock.waitForRoster(_converse, 'current', 1);
+            await mock.openControlBox(_converse);
+            const icon_el = document.querySelector('converse-roster-contact converse-icon');
+            expect(icon_el.getAttribute('color')).toBe('var(--subdued-color)');
+
+            let pres = $pres({from: 'mercutio@montague.lit/resource'});
+            _converse.connection._dataRecv(mock.createRequest(pres));
+            await u.waitUntil(() => icon_el.getAttribute('color') === 'var(--chat-status-online)');
+
+            pres = $pres({from: 'mercutio@montague.lit/resource'}).c('show', 'away');
+            _converse.connection._dataRecv(mock.createRequest(pres));
+            await u.waitUntil(() => icon_el.getAttribute('color') === 'var(--chat-status-away)');
+
+            pres = $pres({from: 'mercutio@montague.lit/resource'}).c('show', 'xa');
+            _converse.connection._dataRecv(mock.createRequest(pres));
+            await u.waitUntil(() => icon_el.getAttribute('color') === 'var(--subdued-color)');
+
+            pres = $pres({from: 'mercutio@montague.lit/resource'}).c('show', 'dnd');
+            _converse.connection._dataRecv(mock.createRequest(pres));
+            await u.waitUntil(() => icon_el.getAttribute('color') === 'var(--chat-status-busy)');
+
+            pres = $pres({from: 'mercutio@montague.lit/resource', type: 'unavailable'});
+            _converse.connection._dataRecv(mock.createRequest(pres));
+            await u.waitUntil(() => icon_el.getAttribute('color') === 'var(--subdued-color)');
+        }));
+
         it("can be added to the roster and they will be sorted alphabetically",
             mock.initConverse(
                 [], {},

+ 2 - 1
webpack.html

@@ -40,7 +40,8 @@
             // muc_domain: 'conference.chat.example.org',
             muc_respect_autojoin: true,
             view_mode: 'fullscreen',
-            websocket_url: 'ws://chat.example.org:5380/xmpp-websocket',
+            websocket_url: 'ws://chat.example.org:5381/xmpp-websocket',
+            // websocket_url: 'wss://chat.example.org:5381/xmpp-websocket',
             // websocket_url: 'wss://conversejs.org/xmpp-websocket',
             // bosh_service_url: 'http://chat.example.org:5280/http-bind',
             allow_user_defined_connection_url: true,

+ 6 - 1
webpack/webpack.serve.js

@@ -12,7 +12,12 @@ module.exports = merge(common, {
     devtool: "inline-source-map",
     devServer: {
         static: [ path.resolve(__dirname, '../') ],
-        port: 3003
+        port: 3003,
+        // https: {
+        //     key: './certs/chat.example.org.key',
+        //     cert: './certs/chat.example.org.crt',
+        //     requestCert: true,
+        // },
     },
     plugins: [
         new HTMLWebpackPlugin({