plugin_development.rst 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. .. raw:: html
  2. <div id="banner"><a href="https://github.com/jcbrand/converse.js/blob/master/docs/source/plugin_development.rst">Edit me on GitHub</a></div>
  3. .. _`writing-a-plugin`:
  4. Writing a plugin
  5. ================
  6. Introduction
  7. ------------
  8. Converse.js has a plugin architecture based on `pluggable.js <https://github.com/jcbrand/pluggable.js/>`_
  9. and is itself composed out of plugins.
  10. There are only a few files that are included in the default build of Converse
  11. which aren't plugins.
  12. An important one is `core.js <https://github.com/conversejs/.js/blob/master/src/headless/core.js>`_,
  13. which is responsible for bootstrapping the plugin architecture,
  14. setting up and maintaining the connection to the XMPP
  15. server and declaring the public (`window.converse </docs/html/api/converse.html>`_) and protected (`_converse.api </docs/html/api/-_converse.api.html>`_) APIs.
  16. The other non-plugin files all contain utility methods in
  17. `src/utils <https://github.com/conversejs/converse.js/blob/master/src/utils>`_ and
  18. `src/headless/utils <https://github.com/conversejs/converse.js/blob/master/src/headless/utils>`_.
  19. As a general rule, any file in the ``./src`` directory that starts with
  20. ``converse-`` is a plugin (with the exception of ``converse-core.js``.
  21. The plugin architecture lets you add new features or modify existing functionality in a
  22. modular and self-contained way, without having to change other files.
  23. This ensures that plugins are fully optional (one of the design goals of
  24. Converse) and can be removed from the main build without breaking the app.
  25. For example, the ``converse-omemo``,
  26. ``converse-rosterview``, ``converse-dragresize``, ``converse-minimize``,
  27. ``converse-muc`` and ``converse-muc-views`` plugins can all be removed from the
  28. build without breaking the app.
  29. To more deeply understand how the plugin architecture works, read the
  30. `pluggable.js documentation <https://jcbrand.github.io/pluggable.js/>`_
  31. and to understand its inner workings, refer to the `annotated source code
  32. <https://jcbrand.github.io/pluggable.js/docs/pluggable.html>`_.
  33. Advantages of plugins
  34. ---------------------
  35. Writing a plugin is the recommended way to customize or add new features to Converse.
  36. The main benefit of plugins is that they allow for **isolation of concerns** (and features).
  37. From this benefit flows various 2nd order advantages, such as the ability to
  38. make smaller production builds (by excluding unused plugins) and an easier
  39. upgrade path by avoiding touching Converse's internals.
  40. Plugins are especially useful if you want to add proprietary modifications, since the
  41. Mozilla Public License version 2 doesn't require you to open source your
  42. plugin files. Be aware that this doesn't apply when you intend to use libsignal for
  43. OMEMO encryption because libsignal's license is GPLv3 (and turns the entire app
  44. into a GPLv3 app).
  45. Each plugin comes in its own file, and Converse's plugin architecture,
  46. `pluggable.js <https://github.com/jcbrand/pluggable.js/>`_, provides you
  47. with the ability to "hook in" to the core code and other plugins.
  48. Plugins enable developers to extend and override existing objects,
  49. functions and the models and views that make up
  50. Converse. You can also create new models and views.
  51. .. note:: **Trying out a plugin in JSFiddle**
  52. Because Converse consists only of JavaScript, HTML and CSS (with no backend
  53. code required like PHP, Python or Ruby) it runs fine in JSFiddle.
  54. Here's a Fiddle with a Converse plugin that calls ``alert`` once it gets
  55. initialized and also when a chat message gets rendered: https://jsfiddle.net/4drfaok0/15/
  56. .. note:: **Generating a plugin with Yeoman**
  57. The rest of this document explains how to write a plugin for Converse and
  58. ends with a documented example of a plugin.
  59. There is a `Yeoman <http://yeoman.io/>`_ code generator, called
  60. `generator-conversejs <https://github.com/jcbrand/generator-conversejs>`_, which
  61. you can use to generate plugin scaffolding/boilerplate that serves as a
  62. starting point and basis for writing your plugin.
  63. Please refer to the `generator-conversejs <https://github.com/jcbrand/generator-conversejs>`_
  64. README for information on how to use it.
  65. Registering a plugin
  66. --------------------
  67. Plugins need to be registered (and whitelisted) before they can be loaded and
  68. initialized.
  69. You register a Converse plugin by calling ``converse.plugins.add``.
  70. The plugin itself is a JavaScript object which usually has at least an
  71. ``initialize`` method, which gets called at the end of the
  72. ``converse.initialize`` method which is the top-level method that gets called
  73. by the website to configure and initialize Converse itself.
  74. Here's an example code snippet:
  75. .. code-block:: javascript
  76. converse.plugins.add('myplugin', {
  77. initialize: function () {
  78. // This method gets called once converse.initialize has been called
  79. // and the plugin itself has been loaded.
  80. // Inside this method, you have access to the closured
  81. // _converse object as an attribute on "this".
  82. // E.g. this._converse
  83. },
  84. });
  85. .. note:: It's important that `converse.plugins.add` is called **before**
  86. `converse.initialize` is called. Otherwise the plugin will never get
  87. registered and never get called.
  88. Whitelisting of plugins
  89. -----------------------
  90. As of Converse 3.0.0 and higher, plugins need to be whitelisted before they
  91. can be used. This is because plugins have access to a powerful API. For
  92. example, they can read all messages and send messages on the user's behalf.
  93. To avoid malicious plugins being registered (i.e. by malware infected
  94. advertising networks) we now require whitelisting.
  95. To whitelist a plugin simply means to specify :ref:`whitelisted_plugins` when
  96. you call ``converse.initialize``.
  97. If you're adding a "core" plugin, which means a plugin that will be
  98. included in the default, open-source version of Converse, then you'll
  99. instead whitelist the plugin by adding its name to the `core_plugins` array in
  100. `./src/headless/converse-core.js <https://github.com/jcbrand/converse.js/blob/master/src/headless/converse-core.js>`_.
  101. or the `WHITELISTED_PLUGINS` array in `./src/converse.js <https://github.com/jcbrand/converse.js/blob/master/src/converse.js>`_.
  102. Where you add it depends on whether your plugin is part of the headless build
  103. (which means it doesn't contain any view code) or not.
  104. Security and access to the inner workings
  105. -----------------------------------------
  106. The globally available ``converse`` object, which exposes the API methods, such
  107. as ``initialize`` and ``plugins.add``, is a wrapper that encloses and protects
  108. a sensitive inner object, named ``_converse`` (not the underscore prefix).
  109. This inner ``_converse`` object contains all the models and views,
  110. as well as various other attributes and functions.
  111. Within a plugin, you will have access to this internal
  112. `"closured" <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures>`_
  113. ``_converse`` object, which is normally not exposed in the global variable scope.
  114. The inner ``_converse`` object is made private in order to safely hide and
  115. encapsulate sensitive information and methods which should not be exposed
  116. to any 3rd-party scripts that might be running in the same page.
  117. Accessing 3rd party libraries
  118. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  119. Immediately inside the module shown above you can access 3rd party libraries (such
  120. dayjs) via the ``converse.env`` map.
  121. The code for it could look something like this:
  122. .. code-block:: javascript
  123. // Commonly used utilities and variables can be found under the "env"
  124. // namespace of the "converse" global.
  125. const { Promise, Strophe, dayjs, sizzle, $build, $iq, $msg, $pres } = converse.env;
  126. These dependencies are closured so that they don't pollute the global
  127. namespace, that's why you need to access them in such a way inside the module.
  128. Overriding templates
  129. --------------------
  130. Converse uses `lit-html <https://lit-html.polymer-project.org/guide>`_
  131. templates and templates are imported as separate files.
  132. It's possible to configure your module bundler (e.g. Webpack) in such as way that a
  133. different file is loaded when a template is imported.
  134. This allows you to create your own templates that are used instead of the ones
  135. that would have originally been imported.
  136. With Webpack (which Converse uses internally), you can specify an
  137. ``alias`` for the template you want to override. This alias then points to your
  138. own custom template.
  139. For example, in your webpack config file, you could add the following to the
  140. ``config`` object that gets exported:
  141. .. code-block:: javascript
  142. resolve: {
  143. extensions: ['.js'],
  144. modules: [
  145. path.join(__dirname, 'node_modules'),
  146. path.join(__dirname, 'node_modules/converse.js/src')
  147. ],
  148. alias: {
  149. 'plugins/profile/templates/profile.js$': path.resolve(__dirname, 'templates/custom-profile.js')
  150. }
  151. }
  152. This will override the template that gets imported at the path ``plugins/profile/templates/profile.js``
  153. with your own template at the path ``templates/custom-profile.js`` (relative to your webpack config file).
  154. Overriding templates
  155. --------------------
  156. Converse defines many custom elements.
  157. These elements are derived from the ``CustomElement`` class, and decared using ``api.elements.define``.
  158. You can redefine any custom element just by calling ``api.elements.define`` again in your plugin ``initialize`` method.
  159. If you want to extend the custom element class, you can get it using ``api.elements.registry``.
  160. Here is an example:
  161. .. code-block:: javascript
  162. // You can get the lit html function in the converse env:
  163. const html = converse.env.html;
  164. converse.plugins.add('myplugin', {
  165. initialize: function () {
  166. const _converse = this._converse;
  167. // to get the base class of the `converse-chat-message` custom element:
  168. const Message = _converse.api.elements.registry['converse-chat-message'];
  169. // Now you can override it:
  170. class MyMessage extends Message {
  171. // Override the render method:
  172. render () {
  173. return html`MyPlugin was here - ${super.render()}`
  174. }
  175. }
  176. // And now, just redefined the custom element:
  177. _converse.api.elements.define('converse-chat-message', MyMessage)
  178. },
  179. });
  180. Object and class Overrides
  181. --------------------------
  182. .. note:: Using the `overrides` feature from pluggable.js is discouraged. It's
  183. much better to use events, promises and `hooks`_ to modify the behaviour of
  184. Converse.
  185. The pluggable.js `overrides` will only work on objects and classes that are
  186. set as attributes on the `_converse` object, which doesn't apply to many
  187. newer classes and objects, such as the web components. For these clasess,
  188. overrides won't work at all.
  189. This section is left here for completeness, because in some special cases
  190. overrides are still used.
  191. Plugins can override core code or code from other plugins. You can specify
  192. overrides in the object passed to ``converse.plugins.add``.
  193. In an override you can still call the overridden function, by calling
  194. ``this.__super__.methodName.apply(this, arguments);`` where ``methodName`` is
  195. the name of the function or method you're overriding.
  196. The following code snippet provides an example of two different overrides:
  197. .. code-block:: javascript
  198. overrides: {
  199. /* The *_converse* object has a method "onConnected".
  200. * You can override that method as follows:
  201. */
  202. onConnected: function () {
  203. // Overrides the onConnected method in Converse
  204. // Top-level functions in "overrides" are bound to the
  205. // inner "_converse" object.
  206. const _converse = this;
  207. // Your custom code can come here ...
  208. // You can access the original function being overridden
  209. // via the __super__ attribute.
  210. // Make sure to pass on the arguments supplied to this
  211. // function and also to apply the proper "this" object.
  212. _converse.__super__.onConnected.apply(this, arguments);
  213. // Your custom code can come here ...
  214. },
  215. /* On the XMPPStatus model is a method sendPresence.
  216. * We can override is as follows:
  217. */
  218. XMPPStatus: {
  219. sendPresence: function (type, status_message, jid) {
  220. // The "_converse" object is available via the __super__
  221. // attribute.
  222. const _converse = this.__super__._converse;
  223. // Custom code can come here ...
  224. // You can call the original overridden method, by
  225. // accessing it via the __super__ attribute.
  226. // When calling it, you need to apply the proper
  227. // context as reference by the "this" variable.
  228. this.__super__.sendPresence.apply(this, arguments);
  229. }
  230. }
  231. }
  232. Use the ``overrides`` feature with caution. It basically resorts to
  233. monkey patching which pollutes the call stack and can make your code fragile
  234. and prone to bugs when Converse gets updated. Too much use of ``overrides``
  235. is therefore a "code smell" which should ideally be avoided.
  236. A better approach is to use the events and `hooks`_ emitted by Converse, and to add
  237. your code in event handlers. This is however not always possible, in which case
  238. the overrides are a powerful tool.
  239. Also, while it's possible to add new methods to classes via the ``overrides``
  240. feature, it's better and more explicit to use composition with
  241. ``Object.assign``.
  242. For example:
  243. .. code-block:: javascript
  244. function doSomething () {
  245. // Your code comes here
  246. }
  247. Object.assign(_converse.ChatBoxView.prototype, { doSomething });
  248. .. _`dependencies`:
  249. Plugin dependencies
  250. -------------------
  251. When using ``overrides``, the code that you want to override (which is either
  252. in ``converse-core`` or in other plugins), needs to be parsed already by the
  253. time your ``overrides`` are being parsed.
  254. Additionally, when you register event or promise handlers in your plugin for
  255. events/promises that fire in other plugins, then you want those plugins to have
  256. been loaded before your plugin gets loaded.
  257. To resolve this problem we have the ``dependencies`` Array attribute.
  258. With this you can specify those dependencies which need to be loaded before
  259. your plugin is loaded.
  260. In some cases, you might want to depend on another plugin if it's available,
  261. but don't care when it's not available.
  262. An example is the `converse-dragresize <https://github.com/jcbrand/converse.js/blob/master/src/converse-dragresize.js>`_
  263. plugin, which will add drag-resize handles to the headlines box (which shows
  264. messages of type ``headline``) but doesn't care when that particular plugin is
  265. not available.
  266. If the :ref:`strict_plugin_dependencies` setting is set to ``false`` (which is
  267. its default value), then no error will be raised if the plugin is not found.
  268. In this case, you can't specify the plugin as a dependency in the ``define``
  269. statement at the top of the plugin, since it might not always be available,
  270. which would cause ``require.js`` to throw an error.
  271. Extending Converse's configuration settings
  272. ----------------------------------------------
  273. Converse comes with various :ref:`configuration-settings` that can be used to
  274. modify its functionality and behavior.
  275. All configuration settings have default values which can be overridden when
  276. `converse.initialize` (see `converse.initialize </docs/html/api/converse.html#.initialize>`_)
  277. gets called.
  278. Plugins often need their own additional configuration settings and you can add
  279. these settings with the `_converse.api.settings.extend </docs/html/api/-_converse.api.settings.html#.extend>`_
  280. method.
  281. Exposing promises
  282. -----------------
  283. Converse has a `waitUntil </docs/html/api/-_converse.api.html#.waitUntil>`_ API method
  284. which allows you to wait for various promises to resolve before executing a
  285. piece of code.
  286. You can add new promises for your plugin by calling
  287. `_converse.api.promises.add </docs/html/api/-_converse.api.promises.html#.add>`_.
  288. Generally, your plugin will then also be responsible for making sure these
  289. promises are resolved. You do this by calling
  290. `_converse.api.trigger </docs/html/api/-_converse.api.html#.trigger>`_, which not
  291. only resolves the plugin but will also emit an event with the same name.
  292. Dealing with asynchronicity
  293. ---------------------------
  294. Due to the asynchronous nature of XMPP, many subroutines in Converse execute
  295. at different times and not necessarily in the same order.
  296. In many cases, when you want to execute a piece of code in a plugin, you first
  297. want to make sure that the supporting data-structures that your code might rely
  298. on have been created and populated with data.
  299. There are two ways of waiting for the right time before executing your code.
  300. You can either listen for certain events, or you can wait for promises to
  301. resolve.
  302. For example, when you want to query the message archive between you and a
  303. friend, you would call ``this._converse.api.archive.query({'with': 'friend@example.org'});``
  304. However, simply calling this immediately in the ``initialize`` method of your plugin will
  305. not work, since the user is not logged in yet.
  306. In this case, you should first listen for the ``connection`` event, and then do your query, like so:
  307. .. code-block:: javascript
  308. converse.plugins.add('myplugin', {
  309. initialize: function () {
  310. const _converse = this._converse;
  311. _converse.api.listen.on('connected', function () {
  312. _converse.api.archive.query({'with': 'admin2@localhost'});
  313. });
  314. }
  315. });
  316. Another example is in the ``Bookmarks`` plugin (in
  317. `src/converse-bookmarks.js <https://github.com/jcbrand/converse.js/blob/6c3aa34c23d97d679823a64376418cd0f40a8b94/src/converse-bookmarks.js#L528>`_).
  318. Before bookmarks can be fetched and shown to the user, we first have to wait until
  319. the `"Rooms"` panel of the ``ControlBox`` has been rendered and inserted into
  320. the DOM. Otherwise we have no place to show the bookmarks yet.
  321. Therefore, there are the following lines of code in the ``initialize`` method of
  322. `converse-bookmarks.js <https://github.com/jcbrand/converse.js/blob/6c3aa34c23d97d679823a64376418cd0f40a8b94/src/converse-bookmarks.js#L528>`_:
  323. .. code-block:: javascript
  324. Promise.all([
  325. _converse.api.waitUntil('chatBoxesFetched'),
  326. _converse.api.waitUntil('roomsPanelRendered')
  327. ]).then(initBookmarks);
  328. What this means, is that the plugin will wait until the ``chatBoxesFetched``
  329. and ``roomsPanelRendered`` promises have been resolved before it calls the
  330. ``initBookmarks`` method (which is defined inside the plugin).
  331. This way, we know that we have everything in place and set up correctly before
  332. fetching the bookmarks.
  333. As yet another example, there is also the following code in the ``initialize``
  334. method of the plugin:
  335. .. code-block:: javascript
  336. _converse.api.listen.on('chatBoxOpened', function renderMinimizeButton (view) {
  337. // Inserts a "minimize" button in the chatview's header
  338. // Implementation code removed for brevity
  339. // ...
  340. });
  341. In this case, the plugin waits for the ``chatBoxOpened`` event, before it then
  342. calls ``renderMinimizeButton``, which adds a new button to the chatbox (which
  343. enables you to minimize it).
  344. Finding the right promises and/or events to listen to, can be a bit
  345. challenging, and sometimes it might be necessary to create new events or
  346. promises.
  347. Please refer to the `API documentation </docs/html/api/http://localhost:8008/docs/html/api/>`_
  348. for an overview of what's available to you. If you need new events or promises, then
  349. `please open an issue or make a pull request on Github <https://github.com/jcbrand/converse.js>`_
  350. Hooks
  351. -----
  352. Converse has the concept of ``hooks``, which are special events that allow you
  353. to modify behaviour at runtime.
  354. A hook is similar to an event, but it differs in two meaningful ways:
  355. 1. Converse will wait for all handlers of a hook to finish before continuing inside the function from where the hook was triggered.
  356. 2. Each hook contains a payload, which the handlers can modify or extend, before returning it
  357. (either to the function that triggered the hook or to subsequent handlers).
  358. These two properties of hooks makes it possible for 3rd party plugins to
  359. intercept and update data, allowing them to modify Converse without the need of
  360. resorting to `overrides`_.
  361. A hook is triggered in the following way:
  362. .. code-block:: javascript
  363. async function hookTriggerExample () {
  364. const payload = { foo: 'bar' };
  365. const updated_payload = await api.hook('hookName', this, payload);
  366. return updated_payload;
  367. }
  368. The above could be shortened:
  369. .. code-block:: javascript
  370. async function hookTriggerExample () {
  371. return await api.hook('hookName', this, { foo: 'bar' });
  372. }
  373. The ``async/await`` syntax could also be removed, but then it's less clear to
  374. the reader that this function returns a promise.
  375. Let's assume that in a plugin somewhere a listener is registered for this hook:
  376. .. code-block:: javascript
  377. function hookListenerExample () {
  378. api.listen.on('hookname', (context, payload) => {
  379. return {...payload, 'baz': 'buzz'};
  380. });
  381. }
  382. The ``context`` parameter in our listener is usually the ``this`` of the function
  383. that triggered the hook (as is the case in ``hookTriggerExample``),
  384. but could also be a ``Model`` instance.
  385. The ``payload`` parameter is the same payload that was passed in in
  386. ``hookTriggerExample``.
  387. The ``hookListenerExample`` function accepts the payload and returns a new one
  388. which contains the original payload together with a new key and value.
  389. The ``updated_payload`` that is now returned from ``hookTriggerExample`` looks
  390. as follows:
  391. ::
  392. { foo: 'bar', bazz: 'buzz' }
  393. Our plugin was able to add data to the payload without requiring any kind of
  394. knowledge from ``hookTriggerExample`` about ``hookListenerExample`` and
  395. without any kind of coupling betwee the code.
  396. A good example of a real-world hook in Converse, is the
  397. `getMessageActionButtons <https://conversejs.org/docs/html/api/-_converse.html#event:getMessageActionButtons>`_
  398. which allows you to add, modify or delete the actions you can take on a message
  399. in a chat.
  400. The `Actions <https://github.com/conversejs/community-plugins/tree/master/packages/actions>`_
  401. 3rd party community plugin makes use of this hook to add extra actions such as
  402. ``like`` or ``dislike`` to chat messages.
  403. An example plugin
  404. -----------------
  405. Below follows a documented example of a plugin. This is the same code that gets
  406. generated by `generator-conversejs <https://github.com/jcbrand/generator-conversejs>`_.
  407. .. code-block:: javascript
  408. import converse from "@converse/headless/converse-core";
  409. // Commonly used utilities and variables can be found under the "env"
  410. // namespace of the "converse" global.
  411. const { Promise, Strophe, dayjs, sizzle, _, $build, $iq, $msg, $pres } = converse.env;
  412. // The following line registers your plugin.
  413. converse.plugins.add("myplugin", {
  414. /* Dependencies are other plugins which might be
  415. * overridden or relied upon, and therefore need to be loaded before
  416. * this plugin. They are "optional" because they might not be
  417. * available, in which case any overrides applicable to them will be
  418. * ignored.
  419. *
  420. * NB: These plugins need to have already been imported or loaded,
  421. * either in your plugin or somewhere else.
  422. *
  423. * It's possible to make these dependencies "non-optional".
  424. * If the setting "strict_plugin_dependencies" is set to true,
  425. * an error will be raised if the plugin is not found.
  426. */
  427. dependencies: [],
  428. /* Converse's plugin mechanism will call the initialize
  429. * method on any plugin (if it exists) as soon as the plugin has
  430. * been loaded.
  431. */
  432. initialize: function () {
  433. /* Inside this method, you have access to the private
  434. * `_converse` object.
  435. */
  436. const _converse = this._converse;
  437. _converse.log("The \"myplugin\" plugin is being initialized");
  438. /* From the `_converse` object you can get any configuration
  439. * options that the user might have passed in via
  440. * `converse.initialize`.
  441. *
  442. * You can also specify new configuration settings for this
  443. * plugin, or override the default values of existing
  444. * configuration settings. This is done like so:
  445. */
  446. _converse.api.settings.update({
  447. 'initialize_message': 'Initializing myplugin!'
  448. });
  449. /* The user can then pass in values for the configuration
  450. * settings when `converse.initialize` gets called.
  451. * For example:
  452. *
  453. * converse.initialize({
  454. * "initialize_message": "My plugin has been initialized"
  455. * });
  456. */
  457. alert(this._converse.initialize_message);
  458. /* Besides `_converse.api.settings.update`, there is also a
  459. * `_converse.api.promises.add` method, which allows you to
  460. * add new promises that your plugin is obligated to fulfill.
  461. *
  462. * This method takes a string or a list of strings which
  463. * represent the promise names:
  464. *
  465. * _converse.api.promises.add('myPromise');
  466. *
  467. * Your plugin should then, when appropriate, resolve the
  468. * promise by calling `_converse.api.trigger`, which will also
  469. * trigger an event with the same name as the promise.
  470. * For example:
  471. *
  472. * _converse.api.trigger('operationCompleted');
  473. *
  474. * Other plugins can then either listen for the event
  475. * `operationCompleted` like so:
  476. *
  477. * _converse.api.listen.on('operationCompleted', function { ... });
  478. *
  479. * or they can wait for the promise to be fulfilled like so:
  480. *
  481. * _converse.api.waitUntil('operationCompleted', function { ... });
  482. */
  483. /* In your plugin, you can also listen for hooks.
  484. * Hooks allow you to add or modify data and properties used by
  485. * Converse.
  486. *
  487. * For example, the getToolbarButtons hook allows you to add new buttons to the chat toolbar.
  488. * https://conversejs.org/docs/html/api/-_converse.html#event:getToolbarButtons
  489. */
  490. api.listen.on('getToolbarButtons', (toolbar_el, buttons) => {
  491. buttons.push(html`
  492. <button class="my-button" @click=${alert('hello world!')}>
  493. <converse-icon class="fa fa-eye" size="1em" color="blue"></converse-icon>
  494. </button>
  495. `);
  496. return buttons;
  497. });
  498. }
  499. });