plugin_development.rst 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. .. raw:: html
  2. <div id="banner"><a href="https://github.com/jcbrand/converse.js/blob/master/docs/source/theming.rst">Edit me on GitHub</a></div>
  3. .. _`writing-a-plugin`:
  4. Writing a plugin
  5. ================
  6. .. contents:: Table of Contents
  7. :depth: 2
  8. :local:
  9. Introduction
  10. ------------
  11. Converse.js is exposes a plugin architecture which allows developers to modify
  12. and extend its functionality.
  13. Specifically, plugins enable developers to extend and override existing objects,
  14. functions and `Backbone <http://backbonejs.org/>`_ models and views that make up
  15. Converse.js, and also give them the ability to write new models and views.
  16. Various core features of Converse.js, such as
  17. `Message Archive Management <https://xmpp.org/extensions/xep-0313.html>`_ and
  18. `Group chats <https://xmpp.org/extensions/xep-0045.html>`_ are implemented
  19. as plugins, thereby showing their power and flexibility.
  20. Converse.js uses `pluggable.js <https://github.com/jcbrand/pluggable.js/>`_ as
  21. its plugin architecture.
  22. To more deeply understand how this plugin architecture works, please read the
  23. `pluggable.js documentation <https://jcbrand.github.io/pluggable.js/>`_
  24. and to understand its inner workins, please refer to the `annotated source code
  25. <https://jcbrand.github.io/pluggable.js/docs/pluggable.html>`_.
  26. Playing with a Converse.js plugin in JSFiddle
  27. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  28. Because Converse.js consists only of JavaScript, HTML and CSS (with no backend
  29. code required like PHP, Python or Ruby) it runs fine in JSFiddle.
  30. Here's an Fiddle with a Converse.js plugin that calls `alert` once it gets
  31. initialized and also when a chat message gets rendered:
  32. https://jsfiddle.net/4drfaok0/15/
  33. Registering a plugin
  34. --------------------
  35. You register a converse.js plugin as follows:
  36. .. code-block:: javascript
  37. converse.plugins.add('myplugin', {
  38. initialize: function () {
  39. // This method gets called once converse.initialize has been called
  40. // and the plugin itself has been loaded.
  41. // Inside this method, you have access to the closured
  42. // _converse object as an attribute on "this".
  43. // E.g. this._converse
  44. },
  45. });
  46. .. note:: It's important that `converse.plugins.add` is called **before**
  47. `converse.initialize` is called. Otherwise the plugin will never get
  48. registered and never get called.
  49. Whitelisting of plugins
  50. -----------------------
  51. As of converse.js 3.0.0 and higher, plugins need to be whitelisted before they
  52. can be used. This is because plugins have access to a powerful API. For
  53. example, they can read all messages and send messages on the user's behalf.
  54. To avoid malicious plugins being registered (i.e. by malware infected
  55. advertising networks) we now require whitelisting.
  56. To whitelist a plugin simply means to specify :ref:`whitelisted_plugins` when
  57. you call ``converse.initialize``.
  58. Security and access to the inner workings
  59. -----------------------------------------
  60. The globally available ``converse`` object, which exposes the API methods, such
  61. as ``initialize`` and ``plugins.add``, is a wrapper that encloses and protects
  62. a sensitive inner object, named ``_converse`` (not the underscore prefix).
  63. This inner ``_converse`` object contains all the Backbone models and views,
  64. as well as various other attributes and functions.
  65. Within a plugin, you will have access to this internal
  66. `"closured" <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures>`_
  67. ``_converse`` object, which is normally not exposed in the global variable scope.
  68. The inner ``_converse`` object is made private in order to safely hide and
  69. encapsulate sensitive information and methods which should not be exposed
  70. to any 3rd-party scripts that might be running in the same page.
  71. Loading a plugin module
  72. -----------------------
  73. Converse.js uses the UMD (Universal Modules Definition) as its module syntax.
  74. This makes modules loadable via `require.js`, `webpack` or other module
  75. loaders, but also includable as old-school `<script>` tags in your HTML.
  76. Here's an example of the plugin shown above wrapped inside a UMD module:
  77. .. code-block:: javascript
  78. (function (root, factory) {
  79. if (typeof define === 'function' && define.amd) {
  80. // AMD. Register as a module called "myplugin"
  81. define("myplugin", ["converse"], factory);
  82. } else {
  83. // Browser globals. If you're not using a module loader such as require.js,
  84. // then this line below executes. Make sure that your plugin's <script> tag
  85. // appears after the one from converse.js.
  86. factory(converse);
  87. }
  88. }(this, function (converse) {
  89. converse.plugins.add('myplugin', {
  90. initialize: function () {
  91. // This method gets called once converse.initialize has been called
  92. // and the plugin itself has been loaded.
  93. // Inside this method, you have access to the closured
  94. // _converse object as an attribute on "this".
  95. // E.g. this._converse
  96. },
  97. });
  98. });
  99. Accessing 3rd party libraries
  100. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  101. Immediately inside the module shown above you can access 3rd party libraries (such
  102. moment, underscore and jQuery) via the ``converse.env`` map.
  103. The code for it would look something like this:
  104. .. code-block:: javascript
  105. // Commonly used utilities and variables can be found under the "env"
  106. // namespace of the "converse" global.
  107. var Strophe = converse.env.Strophe,
  108. $iq = converse.env.$iq,
  109. $msg = converse.env.$msg,
  110. $pres = converse.env.$pres,
  111. $build = converse.env.$build,
  112. b64_sha1 = converse.env.b64_sha1;
  113. $ = converse.env.jQuery,
  114. _ = converse.env._,
  115. moment = converse.env.moment;
  116. These dependencies are closured so that they don't pollute the global
  117. namespace, that's why you need to access them in such a way inside the module.
  118. Overrides
  119. ---------
  120. Plugins can override core code or code from other plugins. Refer to the full
  121. example at the bottom for code details.
  122. Use the ``overrides`` functionality with caution. It basically resorts to
  123. monkey patching which pollutes the call stack and can make your code fragile
  124. and prone to bugs when Converse.js gets updated. Too much use of ``overrides``
  125. is therefore a "code smell" which should ideally be avoided.
  126. A better approach is to listen to the events emitted by Converse.js, and to add
  127. your code in event handlers. This is however not always possible, in which case
  128. the overrides are a powerful tool.
  129. .. _`optional_dependencies`:
  130. Optional plugin dependencies
  131. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  132. When using ``overrides``, the code that you want to override (which is either
  133. in ``converse-core`` or in other plugins), needs to be loaded already by the
  134. type the ``overrides`` object is being parsed.
  135. So it's important to include overridden plugins in the AMD ``define`` statement
  136. at the top of the plugin module.
  137. However, sometimes you want to override parts of another plugin if it exists, but you
  138. don't want anything to break if it doesn't exist (for example when using a
  139. custom build which excludes that plugin). An example is the
  140. `converse-dragresize <https://github.com/jcbrand/converse.js/blob/master/src/converse-dragresize.js>`_
  141. plugin, which will add drag-resize handles to the headlines box (which shows
  142. messages of type ``headline``) but doesn't care if that particular plugin isn't
  143. actually loaded.
  144. In this case, you can't specify the plugin as a dependency in the ``define``
  145. statement at the top of the plugin, since it might not always be available,
  146. which would cause ``require.js`` to throw an error.
  147. To resolve this problem we have the ``optional_dependencies`` Array attribute.
  148. With this you can specify those dependencies which need to be loaded before
  149. your plugin, if they exist. If they don't exist, they won't be ignored.
  150. If the setting :ref:`strict_plugin_dependencies` is set to true,
  151. an error will be raised if the plugin is not found, thereby making them
  152. non-optional.
  153. Extending converse.js's configuration settings
  154. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  155. Converse.js comes with various :ref:`configuration-settings`_ that can be used to
  156. modify its functionality and behavior.
  157. All configuration settings have default values which can be overridden when
  158. `converse.initialize` (see :ref:`initialize`_) gets called.
  159. Plugins often need their own additional configuration settings and you can add
  160. these settings with the `_converse.api.settings.update` method (see
  161. :ref:`settings-update`_).
  162. Exposing promises
  163. ~~~~~~~~~~~~~~~~~
  164. Converse.js has a ``waitUntil`` API method (see :ref:`waituntil-grouping`_)
  165. which allows you to wait for various promises to resolve before executing a
  166. piece of code.
  167. You can add new promises for your plugin by calling
  168. ``_converse.api.promises.add`` (see :ref:`promises-grouping`_).
  169. Generally, your plugin will then also be responsible for making sure these
  170. promises are resolved. You do this by calling ``_converse.api.emit``, which not
  171. only resolves the plugin but will also emit an event with the same name.
  172. A full example plugin
  173. ---------------------
  174. .. code-block:: javascript
  175. (function (root, factory) {
  176. if (typeof define === 'function' && define.amd) {
  177. // AMD. Register as a module called "myplugin"
  178. define("<%= name %>", ["converse"], factory);
  179. } else {
  180. // Browser globals. If you're not using a module loader such as require.js,
  181. // then this line below executes. Make sure that your plugin's <script> tag
  182. // appears after the one from converse.js.
  183. factory(converse);
  184. }
  185. }(this, function (converse) {
  186. // Commonly used utilities and variables can be found under the "env"
  187. // namespace of the "converse" global.
  188. var Strophe = converse.env.Strophe,
  189. $iq = converse.env.$iq,
  190. $msg = converse.env.$msg,
  191. $pres = converse.env.$pres,
  192. $build = converse.env.$build,
  193. b64_sha1 = converse.env.b64_sha1;
  194. $ = converse.env.jQuery,
  195. _ = converse.env._,
  196. moment = converse.env.moment;
  197. // The following line registers your plugin.
  198. converse.plugins.add("<%= name %>", {
  199. /* Optional dependencies are other plugins which might be
  200. * overridden or relied upon, and therefore need to be loaded before
  201. * this plugin. They are called "optional" because they might not be
  202. * available, in which case any overrides applicable to them will be
  203. * ignored.
  204. *
  205. * NB: These plugins need to have already been loaded via require.js.
  206. *
  207. * It's possible to make optional dependencies non-optional.
  208. * If the setting "strict_plugin_dependencies" is set to true,
  209. * an error will be raised if the plugin is not found.
  210. */
  211. 'optional_dependencies': [],
  212. /* Converse.js's plugin mechanism will call the initialize
  213. * method on any plugin (if it exists) as soon as the plugin has
  214. * been loaded.
  215. */
  216. 'initialize': function () {
  217. /* Inside this method, you have access to the private
  218. * `_converse` object.
  219. */
  220. var _converse = this._converse;
  221. _converse.log("The <%= name %> plugin is being initialized");
  222. /* From the `_converse` object you can get any configuration
  223. * options that the user might have passed in via
  224. * `converse.initialize`. These values are stored in the
  225. * "user_settings" attribute.
  226. *
  227. * You can also specify new configuration settings for this
  228. * plugin, or override the default values of existing
  229. * configuration settings. This is done like so:
  230. */
  231. _converse.api.settings.update({
  232. 'initialize_message': 'Initializing <%= name %>!'
  233. });
  234. /* The user can then pass in values for the configuration
  235. * settings when `converse.initialize` gets called.
  236. * For example:
  237. *
  238. * converse.initialize({
  239. * "initialize_message": "My plugin has been initialized"
  240. * });
  241. *
  242. * And the configuration setting is then available via the
  243. * `user_settings` attribute:
  244. */
  245. alert(this._converse.user_settings.initialize_message);
  246. /* Besides `_converse.api.settings.update`, there is also a
  247. * `_converse.api.promises.add` method, which allows you to
  248. * add new promises that your plugin is obligated to fulfill.
  249. *
  250. * This method takes a string or a list of strings which
  251. * represent the promise names:
  252. *
  253. * _converse.api.promises.add('myPromise');
  254. *
  255. * Your plugin should then, when appropriate, resolve the
  256. * promise by calling `_converse.api.emit`, which will also
  257. * emit an event with the same name as the promise.
  258. * For example:
  259. *
  260. * _converse.api.emit('operationCompleted');
  261. *
  262. * Other plugins can then either listen for the event
  263. * `operationCompleted` like so:
  264. *
  265. * _converse.api.listen.on('operationCompleted', function { ... });
  266. *
  267. * or they can wait for the promise to be fulfilled like so:
  268. *
  269. * _converse.api.waitUntil('operationCompleted', function { ... });
  270. */
  271. },
  272. /* If you want to override some function or a Backbone model or
  273. * view defined elsewhere in converse.js, then you do that under
  274. * the "overrides" namespace.
  275. */
  276. 'overrides': {
  277. /* For example, the private *_converse* object has a
  278. * method "onConnected". You can override that method as follows:
  279. */
  280. 'onConnected': function () {
  281. // Overrides the onConnected method in converse.js
  282. // Top-level functions in "overrides" are bound to the
  283. // inner "_converse" object.
  284. var _converse = this;
  285. // Your custom code can come here ...
  286. // You can access the original function being overridden
  287. // via the __super__ attribute.
  288. // Make sure to pass on the arguments supplied to this
  289. // function and also to apply the proper "this" object.
  290. _converse.__super__.onConnected.apply(this, arguments);
  291. // Your custom code can come here ...
  292. },
  293. /* Override converse.js's XMPPStatus Backbone model so that we can override the
  294. * function that sends out the presence stanza.
  295. */
  296. 'XMPPStatus': {
  297. 'sendPresence': function (type, status_message, jid) {
  298. // The "_converse" object is available via the __super__
  299. // attribute.
  300. var _converse = this.__super__._converse;
  301. // Custom code can come here ...
  302. // You can call the original overridden method, by
  303. // accessing it via the __super__ attribute.
  304. // When calling it, you need to apply the proper
  305. // context as reference by the "this" variable.
  306. this.__super__.sendPresence.apply(this, arguments);
  307. // Custom code can come here ...
  308. }
  309. }
  310. }
  311. });
  312. }));