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.
This commit is contained in:
JC Brand 2023-06-09 20:37:27 +02:00
parent 9ba339a6d9
commit cb1f929045
10 changed files with 75 additions and 29 deletions

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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();
}
});

View File

@ -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.

View File

@ -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"))) {

View File

@ -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 () {

View File

@ -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(
[], {},

View File

@ -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,

View File

@ -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({