Skip to content
Snippets Groups Projects
solid-xmpp-chat.js 10.41 KiB
import './conversejs/converse.min.js';
import './conversejs/emojis.js';
import './plugins/converse-rai.js';
import { Helpers, store } from 'https://unpkg.com/@startinblox/core@0.12';
import { Sib } from "https://unpkg.com/@startinblox/core@0.12/dist/libs/Sib.js";
import { StoreMixin } from 'https://unpkg.com/@startinblox/core@0.12/dist/mixins/storeMixin.js';
import ComponentPath from './path.js';

class Deferred {
  constructor() {
    this.promise = new Promise(((resolve, reject) => {
      this.resolve = resolve;
      this.reject = reject;
    }).bind(this));
    this.then = this.promise.then.bind(this.promise);
    this.catch = this.promise.catch.bind(this.promise);
  }
}

export const SolidXMPPChat = {
  name: 'solid-xmpp-chat',
  use: [ StoreMixin ],
  attributes: {
    authentication: {
      type: String,
      default: 'login',
    },
    autoLogin: {
      type: Boolean,
      default: true,
    },
    i18n: {
      type: String,
      default: 'fr',
    },
    websocketUrl: {
      type: String,
      default: 'wss://jabber.happy-dev.fr/xmpp-websocket',
    },
  },

  get extra_context() {
    return { "foaf": 'http://xmlns.com/foaf/0.1/', "chatProfile": "http://happy-dev.fr/owl/#chatProfile", "jabberID": "foaf:jabberID" };
  },

  async created() {
    const check_identified = await document.querySelector('sib-auth').getUserIdToken();
    if(check_identified) {
      this.element.attachShadow({
        mode: "open"
      });
      this.element.shadowRoot.append(...Helpers.importCSS(
        `${ComponentPath}/dist/conversejs/converse.min.css`,
        `${ComponentPath}/dist/index.css`
      ));
      if (window.converse_sib === undefined) {
        this.initializeConverse();
      }
    } // Else, not logged in on page load (even if not on chat)
  },

  empty() {
    this.element.innerHTML = '';
  },

  async populate() {
    if(typeof converse_sib !== 'undefined') {
      await converse_sib.loaded_deferred;
      if (await this.resource.jabberRoom) {
        this.jid = await this.resource['jabberID'];
      } else {
        this.jid = await this.resource['chatProfile.jabberID'];
      }
      await converse_sib.connected_deferred
      converse_sib.service.plugins.sibChat.changeChat(
        this.jid,
        await this.resource.jabberRoom,
        this.element.shadowRoot,
      );
      window.dispatchEvent(new CustomEvent('read', {
        detail: {
          resource: {
            "@id": this.resource['@id']
          }
        }
      }));
    } // Else, not logged in, on chat change
  },

  async initializeConverse() {
    window.converse_sib = {};

    // Deferred resolved at the end of this function
    converse_sib.loaded_deferred = new Deferred();

    // Deferred resolved after converse.initialize
    converse_sib.connected_deferred = new Deferred();

    // Registering window.converse_sib.service
    converse_sib.service = {
      'waitUntilLoaded': converse_sib.loaded_deferred.promise,
      'initialize': function (options) {
        this.waitUntilLoaded().done(this.api.initialize, options);
      },
      'waitUntilConnected': converse_sib.connected_deferred.promise,
      'plugins': {}
    };

    // Alias for solid-xmpp-chat use # want to un-expose converse from window later
    // window.converse_sib.service.api === window.converse
    converse_sib.service.api = converse;

    // Change chat plugin
    converse_sib.service.plugins.sibChat = new(class {
      changeChat(jid, is_groupchat, root) {
        if (!jid) {
          return;
        }
        jid = jid.toLowerCase();
        this._converse.root = root;

        // Get the current used solid-xmpp-chat
        const converse_el = Array.from(document.querySelectorAll('solid-xmpp-chat'))
          .map(el => el.shadowRoot.getElementById('conversejs'))
          .filter(el => el)
          .pop();

        if (converse_el) {
          root.appendChild(converse_el);
        }
        if (is_groupchat) {
          this._converse.api.rooms.open(jid, {}, true);
        } else {
          this._converse.api.chats.open(jid, {}, true);
        }
      }
    });
    // Initialize change change plugin
    converse.plugins.add('conversejs-changechat', converse_sib.service.plugins.sibChat);
    // Initialize deferred resolution plugin
    converse.plugins.add('conversejs-sib-connected', {
      initialize() {
        this._converse.api.listen.on('connected', converse_sib.connected_deferred.resolve);
      }
    });

    // Initialize AUTHFAIL plugin
    converse.plugins.add('conversejs-sib-disconnected', {
      initialize() {
        this._converse.api.listen.on('disconnected', () => {
          if(this._converse.connfeedback.attributes.connection_status === converse.env.Strophe.Status.AUTHFAIL) {
            Array.from(document.querySelectorAll('solid-xmpp-chat')).map(el => el.shadowRoot.innerHTML = 
              `<div style='margin:3em;line-height:32px;'><b style='color:red;'>Erreur d'authentification.</b><br /><i style='color:grey;'>Êtes-vous sur le Hubl affilié à votre compte ?</i></div>`
            );
          }
        });
      }
    });

    // Initialize rai plugin
    converse.plugins.add('conversejs-rai', {
      async initialize() {

        let userRooms = (await Promise.all([
          new Promise((resolve, reject) => {
            const circleInterval = setInterval(async () => {
              let retry = false;
              let circles = [];
              const user = await document.querySelector('sib-auth').getUser();
              const userProfile = await store.getData(user['@id'], this.context);
              for (let circleMembership of await userProfile['circles.ldp:contains']) {
                if (circleMembership) {
                  circles.push(await circleMembership['circle.jabberID']);
                } else {
                  retry = true;
                }
              }
              if (retry) {
              } else {
                clearInterval(circleInterval);
                resolve(circles);
              }
            }, 250);
          }),
          new Promise((resolve, reject) => {
            const projectInterval = setInterval(async () => {
              let retry = false;
              let projects = [];
              const user = await document.querySelector('sib-auth').getUser();
              const userProfile = await store.getData(user['@id'], this.context);
              for (let projectMembership of await userProfile['projects.ldp:contains']) {
                if (projectMembership) {
                  projects.push(await projectMembership['project.jabberID']);
                } else {
                  retry = true;
                }
              }
              if (retry) {
              } else {
                clearInterval(projectInterval);
                resolve(projects);
              }
            }, 250);
          })
        ])).flat();

        // @MattJ Here userRooms is an array of each jabberID the user is on.
        this._converse.api.trigger('raiRoomsUpdated', userRooms);
        this._converse.api.listen.on('chatRoomActivityIndicators', function (jid) {
          window.dispatchEvent(new CustomEvent('newMessage', {
            detail: {
              jid: jid
            }
          }));
        });
      }
    });

    // Initialize deferred resolution plugin
    converse.plugins.add('conversejs-sib-focused', {
      initialize() {
        this._converse.api.listen.on('chatBoxFocused', function() {
          // Get the currently used solid-xmpp-chat & send read event of
          const resource = Array.from(document.querySelectorAll('solid-xmpp-chat'))
            .filter(el => {return el.shadowRoot.getElementById('conversejs')})
            .pop()
            .component.resource;
          window.dispatchEvent(new CustomEvent('read', {
            detail: {
              resource: resource
            }
          }));
        });
      }
    });

    const user = await document.querySelector('sib-auth').getUser();
    const userProfile = await store.getData(user['@id'], this.context);
    const jabberID = await userProfile['chatProfile.jabberID']; 

    if(this.resource) {
      if (await this.resource.jabberRoom) {
        this.jid = await this.resource['jabberID'];
      } else {
        this.jid = await this.resource['chatProfile.jabberID'];
      }
    } else {
      this.jid = jabberID;
    }

    const idToken = await document.querySelector('sib-auth').getUserIdToken();

    sessionStorage.clear();
    indexedDB.deleteDatabase('converse-persistent');

    converse.initialize({
      'assets_path': ComponentPath + '/dist/conversejs/',
      'authentication': this.element.dataset.authentication || 'login',
      'password': idToken,
      'allow_chat_pending_contacts': true,
      'allow_message_retraction': 'moderator',
      'allow_non_roster_messaging': true,
      'allow_dragresize': false,
      'allow_logout': false,
      'archived_messages_page_size': "30",
      'auto_list_rooms': true,
      'auto_login': this.element.dataset.autoLogin === 'true',
      'auto_join_on_invite': true,
      'auto_reconnect': true,
      'auto_register_muc_nickname': true,
      'websocket_url': this.element.dataset.websocketUrl || 'wss://jabber.happy-dev.fr/xmpp-websocket',
      'enable_smacks': true,
      "clear_messages_on_reconnection": false,
      "discover_connection_methods": false,
      'jid': jabberID.toLowerCase(),
      'i18n': this.element.dataset.i18n || 'fr',
      'loglevel': 'fatal',
      'message_archiving': 'always',
      'message_archiving_timeout': 60000,
      'muc_disable_slash_commands': true,
      'muc_hats': ['hats', 'vcard_roles', 'moderator'],
      'role_affiliation_hat_conversions': {'moderator': 'Administrateur'},
      'muc_nickname_from_jid': true,
      'muc_fetch_members': true,
      'muc_show_info_messages': [],
      'play_sounds': false,
      'root': this.element.shadowRoot,
      'show_client_info': false,
      'show_desktop_notifications': false,
      'persistent_store': 'IndexedDB',
      'sounds_path': ComponentPath + '/dist/conversejs/',
      'show_send_button': false,
      'view_mode': 'fullscreen',
      'visible_toolbar_buttons': {
        call: false,
        spoiler: false,
        emoji: true,
        fileupload: false, // Not working in current Converse
        toggle_occupants: false
      },
      'whitelisted_plugins': ['rai', 'conversejs-sib-disconnected', 'conversejs-sib-connected', 'conversejs-sib-focused', 'conversejs-changechat', 'conversejs-rai'],
    });

    converse_sib.loaded_deferred.resolve();
  }
};

Sib.register(SolidXMPPChat);