Browse Source

Updates #698: More work on MUC private messages

JC Brand 7 months ago
parent
commit
6781c672ca

+ 7 - 5
src/headless/plugins/muc/muc.js

@@ -238,9 +238,10 @@ class MUC extends ModelWithMessages(ColorAwareModel(ChatBoxBase)) {
 
     /**
      * Given the passed in MUC message, send a XEP-0333 chat marker.
-     * @param {MUCMessage} msg
+     * @async
+     * @param {Message} msg
      * @param {('received'|'displayed'|'acknowledged')} [type='displayed']
-     * @param {Boolean} force - Whether a marker should be sent for the
+     * @param {boolean} [force=false] - Whether a marker should be sent for the
      *  message, even if it didn't include a `markable` element.
      */
     sendMarkerForMessage (msg, type = 'displayed', force = false) {
@@ -252,11 +253,12 @@ class MUC extends ModelWithMessages(ColorAwareModel(ChatBoxBase)) {
             const id = msg.get(key);
             if (!id) {
                 log.error(`Can't send marker for message without stanza ID: ${key}`);
-                return;
+                return Promise.resolve();
             }
             const from_jid = Strophe.getBareJidFromJid(msg.get('from'));
             sendMarker(from_jid, id, type, msg.get('type'));
         }
+        return Promise.resolve();
     }
 
     /**
@@ -2788,9 +2790,9 @@ class MUC extends ModelWithMessages(ColorAwareModel(ChatBoxBase)) {
         this.save(settings);
     }
 
-    clearUnreadMsgCounter () {
+    async clearUnreadMsgCounter () {
         if (this.get('num_unread_general') > 0 || this.get('num_unread') > 0 || this.get('has_activity')) {
-            this.sendMarkerForMessage(this.messages.last());
+            await this.sendMarkerForMessage(this.messages.last());
         }
         safeSave(this, {
             'has_activity': false,

+ 1 - 1
src/headless/plugins/muc/tests/muc.js

@@ -33,7 +33,7 @@ describe("Groupchats", function () {
 
         // Check that unread counters are cleared when chat becomes visible
         model.set('hidden', false);
-        expect(model.get('num_unread_general')).toBe(0);
+        await u.waitUntil(() => model.get('num_unread_general') === 0);
         expect(model.get('num_unread')).toBe(0);
     }));
 

+ 1 - 1
src/headless/shared/model-with-messages.js

@@ -701,7 +701,7 @@ export default function ModelWithMessages(BaseModel) {
          * Given the passed in message object, send a XEP-0333 chat marker.
          * @param {Message} msg
          * @param {('received'|'displayed'|'acknowledged')} [type='displayed']
-         * @param {Boolean} force - Whether a marker should be sent for the
+         * @param {boolean} [force=false] - Whether a marker should be sent for the
          *  message, even if it didn't include a `markable` element.
          */
         async sendMarkerForMessage(msg, type = 'displayed', force = false) {

+ 1 - 1
src/headless/types/plugins/chat/model.d.ts

@@ -52,7 +52,7 @@ declare const ChatBox_base: {
             from: any;
             msgid: any;
         };
-        sendMarkerForMessage(msg: import("./message.js").default, type?: ("received" | "displayed" | "acknowledged"), force?: boolean): void;
+        sendMarkerForMessage(msg: import("./message.js").default, type?: ("received" | "displayed" | "acknowledged"), force?: boolean): Promise<void>;
         handleUnreadMessage(message: import("./message.js").default): void;
         handleErrorMessageStanza(stanza: Element): Promise<void>;
         incrementUnreadMsgsCounter(message: import("./message.js").default): void;

+ 6 - 5
src/headless/types/plugins/muc/muc.d.ts

@@ -52,7 +52,7 @@ declare const MUC_base: {
             from: any;
             msgid: any;
         };
-        sendMarkerForMessage(msg: import("../chat/message.js").default, type?: ("received" | "displayed" | "acknowledged"), force?: boolean): void;
+        sendMarkerForMessage(msg: import("../chat/message.js").default, type?: ("received" | "displayed" | "acknowledged"), force?: boolean): Promise<void>;
         handleUnreadMessage(message: import("../chat/message.js").default): void;
         handleErrorMessageStanza(stanza: Element): Promise<void>;
         incrementUnreadMsgsCounter(message: import("../chat/message.js").default): void;
@@ -266,12 +266,13 @@ declare class MUC extends MUC_base {
     clearOccupantsCache(): void;
     /**
      * Given the passed in MUC message, send a XEP-0333 chat marker.
-     * @param {MUCMessage} msg
+     * @async
+     * @param {Message} msg
      * @param {('received'|'displayed'|'acknowledged')} [type='displayed']
-     * @param {Boolean} force - Whether a marker should be sent for the
+     * @param {boolean} [force=false] - Whether a marker should be sent for the
      *  message, even if it didn't include a `markable` element.
      */
-    sendMarkerForMessage(msg: import("./message.js").default, type?: ("received" | "displayed" | "acknowledged"), force?: boolean): void;
+    sendMarkerForMessage(msg: import("../chat/message.js").default, type?: ("received" | "displayed" | "acknowledged"), force?: boolean): Promise<void>;
     /**
      * Finds the last eligible message and then sends a XEP-0333 chat marker for it.
      * @param { ('received'|'displayed'|'acknowledged') } [type='displayed']
@@ -833,7 +834,7 @@ declare class MUC extends MUC_base {
      * @param {MUCMessage} message - The text message
      */
     incrementUnreadMsgsCounter(message: import("./message.js").default): void;
-    clearUnreadMsgCounter(): void;
+    clearUnreadMsgCounter(): Promise<void>;
 }
 import { Model } from '@converse/skeletor';
 import ChatBoxBase from '../../shared/chatbox';

+ 1 - 1
src/headless/types/plugins/muc/occupant.d.ts

@@ -52,7 +52,7 @@ declare const MUCOccupant_base: {
             from: any;
             msgid: any;
         };
-        sendMarkerForMessage(msg: import("../chat").Message, type?: ("received" | "displayed" | "acknowledged"), force?: boolean): void;
+        sendMarkerForMessage(msg: import("../chat").Message, type?: ("received" | "displayed" | "acknowledged"), force?: boolean): Promise<void>;
         handleUnreadMessage(message: import("../chat").Message): void;
         handleErrorMessageStanza(stanza: Element): Promise<void>;
         incrementUnreadMsgsCounter(message: import("../chat").Message): void;

+ 1 - 1
src/headless/types/shared/chatbox.d.ts

@@ -51,7 +51,7 @@ declare const ChatBoxBase_base: {
             from: any;
             msgid: any;
         };
-        sendMarkerForMessage(msg: import("../index.js").Message, type?: ("received" | "displayed" | "acknowledged"), force?: boolean): void;
+        sendMarkerForMessage(msg: import("../index.js").Message, type?: ("received" | "displayed" | "acknowledged"), force?: boolean): Promise<void>;
         handleUnreadMessage(message: import("../index.js").Message): void;
         handleErrorMessageStanza(stanza: Element): Promise<void>;
         incrementUnreadMsgsCounter(message: import("../index.js").Message): void;

+ 2 - 2
src/headless/types/shared/model-with-messages.d.ts

@@ -177,10 +177,10 @@ export default function ModelWithMessages<T extends import("./types").ModelExten
          * Given the passed in message object, send a XEP-0333 chat marker.
          * @param {Message} msg
          * @param {('received'|'displayed'|'acknowledged')} [type='displayed']
-         * @param {Boolean} force - Whether a marker should be sent for the
+         * @param {boolean} [force=false] - Whether a marker should be sent for the
          *  message, even if it didn't include a `markable` element.
          */
-        sendMarkerForMessage(msg: import("../plugins/chat/message").default, type?: ("received" | "displayed" | "acknowledged"), force?: boolean): void;
+        sendMarkerForMessage(msg: import("../plugins/chat/message").default, type?: ("received" | "displayed" | "acknowledged"), force?: boolean): Promise<void>;
         /**
          * Given a newly received {@link Message} instance,
          * update the unread counter if necessary.

+ 4 - 4
src/headless/types/utils/index.d.ts

@@ -63,10 +63,10 @@ declare const _default: {
     merge(dst: any, src: any): void;
     isError(obj: any): boolean;
     isFunction(val: any): boolean;
-    isValidJID(jid: any): boolean;
-    isValidMUCJID(jid: any): boolean;
-    isSameBareJID(jid1: any, jid2: any): boolean;
-    isSameDomain(jid1: any, jid2: any): boolean;
+    isValidJID(jid?: string | null): boolean;
+    isValidMUCJID(jid: string): boolean;
+    isSameBareJID(jid1: string, jid2: string): boolean;
+    isSameDomain(jid1: string, jid2: string): boolean;
     getJIDFromURI(jid: string): string;
     isElement(el: unknown): boolean;
     isTagEqual(stanza: Element | typeof import("strophe.js").Builder, name: string): boolean;

+ 22 - 4
src/headless/types/utils/jid.d.ts

@@ -1,7 +1,25 @@
-export function isValidJID(jid: any): boolean;
-export function isValidMUCJID(jid: any): boolean;
-export function isSameBareJID(jid1: any, jid2: any): boolean;
-export function isSameDomain(jid1: any, jid2: any): boolean;
+/**
+ * @param {string|null} [jid]
+ * @returns {boolean}
+ */
+export function isValidJID(jid?: string | null): boolean;
+/**
+ * @param {string} jid
+ * @returns {boolean}
+ */
+export function isValidMUCJID(jid: string): boolean;
+/**
+ * @param {string} jid1
+ * @param {string} jid2
+ * @returns {boolean}
+ */
+export function isSameBareJID(jid1: string, jid2: string): boolean;
+/**
+ * @param {string} jid1
+ * @param {string} jid2
+ * @returns {boolean}
+ */
+export function isSameDomain(jid1: string, jid2: string): boolean;
 /**
  * @param {string} jid
  */

+ 1 - 1
src/plugins/rosterview/contactview.js

@@ -68,7 +68,7 @@ export default class RosterContact extends CustomElement {
         chat?.close();
 
         try {
-            if (this.model.get('subscription') === 'none') {
+            if (this.model.get('subscription') === 'none' && this.model.get('ask') !== 'subscribe') {
                 this.model.destroy();
             } else {
                 this.model.removeFromRoster();

+ 1 - 1
src/plugins/rosterview/modals/add-contact.js

@@ -40,7 +40,7 @@ export default class AddContactModal extends BaseModal {
     }
 
     afterSubmission (_form, jid, name, group) {
-        _converse.state.roster.addAndSubscribe({ jid, name, groups: Array.isArray(group) ? group : [group] });
+        _converse.state.roster.addContact({ jid, name, groups: Array.isArray(group) ? group : [group] });
         this.model.clear();
         this.modal.hide();
     }

+ 0 - 2
src/plugins/rosterview/tests/protocol.js

@@ -51,7 +51,6 @@ describe("The Protocol", function () {
             mock.openControlBox(_converse);
             const cbview = _converse.chatboxviews.get('controlbox');
 
-            spyOn(_converse.roster, "addAndSubscribe").and.callThrough();
             spyOn(_converse.roster, "addContact").and.callThrough();
             spyOn(_converse.roster, "sendContactAddIQ").and.callThrough();
             spyOn(_converse.api.vcard, "get").and.callThrough();
@@ -75,7 +74,6 @@ describe("The Protocol", function () {
              * subscription, the user's client SHOULD perform a "roster set"
              * for the new roster item.
              */
-            expect(_converse.roster.addAndSubscribe).toHaveBeenCalled();
             expect(_converse.roster.addContact).toHaveBeenCalled();
 
             /* The request consists of sending an IQ

+ 11 - 7
src/plugins/rosterview/tests/roster.js

@@ -444,7 +444,7 @@ describe("The Contacts Roster", function () {
 
             const roster = rosterview.querySelector('.roster-contacts');
             await u.waitUntil(() => sizzle('li', roster).filter(u.isVisible).length === 20, 900);
-            expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(5);
+            expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(6);
 
             filter.value = "online";
             u.triggerEvent(filter, 'change');
@@ -479,7 +479,7 @@ describe("The Contacts Roster", function () {
             // Check that the groups appear alphabetically and that
             // requesting and pending contacts are last.
             const rosterview = document.querySelector('converse-roster');
-            await u.waitUntil(() => sizzle('.roster-group a.group-toggle', rosterview).length === 6);
+            await u.waitUntil(() => sizzle('.roster-group a.group-toggle', rosterview).length === 7);
             let group_titles = sizzle('.roster-group a.group-toggle', rosterview).map(o => o.textContent.trim());
             expect(group_titles).toEqual([
                 "Contact requests",
@@ -488,13 +488,14 @@ describe("The Contacts Roster", function () {
                 "friends & acquaintences",
                 "ænemies",
                 "Ungrouped",
+                "Pending contacts",
             ]);
 
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             const contact = await _converse.api.contacts.get(contact_jid);
             contact.save({'num_unread': 5});
 
-            await u.waitUntil(() => sizzle('.roster-group a.group-toggle', rosterview).length === 7);
+            await u.waitUntil(() => sizzle('.roster-group a.group-toggle', rosterview).length === 8);
             group_titles = sizzle('.roster-group a.group-toggle', rosterview).map(o => o.textContent.trim());
 
             expect(group_titles).toEqual([
@@ -504,7 +505,8 @@ describe("The Contacts Roster", function () {
                 "Family",
                 "friends & acquaintences",
                 "ænemies",
-                "Ungrouped"
+                "Ungrouped",
+                "Pending contacts",
             ]);
             const contacts = sizzle('.roster-group[data-group="New messages"] li', rosterview);
             expect(contacts.length).toBe(1);
@@ -512,7 +514,7 @@ describe("The Contacts Roster", function () {
             expect(contacts[0].querySelector('.msgs-indicator').textContent).toBe("5");
 
             contact.save({'num_unread': 0});
-            await u.waitUntil(() => sizzle('.roster-group a.group-toggle', rosterview).length === 6);
+            await u.waitUntil(() => sizzle('.roster-group a.group-toggle', rosterview).length === 7);
             group_titles = sizzle('.roster-group a.group-toggle', rosterview).map(o => o.textContent.trim());
             expect(group_titles).toEqual([
                 "Contact requests",
@@ -520,7 +522,8 @@ describe("The Contacts Roster", function () {
                 "Family",
                 "friends & acquaintences",
                 "ænemies",
-                "Ungrouped"
+                "Ungrouped",
+                "Pending contacts",
             ]);
         }));
 
@@ -534,7 +537,7 @@ describe("The Contacts Roster", function () {
             await mock.waitForRoster(_converse, 'all');
             await mock.createContacts(_converse, 'requesting');
             const rosterview = document.querySelector('converse-roster');
-            await u.waitUntil(() => sizzle('.roster-group a.group-toggle', rosterview).length === 6);
+            await u.waitUntil(() => sizzle('.roster-group a.group-toggle', rosterview).length === 7);
             const group_titles = sizzle('.roster-group a.group-toggle', rosterview).map(o => o.textContent.trim());
             expect(group_titles).toEqual([
                 "Contact requests",
@@ -543,6 +546,7 @@ describe("The Contacts Roster", function () {
                 "friends & acquaintences",
                 "ænemies",
                 "Ungrouped",
+                "Pending contacts",
             ]);
             // Check that usernames appear alphabetically per group
             Object.keys(mock.groups).forEach(name  => {

+ 5 - 0
src/plugins/rosterview/utils.js

@@ -141,6 +141,11 @@ export function populateContactsMap (contacts_map, contact) {
     return contacts_map;
 }
 
+/**
+ * @param {RosterContact} contact1
+ * @param {RosterContact} contact2
+ * @returns {(-1|0|1)}
+ */
 export function contactsComparator (contact1, contact2) {
     const status1 = contact1.presence.get('show') || 'offline';
     const status2 = contact2.presence.get('show') || 'offline';

+ 6 - 1
src/types/plugins/rosterview/utils.d.ts

@@ -21,7 +21,12 @@ export function shouldShowGroup(group: any, model: any): boolean;
  * @returns {import('./types').ContactsMap}
  */
 export function populateContactsMap(contacts_map: import("./types").ContactsMap, contact: RosterContact): import("./types").ContactsMap;
-export function contactsComparator(contact1: any, contact2: any): 0 | 1 | -1;
+/**
+ * @param {RosterContact} contact1
+ * @param {RosterContact} contact2
+ * @returns {(-1|0|1)}
+ */
+export function contactsComparator(contact1: RosterContact, contact2: RosterContact): (-1 | 0 | 1);
 export function groupsComparator(a: any, b: any): 0 | 1 | -1;
 export function getGroupsAutoCompleteList(): any[];
 export function getJIDsAutoCompleteList(): any[];