123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- import "./autocomplete.js"
- import log from "@converse/headless/log";
- import sizzle from "sizzle";
- import { CustomElement } from './element.js';
- import { __ } from '@converse/headless/i18n';
- import { api, converse } from "@converse/headless/converse-core";
- import { html } from "lit-html";
- import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
- const { Strophe, $iq } = converse.env;
- const u = converse.env.utils;
- const tpl_command_form = (o, command) => {
- const i18n_hide = __('Hide');
- const i18n_run = __('Execute');
- return html`
- <form @submit=${o.runCommand}>
- ${ command.alert ? html`<div class="alert alert-${command.alert_type}" role="alert">${command.alert}</div>` : '' }
- <fieldset class="form-group">
- <input type="hidden" name="command_node" value="${command.node}"/>
- <input type="hidden" name="command_jid" value="${command.jid}"/>
- <p class="form-help">${command.instructions}</p>
- <!-- Fields are generated internally, with xForm2webForm -->
- ${ command.fields.map(field => unsafeHTML(field)) }
- </fieldset>
- <fieldset>
- <input type="submit" class="btn btn-primary" value="${i18n_run}">
- <input type="button" class="btn btn-secondary button-cancel" value="${i18n_hide}" @click=${o.hideCommandForm}>
- </fieldset>
- </form>
- `;
- }
- const tpl_command = (o, command) => html`
- <li class="room-item list-group-item">
- <div class="available-chatroom d-flex flex-row">
- <a class="open-room available-room w-100"
- @click=${o.toggleCommandForm}
- data-command-node="${command.node}"
- data-command-jid="${command.jid}"
- data-command-name="${command.name}"
- title="${command.name}"
- href="#">${command.name || command.jid}</a>
- </div>
- ${ command.node === o.showform ? tpl_command_form(o, command) : '' }
- </li>
- `;
- async function getAutoCompleteList () {
- const models = [...(await api.rooms.get()), ...(await api.contacts.get())];
- const jids = [...new Set(models.map(o => Strophe.getDomainFromJid(o.get('jid'))))];
- return jids;
- }
- const tpl_adhoc = (o) => {
- const i18n_choose_service = __('On which entity do you want to run commands?');
- const i18n_choose_service_instructions = __(
- 'Certain XMPP services and entities allow privileged users to execute ad-hoc commands on them.');
- const i18n_commands_found = __('Commands found');
- const i18n_fetch_commands = __('List available commands');
- const i18n_jid_placeholder = __('XMPP Address');
- const i18n_no_commands_found = __('No commands found');
- return html`
- ${ o.alert ? html`<div class="alert alert-${o.alert_type}" role="alert">${o.alert}</div>` : '' }
- <form class="converse-form" @submit=${o.fetchCommands}>
- <fieldset class="form-group">
- <label>
- ${i18n_choose_service}
- <p class="form-help">${i18n_choose_service_instructions}</p>
- <converse-autocomplete
- .getAutoCompleteList="${getAutoCompleteList}"
- placeholder="${i18n_jid_placeholder}"
- name="jid"/>
- </label>
- </fieldset>
- <fieldset class="form-group">
- <input type="submit" class="btn btn-primary" value="${i18n_fetch_commands}">
- </fieldset>
- ${ o.view === 'list-commands' ? html`
- <fieldset class="form-group">
- <ul class="list-group">
- <li class="list-group-item active">${ o.commands.length ? i18n_commands_found : i18n_no_commands_found }:</li>
- ${ o.commands.map(cmd => tpl_command(o, cmd)) }
- </ul>
- </fieldset>`
- : '' }
- </form>
- `;
- }
- async function fetchCommandForm (command) {
- const node = command.node;
- const jid = command.jid;
- const stanza = $iq({
- 'type': 'set',
- 'to': jid
- }).c('command', {
- 'xmlns': Strophe.NS.ADHOC,
- 'node': node,
- 'action': 'execute'
- });
- try {
- const iq = await api.sendIQ(stanza);
- const cmd_el = sizzle(`command[xmlns="${Strophe.NS.ADHOC}"]`, iq).pop();
- command.sessionid = cmd_el.getAttribute('sessionid');
- command.instructions = sizzle('x[type="form"][xmlns="jabber:x:data"] instructions', cmd_el).pop()?.textContent;
- command.fields = sizzle('x[type="form"][xmlns="jabber:x:data"] field', cmd_el)
- .map(f => u.xForm2webForm(f, cmd_el));
- } catch (e) {
- if (e === null) {
- log.error(`Error: timeout while trying to execute command for ${jid}`);
- } else {
- log.error(`Error while trying to execute command for ${jid}`);
- log.error(e);
- }
- command.fields = [];
- }
- }
- export default class AdHocCommands extends CustomElement {
- static get properties () {
- return {
- 'alert': { type: String },
- 'alert_type': { type: String },
- 'nonce': { type: String }, // Used to force re-rendering
- 'showform': { type: String },
- 'view': { type: String },
- }
- }
- constructor () {
- super();
- this.view = 'choose-service';
- this.showform = '';
- this.commands = [];
- }
- render () {
- return tpl_adhoc({
- 'alert': this.alert,
- 'alert_type': this.alert_type,
- 'commands': this.commands,
- 'fetchCommands': ev => this.fetchCommands(ev),
- 'hideCommandForm': ev => this.hideCommandForm(ev),
- 'runCommand': ev => this.runCommand(ev),
- 'showform': this.showform,
- 'toggleCommandForm': ev => this.toggleCommandForm(ev),
- 'view': this.view,
- });
- }
- async fetchCommands (ev) {
- ev.preventDefault();
- delete this.alert_type;
- delete this.alert;
- const form_data = new FormData(ev.target);
- const jid = form_data.get('jid').trim();
- let supported;
- try {
- supported = await api.disco.supports(Strophe.NS.ADHOC, jid)
- } catch (e) {
- log.error(e);
- }
- if (supported) {
- try {
- this.commands = await api.adhoc.getCommands(jid);
- this.view = 'list-commands';
- } catch (e) {
- log.error(e);
- this.alert_type = 'danger';
- this.alert = __('Sorry, an error occurred while looking for commands on that entity.');
- this.commands = [];
- log.error(e);
- return;
- }
- } else {
- this.alert_type = 'danger';
- this.alert = __("The specified entity doesn't support ad-hoc commands");
- }
- }
- async toggleCommandForm (ev) {
- ev.preventDefault();
- const node = ev.target.getAttribute('data-command-node');
- const cmd = this.commands.filter(c => c.node === node)[0];
- this.showform !== node && await fetchCommandForm(cmd);
- this.showform = node;
- }
- hideCommandForm (ev) {
- ev.preventDefault();
- this.showform = ''
- }
- async runCommand (ev) {
- ev.preventDefault();
- const form_data = new FormData(ev.target);
- const jid = form_data.get('command_jid').trim();
- const node = form_data.get('command_node').trim();
- const cmd = this.commands.filter(c => c.node === node)[0];
- const inputs = sizzle(':input:not([type=button]):not([type=submit])', ev.target);
- const configArray = inputs
- .filter(i => !['command_jid', 'command_node'].includes(i.getAttribute('name')))
- .map(u.webForm2xForm);
- const iq = $iq({to: jid, type: "set"})
- .c("command", {
- 'sessionid': cmd.sessionid,
- 'node': cmd.node,
- 'xmlns': Strophe.NS.ADHOC
- }).c("x", {xmlns: Strophe.NS.XFORM, type: "submit"});
- configArray.forEach(node => iq.cnode(node).up());
- let result;
- try {
- result = await api.sendIQ(iq);
- } catch (e) {
- cmd.alert_type = 'danger';
- cmd.alert = __('Sorry, an error occurred while trying to execute the command. See the developer console for details');
- log.error('Error while trying to execute an ad-hoc command');
- log.error(e);
- }
- if (result) {
- cmd.alert = result.querySelector('note')?.textContent;
- } else {
- cmd.alert = 'Done';
- }
- cmd.alert_type = 'primary';
- this.nonce = u.getUniqueId();
- }
- }
- api.elements.define('converse-adhoc-commands', AdHocCommands);
|