utils.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import _converse from '../../shared/_converse.js';
  2. import api, { converse } from '../../shared/api/index.js';
  3. import log from '../../log.js';
  4. import { isArchived, isHeadline, isServerMessage, } from '../../shared/parsers';
  5. import { parseMessage } from './parsers.js';
  6. import { shouldClearCache } from '../../utils/core.js';
  7. const { Strophe, u } = converse.env;
  8. export function openChat (jid) {
  9. if (!u.isValidJID(jid)) {
  10. return log.warn(`Invalid JID "${jid}" provided in URL fragment`);
  11. }
  12. api.chats.open(jid);
  13. }
  14. export async function onClearSession () {
  15. if (shouldClearCache()) {
  16. await Promise.all(
  17. _converse.chatboxes.map(c => c.messages && c.messages.clearStore({ 'silent': true }))
  18. );
  19. const filter = o => o.get('type') !== _converse.CONTROLBOX_TYPE;
  20. _converse.chatboxes.clearStore({ 'silent': true }, filter);
  21. }
  22. }
  23. async function handleErrorMessage (stanza) {
  24. const from_jid = Strophe.getBareJidFromJid(stanza.getAttribute('from'));
  25. if (u.isSameBareJID(from_jid, _converse.bare_jid)) {
  26. return;
  27. }
  28. const chatbox = await api.chatboxes.get(from_jid);
  29. if (chatbox?.get('type') === _converse.PRIVATE_CHAT_TYPE) {
  30. chatbox?.handleErrorMessageStanza(stanza);
  31. }
  32. }
  33. export function autoJoinChats () {
  34. // Automatically join private chats, based on the
  35. // "auto_join_private_chats" configuration setting.
  36. api.settings.get('auto_join_private_chats').forEach(jid => {
  37. if (_converse.chatboxes.where({ 'jid': jid }).length) {
  38. return;
  39. }
  40. if (typeof jid === 'string') {
  41. api.chats.open(jid);
  42. } else {
  43. log.error('Invalid jid criteria specified for "auto_join_private_chats"');
  44. }
  45. });
  46. /**
  47. * Triggered once any private chats have been automatically joined as
  48. * specified by the `auto_join_private_chats` setting.
  49. * See: https://conversejs.org/docs/html/configuration.html#auto-join-private-chats
  50. * @event _converse#privateChatsAutoJoined
  51. * @example _converse.api.listen.on('privateChatsAutoJoined', () => { ... });
  52. * @example _converse.api.waitUntil('privateChatsAutoJoined').then(() => { ... });
  53. */
  54. api.trigger('privateChatsAutoJoined');
  55. }
  56. export function registerMessageHandlers () {
  57. _converse.connection.addHandler(
  58. stanza => {
  59. if (
  60. ['groupchat', 'error'].includes(stanza.getAttribute('type')) ||
  61. isHeadline(stanza) ||
  62. isServerMessage(stanza) ||
  63. isArchived(stanza)
  64. ) {
  65. return true;
  66. }
  67. return _converse.handleMessageStanza(stanza) || true;
  68. },
  69. null,
  70. 'message',
  71. );
  72. _converse.connection.addHandler(
  73. stanza => handleErrorMessage(stanza) || true,
  74. null,
  75. 'message',
  76. 'error'
  77. );
  78. }
  79. /**
  80. * Handler method for all incoming single-user chat "message" stanzas.
  81. * @param { MessageAttributes } attrs - The message attributes
  82. */
  83. export async function handleMessageStanza (stanza) {
  84. stanza = stanza.tree?.() ?? stanza;
  85. if (isServerMessage(stanza)) {
  86. // Prosody sends headline messages with type `chat`, so we need to filter them out here.
  87. const from = stanza.getAttribute('from');
  88. return log.info(`handleMessageStanza: Ignoring incoming server message from JID: ${from}`);
  89. }
  90. let attrs;
  91. try {
  92. attrs = await parseMessage(stanza);
  93. } catch (e) {
  94. return log.error(e);
  95. }
  96. if (u.isErrorObject(attrs)) {
  97. attrs.stanza && log.error(attrs.stanza);
  98. return log.error(attrs.message);
  99. }
  100. // XXX: Need to take XEP-428 <fallback> into consideration
  101. const has_body = !!(attrs.body || attrs.plaintext)
  102. const chatbox = await api.chats.get(attrs.contact_jid, { 'nickname': attrs.nick }, has_body);
  103. await chatbox?.queueMessage(attrs);
  104. /**
  105. * @typedef { Object } MessageData
  106. * An object containing the original message stanza, as well as the
  107. * parsed attributes.
  108. * @property { Element } stanza
  109. * @property { MessageAttributes } stanza
  110. * @property { ChatBox } chatbox
  111. */
  112. const data = { stanza, attrs, chatbox };
  113. /**
  114. * Triggered when a message stanza is been received and processed.
  115. * @event _converse#message
  116. * @type { object }
  117. * @property { module:converse-chat~MessageData } data
  118. */
  119. api.trigger('message', data);
  120. }
  121. /**
  122. * Ask the XMPP server to enable Message Carbons
  123. * See [XEP-0280](https://xmpp.org/extensions/xep-0280.html#enabling)
  124. * @param { Boolean } reconnecting
  125. */
  126. export async function enableCarbons () {
  127. const domain = Strophe.getDomainFromJid(_converse.bare_jid);
  128. const supported = await api.disco.supports(Strophe.NS.CARBONS, domain);
  129. if (!supported) {
  130. log.warn("Not enabling carbons because it's not supported!");
  131. return;
  132. }
  133. const iq = new Strophe.Builder('iq', {
  134. 'from': _converse.connection.jid,
  135. 'type': 'set'
  136. }).c('enable', {xmlns: Strophe.NS.CARBONS});
  137. const result = await api.sendIQ(iq, null, false);
  138. if (result === null) {
  139. log.warn(`A timeout occurred while trying to enable carbons`);
  140. } else if (u.isErrorStanza(result)) {
  141. log.warn('An error occurred while trying to enable message carbons.');
  142. log.error(result);
  143. } else {
  144. log.debug('Message carbons have been enabled.');
  145. }
  146. }