plugin_development.rst 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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. Developers are able to extend and override the objects, functions and the
  12. Backbone models and views that make up converse.js by means of writing plugins.
  13. Converse.js uses `pluggable.js <https://github.com/jcbrand/pluggable.js/>`_ as
  14. its plugin architecture.
  15. To understand how this plugin architecture works, please read the
  16. `pluggable.js documentation <https://jcbrand.github.io/pluggable.js/>`_
  17. and to understand its inner workins, please refer to the `annotated source code
  18. <https://jcbrand.github.io/pluggable.js/docs/pluggable.html>`_.
  19. Below you'll find an example plugin. Because convers.js is only Javascript,
  20. HTML and CSS (with no backend code required like PHP, Python or Ruby) it runs
  21. fine in JSFiddle.
  22. Here's an Fiddle with a plugin that calls `alert` when the plugin gets
  23. initialized and when a message gets rendered: https://jsfiddle.net/4drfaok0/15/
  24. Registering a plugin
  25. --------------------
  26. You register a converse.js plugin as follows:
  27. .. code-block:: javascript
  28. converse.plugins.add('myplugin', {
  29. initialize: function () {
  30. // This method gets called once converse.initialize has been called
  31. // and the plugin itself has been loaded.
  32. // Inside this method, you have access to the closured
  33. // _converse object as an attribute on "this".
  34. // E.g. this._converse
  35. },
  36. });
  37. .. note:: It's important that `converse.plugins.add` is called **before**
  38. `converse.initialize` is called. Otherwise the plugin will never get
  39. registered and never get called.
  40. Whitelisting of plugins
  41. -----------------------
  42. As of converse.js 3.0.0 and higher, plugins need to be whitelisted before they
  43. can be used. This is because plugins have access to a powerful API. For
  44. example, they can read all messages and send messages on the user's behalf.
  45. To avoid malicious plugins being registered (i.e. by malware infected
  46. advertising networks) we now require whitelisting.
  47. To whitelist a plugin simply means to specify :ref:`whitelisted_plugins` when
  48. you call ``converse.initialize``.
  49. Security and access to the inner workings
  50. -----------------------------------------
  51. The globally available ``converse`` object, which exposes the API methods, such
  52. as ``initialize`` and ``plugins.add``, is a wrapper that encloses and protects
  53. a sensitive inner object, named ``_converse`` (not the underscore prefix).
  54. This inner ``_converse`` object contains all the Backbone models and views,
  55. as well as various other attributes and functions.
  56. Within a plugin, you will have access to this internal
  57. `"closured" <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures>`_
  58. ``_converse`` object, which is normally not exposed in the global variable scope.
  59. The inner ``_converse`` object is made private in order to safely hide and
  60. encapsulate sensitive information and methods which should not be exposed
  61. to any 3rd-party scripts that might be running in the same page.
  62. Loading a plugin module
  63. -----------------------
  64. Converse.js uses the UMD (Universal Modules Definition) as its module syntax.
  65. This makes modules loadable via `require.js`, `webpack` or other module
  66. loaders, but also includable as old-school `<script>` tags in your HTML.
  67. Here's an example of the plugin shown above wrapped inside a UMD module:
  68. .. code-block:: javascript
  69. (function (root, factory) {
  70. if (typeof define === 'function' && define.amd) {
  71. // AMD. Register as a module called "myplugin"
  72. define("myplugin", ["converse"], factory);
  73. } else {
  74. // Browser globals. If you're not using a module loader such as require.js,
  75. // then this line below executes. Make sure that your plugin's <script> tag
  76. // appears after the one from converse.js.
  77. factory(converse);
  78. }
  79. }(this, function (converse) {
  80. converse.plugins.add('myplugin', {
  81. initialize: function () {
  82. // This method gets called once converse.initialize has been called
  83. // and the plugin itself has been loaded.
  84. // Inside this method, you have access to the closured
  85. // _converse object as an attribute on "this".
  86. // E.g. this._converse
  87. },
  88. });
  89. });
  90. Accessing 3rd party libraries
  91. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  92. Immediately inside the module shown above you can access 3rd party libraries (such
  93. moment, underscore and jQuery) via the ``converse.env`` map.
  94. The code for it would look something like this:
  95. .. code-block:: javascript
  96. // Commonly used utilities and variables can be found under the "env"
  97. // namespace of the "converse" global.
  98. var Strophe = converse.env.Strophe,
  99. $iq = converse.env.$iq,
  100. $msg = converse.env.$msg,
  101. $pres = converse.env.$pres,
  102. $build = converse.env.$build,
  103. b64_sha1 = converse.env.b64_sha1;
  104. $ = converse.env.jQuery,
  105. _ = converse.env._,
  106. moment = converse.env.moment;
  107. These dependencies are closured so that they don't pollute the global
  108. namespace, that's why you need to access them in such a way inside the module.
  109. Overrides
  110. ---------
  111. Plugins can override core code or code from other plugins. Refer to the full
  112. example at the bottom for code details.
  113. Use the ``overrides`` functionality with caution. It basically resorts to
  114. monkey patching which pollutes the call stack and can make your code fragile
  115. and prone to bugs when Converse.js gets updated. Too much use of ``overrides``
  116. is therefore a "code smell" which should ideally be avoided.
  117. A better approach is to listen to the events emitted by Converse.js, and to add
  118. your code in event handlers. This is however not always possible, in which case
  119. the overrides are a powerful tool.
  120. Optional plugin dependencies
  121. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  122. When using ``overrides``, the code that you want to override (which is either
  123. in ``converse-core`` or in other plugins), needs to be loaded already by the
  124. type the ``overrides`` object is being parsed.
  125. So it's important to include overridden plugins in the AMD ``define`` statement
  126. at the top of the plugin module.
  127. However, sometimes you want to override parts of another plugin if it exists, but you
  128. don't want anything to break if it doesn't exist (for example when using a
  129. custom build which excludes that plugin). An example is the
  130. `converse-dragresize <https://github.com/jcbrand/converse.js/blob/master/src/converse-dragresize.js>`_
  131. plugin, which will add drag-resize handles to the headlines box (which shows
  132. messages of type ``headline``) but doesn't care if that particular plugin isn't
  133. actually loaded.
  134. In this case, you can't specify the plugin as a dependency in the ``define``
  135. statement at the top of the plugin, since it might not always be available,
  136. which would cause ``require.js`` to throw an error.
  137. To resolve this problem we thave the ``optional_dependencies`` Array attribute.
  138. With this you can specify those dependencies which need to be loaded before
  139. your plugin, if they exist. If they don't exist, they won't be ignored.
  140. If the setting :ref:`strict_plugin_dependencies` is set to true,
  141. an error will be raised if the plugin is not found, thereby making them
  142. non-optional.
  143. A full example plugin
  144. ---------------------
  145. .. code-block:: javascript
  146. (function (root, factory) {
  147. if (typeof define === 'function' && define.amd) {
  148. // AMD. Register as a module called "myplugin"
  149. define("myplugin", ["converse"], factory);
  150. } else {
  151. // Browser globals. If you're not using a module loader such as require.js,
  152. // then this line below executes. Make sure that your plugin's <script> tag
  153. // appears after the one from converse.js.
  154. factory(converse);
  155. }
  156. }(this, function (converse) {
  157. // Commonly used utilities and variables can be found under the "env"
  158. // namespace of the "converse" global.
  159. var Strophe = converse.env.Strophe,
  160. $iq = converse.env.$iq,
  161. $msg = converse.env.$msg,
  162. $pres = converse.env.$pres,
  163. $build = converse.env.$build,
  164. b64_sha1 = converse.env.b64_sha1;
  165. $ = converse.env.jQuery,
  166. _ = converse.env._,
  167. moment = converse.env.moment;
  168. // The following line registers your plugin.
  169. converse.plugins.add('myplugin', {
  170. initialize: function () {
  171. // Converse.js's plugin mechanism will call the initialize
  172. // method on any plugin (if it exists) as soon as the plugin has
  173. // been loaded.
  174. // Inside this method, you have access to the closured
  175. // _converse object, from which you can get any configuration
  176. // options that the user might have passed in via
  177. // converse.initialize. These values are stored in the
  178. // "user_settings" attribute.
  179. // Let's assume the user might pass in a custom setting, like so:
  180. //
  181. // converse.initialize({
  182. // "initialize_message": "My plugin has been initialized"
  183. // });
  184. //
  185. // Then we can alert that message, like so:
  186. alert(this._converse.user_settings.initialize_message);
  187. },
  188. // Optional dependencies are other plugins which might be
  189. // overridden or relied upon, and therefore need to be loaded before
  190. // this plugin. They are called "optional" because they might not be
  191. // available, in which case any overrides applicable to them will be
  192. // ignored.
  193. // It's possible however to make optional dependencies non-optional.
  194. // If the setting "strict_plugin_dependencies" is set to true,
  195. // an error will be raised if the plugin is not found.
  196. //
  197. // NB: These plugins need to have already been loaded via require.js.
  198. optional_dependencies: [],
  199. overrides: {
  200. // If you want to override some function or a Backbone model or
  201. // view defined elsewhere in converse.js, then you do that under
  202. // this "overrides" namespace.
  203. // For example, the inner protected *_converse* object has a
  204. // method "onConnected". You can override that method as follows:
  205. onConnected: function () {
  206. // Overrides the onConnected method in converse.js
  207. // Top-level functions in "overrides" are bound to the
  208. // inner "_converse" object.
  209. var _converse = this;
  210. // Your custom code comes here.
  211. // ...
  212. // You can access the original function being overridden
  213. // via the __super__ attribute.
  214. // Make sure to pass on the arguments supplied to this
  215. // function and also to apply the proper "this" object.
  216. _converse.__super__.onConnected.apply(this, arguments);
  217. },
  218. XMPPStatus: {
  219. // Override converse.js's XMPPStatus Backbone model so that we can override the
  220. // function that sends out the presence stanza.
  221. sendPresence: function (type, status_message, jid) {
  222. // The "_converse" object is available via the __super__
  223. // attribute.
  224. var _converse = this.__super__._converse;
  225. // Custom code can come here
  226. // ...
  227. // You can call the original overridden method, by
  228. // accessing it via the __super__ attribute.
  229. // When calling it, you need to apply the proper
  230. // context as reference by the "this" variable.
  231. this.__super__.sendPresence.apply(this, arguments);
  232. }
  233. }
  234. }
  235. });
  236. }));