2
0

converse-core.js 75 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784
  1. // Converse.js
  2. // https://conversejs.org
  3. //
  4. // Copyright (c) 2013-2018, the Converse.js developers
  5. // Licensed under the Mozilla Public License (MPLv2)
  6. (function (root, factory) {
  7. define(["sizzle",
  8. "es6-promise/dist/es6-promise.auto",
  9. "./lodash.noconflict",
  10. "./lodash.fp",
  11. "./polyfill",
  12. "./i18n",
  13. "./utils/core",
  14. "moment",
  15. "strophe.js",
  16. "pluggable.js/dist/pluggable",
  17. "./backbone.noconflict",
  18. "backbone.nativeview",
  19. "backbone.browserStorage"
  20. ], factory);
  21. }(this, function (sizzle, Promise, _, f, polyfill, i18n, u, moment, Strophe, pluggable, Backbone) {
  22. "use strict";
  23. // Strophe globals
  24. const { $build, $iq, $msg, $pres } = Strophe;
  25. const b64_sha1 = Strophe.SHA1.b64_sha1;
  26. Strophe = Strophe.Strophe;
  27. // Add Strophe Namespaces
  28. Strophe.addNamespace('CARBONS', 'urn:xmpp:carbons:2');
  29. Strophe.addNamespace('CHATSTATES', 'http://jabber.org/protocol/chatstates');
  30. Strophe.addNamespace('CSI', 'urn:xmpp:csi:0');
  31. Strophe.addNamespace('DELAY', 'urn:xmpp:delay');
  32. Strophe.addNamespace('FORWARD', 'urn:xmpp:forward:0');
  33. Strophe.addNamespace('HINTS', 'urn:xmpp:hints');
  34. Strophe.addNamespace('HTTPUPLOAD', 'urn:xmpp:http:upload:0');
  35. Strophe.addNamespace('MAM', 'urn:xmpp:mam:2');
  36. Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
  37. Strophe.addNamespace('OMEMO', "eu.siacs.conversations.axolotl");
  38. Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
  39. Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
  40. Strophe.addNamespace('REGISTER', 'jabber:iq:register');
  41. Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
  42. Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
  43. Strophe.addNamespace('SID', 'urn:xmpp:sid:0');
  44. Strophe.addNamespace('SPOILER', 'urn:xmpp:spoiler:0');
  45. Strophe.addNamespace('VCARD', 'vcard-temp');
  46. Strophe.addNamespace('VCARDUPDATE', 'vcard-temp:x:update');
  47. Strophe.addNamespace('XFORM', 'jabber:x:data');
  48. // Use Mustache style syntax for variable interpolation
  49. /* Configuration of Lodash templates (this config is distinct to the
  50. * config of requirejs-tpl in main.js). This one is for normal inline templates.
  51. */
  52. _.templateSettings = {
  53. 'escape': /\{\{\{([\s\S]+?)\}\}\}/g,
  54. 'evaluate': /\{\[([\s\S]+?)\]\}/g,
  55. 'interpolate': /\{\{([\s\S]+?)\}\}/g,
  56. 'imports': { '_': _ }
  57. };
  58. /**
  59. * A private, closured object containing the private api (via `_converse.api`)
  60. * as well as private methods and internal data-structures.
  61. *
  62. * @namespace _converse
  63. */
  64. const _converse = {
  65. 'templates': {},
  66. 'promises': {}
  67. }
  68. _.extend(_converse, Backbone.Events);
  69. // Core plugins are whitelisted automatically
  70. _converse.core_plugins = [
  71. 'converse-autocomplete',
  72. 'converse-bookmarks',
  73. 'converse-caps',
  74. 'converse-chatboxes',
  75. 'converse-chatboxviews',
  76. 'converse-chatview',
  77. 'converse-controlbox',
  78. 'converse-core',
  79. 'converse-disco',
  80. 'converse-dragresize',
  81. 'converse-embedded',
  82. 'converse-fullscreen',
  83. 'converse-headline',
  84. 'converse-mam',
  85. 'converse-message-view',
  86. 'converse-minimize',
  87. 'converse-modal',
  88. 'converse-muc',
  89. 'converse-muc-views',
  90. 'converse-notification',
  91. 'converse-omemo',
  92. 'converse-ping',
  93. 'converse-profile',
  94. 'converse-push',
  95. 'converse-register',
  96. 'converse-roomslist',
  97. 'converse-roster',
  98. 'converse-rosterview',
  99. 'converse-singleton',
  100. 'converse-spoilers',
  101. 'converse-vcard'
  102. ];
  103. // Setting wait to 59 instead of 60 to avoid timing conflicts with the
  104. // webserver, which is often also set to 60 and might therefore sometimes
  105. // return a 504 error page instead of passing through to the BOSH proxy.
  106. const BOSH_WAIT = 59;
  107. // Make converse pluggable
  108. pluggable.enable(_converse, '_converse', 'pluggable');
  109. _converse.keycodes = {
  110. TAB: 9,
  111. ENTER: 13,
  112. SHIFT: 16,
  113. CTRL: 17,
  114. ALT: 18,
  115. ESCAPE: 27,
  116. UP_ARROW: 38,
  117. DOWN_ARROW: 40,
  118. FORWARD_SLASH: 47,
  119. AT: 50,
  120. META: 91,
  121. META_RIGHT: 93
  122. };
  123. // Module-level constants
  124. _converse.STATUS_WEIGHTS = {
  125. 'offline': 6,
  126. 'unavailable': 5,
  127. 'xa': 4,
  128. 'away': 3,
  129. 'dnd': 2,
  130. 'chat': 1, // We currently don't differentiate between "chat" and "online"
  131. 'online': 1
  132. };
  133. _converse.PRETTY_CHAT_STATUS = {
  134. 'offline': 'Offline',
  135. 'unavailable': 'Unavailable',
  136. 'xa': 'Extended Away',
  137. 'away': 'Away',
  138. 'dnd': 'Do not disturb',
  139. 'chat': 'Chattty',
  140. 'online': 'Online'
  141. };
  142. _converse.ANONYMOUS = "anonymous";
  143. _converse.CLOSED = 'closed';
  144. _converse.EXTERNAL = "external";
  145. _converse.LOGIN = "login";
  146. _converse.LOGOUT = "logout";
  147. _converse.OPENED = 'opened';
  148. _converse.PREBIND = "prebind";
  149. _converse.IQ_TIMEOUT = 20000;
  150. _converse.CONNECTION_STATUS = {
  151. 0: 'ERROR',
  152. 1: 'CONNECTING',
  153. 2: 'CONNFAIL',
  154. 3: 'AUTHENTICATING',
  155. 4: 'AUTHFAIL',
  156. 5: 'CONNECTED',
  157. 6: 'DISCONNECTED',
  158. 7: 'DISCONNECTING',
  159. 8: 'ATTACHED',
  160. 9: 'REDIRECT',
  161. 10: 'RECONNECTING',
  162. };
  163. _converse.SUCCESS = 'success';
  164. _converse.FAILURE = 'failure';
  165. _converse.DEFAULT_IMAGE_TYPE = 'image/png';
  166. _converse.DEFAULT_IMAGE = "iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAIAAABt+uBvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gwHCy455JBsggAABkJJREFUeNrtnM1PE1sUwHvvTD8otWLHST/Gimi1CEgr6M6FEWuIBo2pujDVsNDEP8GN/4MbN7oxrlipG2OCgZgYlxAbkRYw1KqkIDRCSkM7nXvvW8x7vjyNeQ9m7p1p3z1LQk/v/Dhz7vkEXL161cHl9wI5Ag6IA+KAOCAOiAPigDggLhwQB2S+iNZ+PcYY/SWEEP2HAAAIoSAIoihCCP+ngDDGtVotGAz29/cfOXJEUZSOjg6n06lp2sbGRqlUWlhYyGazS0tLbrdbEASrzgksyeYJId3d3el0uqenRxRFAAAA4KdfIIRgjD9+/Pj8+fOpqSndslofEIQwHA6Pjo4mEon//qmFhYXHjx8vLi4ihBgDEnp7e9l8E0Jo165dQ0NDd+/eDYVC2/qsJElDQ0OEkKWlpa2tLZamxAhQo9EIBoOjo6MXL17csZLe3l5FUT59+lQul5l5JRaAVFWNRqN37tw5ceKEQVWRSOTw4cOFQuHbt2+iKLYCIISQLMu3b99OJpOmKAwEAgcPHszn8+vr6wzsiG6UQQhxuVyXLl0aGBgwUW0sFstkMl6v90fo1KyAMMYDAwPnzp0zXfPg4GAqlWo0Gk0MiBAiy/L58+edTqf5Aa4onj59OhaLYYybFRCEMBaL0fNxBw4cSCQStN0QRUBut3t4eJjq6U+dOiVJElVPRBFQIBDo6+ujCqirqyscDlONGykC2lYyYSR6pBoQQapHZwAoHo/TuARYAOrs7GQASFEUqn6aIiBJkhgA6ujooFpUo6iaTa7koFwnaoWadLNe81tbWwzoaJrWrICWl5cZAFpbW6OabVAEtLi4yABQsVjUNK0pAWWzWQaAcrlcswKanZ1VVZUqHYRQEwOq1Wpv3ryhCmh6erpcLjdrNl+v1ycnJ+l5UELI27dvv3//3qxxEADgy5cvExMT9Mznw4cPtFtAdAPFarU6Pj5eKpVM17yxsfHy5cvV1VXazXu62gVBKBQKT58+rdVqJqrFGL948eLdu3dU8/g/H4FBUaJYLAqC0NPTY9brMD4+PjY25mDSracOCABACJmZmXE6nUePHjWu8NWrV48ePSKEsGlAs7Agfd5nenq6Wq0mk0kjDzY2NvbkyRMIIbP2PLvhBUEQ8vl8NpuNx+M+n29bzhVjvLKycv/+/YmJCcazQuwA6YzW1tYmJyf1SY+2trZ/rRk1Go1SqfT69esHDx4UCgVmNaa/zZ/9ABUhRFXVYDB48uTJeDweiUQkSfL7/T9MA2NcqVTK5fLy8vL8/PzU1FSxWHS5XJaM4wGr9sUwxqqqer3eUCgkSZJuUBBCfTRvc3OzXC6vrKxUKhWn02nhCJ5lM4oQQo/HgxD6+vXr58+fHf8sDOp+HQDg8XgclorFU676dKLlo6yWRdItIBwQB8QBcUCtfosRQjRNQwhhjPUC4w46WXryBSHU1zgEQWBz99EFhDGu1+t+v//48ePxeFxRlD179ng8nh0Efgiher2+vr6ur3HMzMysrq7uTJVdACGEurq6Ll++nEgkPB7Pj9jPoDHqOxyqqubz+WfPnuVyuV9XPeyeagAAAoHArVu3BgcHab8CuVzu4cOHpVKJUnfA5GweY+xyuc6cOXPv3r1IJMLAR8iyPDw8XK/Xi8Wiqqqmm5KZgBBC7e3tN27cuHbtGuPVpf7+/lAoNDs7W61WzfVKpgHSSzw3b95MpVKW3MfRaDQSiczNzVUqFRMZmQOIEOL1eq9fv3727FlL1t50URRFluX5+flqtWpWEGAOIFEUU6nUlStXLKSjy759+xwOx9zcnKZpphzGHMzhcDiTydgk9r1w4YIp7RPTAAmCkMlk2FeLf/tIEKbTab/fbwtAhJBoNGrutpNx6e7uPnTokC1eMU3T0um0DZPMkZER6wERQnw+n/FFSxpy7Nix3bt3WwwIIcRgIWnHkkwmjecfRgGx7DtuV/r6+iwGhDHev3+/bQF1dnYaH6E2CkiWZdsC2rt3r8WAHA5HW1ubbQGZcjajgOwTH/4qNko1Wlg4IA6IA+KAOKBWBUQIsfNojyliKIoRRfH9+/dut9umf3wzpoUNNQ4BAJubmwz+ic+OxefzWWlBhJD29nbug7iT5sIBcUAcEAfEAXFAHBAHxOVn+QMrmWpuPZx12gAAAABJRU5ErkJggg==";
  167. _converse.TIMEOUTS = { // Set as module attr so that we can override in tests.
  168. 'PAUSED': 10000,
  169. 'INACTIVE': 90000
  170. };
  171. // XEP-0085 Chat states
  172. // http://xmpp.org/extensions/xep-0085.html
  173. _converse.INACTIVE = 'inactive';
  174. _converse.ACTIVE = 'active';
  175. _converse.COMPOSING = 'composing';
  176. _converse.PAUSED = 'paused';
  177. _converse.GONE = 'gone';
  178. // Chat types
  179. _converse.PRIVATE_CHAT_TYPE = 'chatbox';
  180. _converse.CHATROOMS_TYPE = 'chatroom';
  181. _converse.HEADLINES_TYPE = 'headline';
  182. _converse.CONTROLBOX_TYPE = 'controlbox';
  183. // Default configuration values
  184. // ----------------------------
  185. _converse.default_settings = {
  186. allow_non_roster_messaging: false,
  187. animate: true,
  188. authentication: 'login', // Available values are "login", "prebind", "anonymous" and "external".
  189. auto_away: 0, // Seconds after which user status is set to 'away'
  190. auto_login: false, // Currently only used in connection with anonymous login
  191. auto_reconnect: true,
  192. auto_xa: 0, // Seconds after which user status is set to 'xa'
  193. blacklisted_plugins: [],
  194. bosh_service_url: undefined,
  195. connection_options: {},
  196. credentials_url: null, // URL from where login credentials can be fetched
  197. csi_waiting_time: 0, // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out.
  198. debug: false,
  199. default_state: 'online',
  200. expose_rid_and_sid: false,
  201. geouri_regex: /https:\/\/www.openstreetmap.org\/.*#map=[0-9]+\/([\-0-9.]+)\/([\-0-9.]+)\S*/g,
  202. geouri_replacement: 'https://www.openstreetmap.org/?mlat=$1&mlon=$2#map=18/$1/$2',
  203. jid: undefined,
  204. keepalive: true,
  205. locales_url: 'locale/{{{locale}}}/LC_MESSAGES/converse.json',
  206. locales: [
  207. 'af', 'ar', 'bg', 'ca', 'cs', 'de', 'es', 'eu', 'en', 'fr', 'he',
  208. 'hi', 'hu', 'id', 'it', 'ja', 'nb', 'nl',
  209. 'pl', 'pt_BR', 'ro', 'ru', 'tr', 'uk', 'zh_CN', 'zh_TW'
  210. ],
  211. message_carbons: true,
  212. nickname: undefined,
  213. password: undefined,
  214. prebind_url: null,
  215. priority: 0,
  216. rid: undefined,
  217. root: window.document,
  218. sid: undefined,
  219. strict_plugin_dependencies: false,
  220. trusted: true,
  221. view_mode: 'overlayed', // Choices are 'overlayed', 'fullscreen', 'mobile'
  222. websocket_url: undefined,
  223. whitelisted_plugins: []
  224. };
  225. _converse.log = function (message, level, style='') {
  226. /* Logs messages to the browser's developer console.
  227. *
  228. * Parameters:
  229. * (String) message - The message to be logged.
  230. * (Integer) level - The loglevel which allows for filtering of log
  231. * messages.
  232. *
  233. * Available loglevels are 0 for 'debug', 1 for 'info', 2 for 'warn',
  234. * 3 for 'error' and 4 for 'fatal'.
  235. *
  236. * When using the 'error' or 'warn' loglevels, a full stacktrace will be
  237. * logged as well.
  238. */
  239. if (level === Strophe.LogLevel.ERROR || level === Strophe.LogLevel.FATAL) {
  240. style = style || 'color: maroon';
  241. }
  242. if (message instanceof Error) {
  243. message = message.stack;
  244. } else if (_.isElement(message)) {
  245. message = message.outerHTML;
  246. }
  247. const prefix = style ? '%c' : '';
  248. const logger = _.assign({
  249. 'debug': _.get(console, 'log') ? console.log.bind(console) : _.noop,
  250. 'error': _.get(console, 'log') ? console.log.bind(console) : _.noop,
  251. 'info': _.get(console, 'log') ? console.log.bind(console) : _.noop,
  252. 'warn': _.get(console, 'log') ? console.log.bind(console) : _.noop
  253. }, console);
  254. if (level === Strophe.LogLevel.ERROR) {
  255. logger.error(`${prefix} ERROR: ${message}`, style);
  256. } else if (level === Strophe.LogLevel.WARN) {
  257. if (_converse.debug) {
  258. logger.warn(`${prefix} ${moment().format()} WARNING: ${message}`, style);
  259. }
  260. } else if (level === Strophe.LogLevel.FATAL) {
  261. logger.error(`${prefix} FATAL: ${message}`, style);
  262. } else if (_converse.debug) {
  263. if (level === Strophe.LogLevel.DEBUG) {
  264. logger.debug(`${prefix} ${moment().format()} DEBUG: ${message}`, style);
  265. } else {
  266. logger.info(`${prefix} ${moment().format()} INFO: ${message}`, style);
  267. }
  268. }
  269. };
  270. Strophe.log = function (level, msg) { _converse.log(level+' '+msg, level); };
  271. Strophe.error = function (msg) { _converse.log(msg, Strophe.LogLevel.ERROR); };
  272. _converse.__ = function (str) {
  273. /* Translate the given string based on the current locale.
  274. *
  275. * Parameters:
  276. * (String) str - The string to translate.
  277. */
  278. if (_.isUndefined(i18n)) {
  279. return str;
  280. }
  281. return i18n.translate.apply(i18n, arguments);
  282. }
  283. const __ = _converse.__;
  284. const PROMISES = [
  285. 'initialized',
  286. 'connectionInitialized',
  287. 'pluginsInitialized',
  288. 'statusInitialized'
  289. ];
  290. function addPromise (promise) {
  291. /* Private function, used to add a new promise to the ones already
  292. * available via the `waitUntil` api method.
  293. */
  294. _converse.promises[promise] = u.getResolveablePromise();
  295. }
  296. _converse.emit = function (name) {
  297. /* Event emitter and promise resolver */
  298. _converse.trigger.apply(this, arguments);
  299. const promise = _converse.promises[name];
  300. if (!_.isUndefined(promise)) {
  301. promise.resolve();
  302. }
  303. };
  304. _converse.isSingleton = function () {
  305. return _.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode);
  306. }
  307. _converse.router = new Backbone.Router();
  308. _converse.initialize = function (settings, callback) {
  309. settings = !_.isUndefined(settings) ? settings : {};
  310. const init_promise = u.getResolveablePromise();
  311. _.each(PROMISES, addPromise);
  312. if (!_.isUndefined(_converse.connection)) {
  313. // Looks like _converse.initialized was called again without logging
  314. // out or disconnecting in the previous session.
  315. // This happens in tests. We therefore first clean up.
  316. Backbone.history.stop();
  317. _converse.chatboxviews.closeAllChatBoxes();
  318. if (_converse.bookmarks) {
  319. _converse.bookmarks.reset();
  320. }
  321. delete _converse.controlboxtoggle;
  322. delete _converse.chatboxviews;
  323. _converse.connection.reset();
  324. _converse.stopListening();
  325. _converse.tearDown();
  326. delete _converse.config;
  327. _converse.initClientConfig();
  328. _converse.off();
  329. }
  330. if ('onpagehide' in window) {
  331. // Pagehide gets thrown in more cases than unload. Specifically it
  332. // gets thrown when the page is cached and not just
  333. // closed/destroyed. It's the only viable event on mobile Safari.
  334. // https://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/
  335. _converse.unloadevent = 'pagehide';
  336. } else if ('onbeforeunload' in window) {
  337. _converse.unloadevent = 'beforeunload';
  338. } else if ('onunload' in window) {
  339. _converse.unloadevent = 'unload';
  340. }
  341. _.assignIn(this, this.default_settings);
  342. // Allow only whitelisted configuration attributes to be overwritten
  343. _.assignIn(this, _.pick(settings, _.keys(this.default_settings)));
  344. if (this.authentication === _converse.ANONYMOUS) {
  345. if (this.auto_login && !this.jid) {
  346. throw new Error("Config Error: you need to provide the server's " +
  347. "domain via the 'jid' option when using anonymous " +
  348. "authentication with auto_login.");
  349. }
  350. }
  351. /* Localisation */
  352. if (!_.isUndefined(i18n)) {
  353. i18n.setLocales(settings.i18n, _converse);
  354. } else {
  355. _converse.locale = 'en';
  356. }
  357. // Module-level variables
  358. // ----------------------
  359. this.callback = callback || _.noop;
  360. /* When reloading the page:
  361. * For new sessions, we need to send out a presence stanza to notify
  362. * the server/network that we're online.
  363. * When re-attaching to an existing session (e.g. via the keepalive
  364. * option), we don't need to again send out a presence stanza, because
  365. * it's as if "we never left" (see onConnectStatusChanged).
  366. * https://github.com/jcbrand/converse.js/issues/521
  367. */
  368. this.send_initial_presence = true;
  369. this.msg_counter = 0;
  370. this.user_settings = settings; // Save the user settings so that they can be used by plugins
  371. // Module-level functions
  372. // ----------------------
  373. this.generateResource = () => `/converse.js-${Math.floor(Math.random()*139749528).toString()}`;
  374. this.sendCSI = function (stat) {
  375. /* Send out a Chat Status Notification (XEP-0352)
  376. *
  377. * Parameters:
  378. * (String) stat: The user's chat status
  379. */
  380. /* Send out a Chat Status Notification (XEP-0352) */
  381. // XXX if (converse.features[Strophe.NS.CSI] || true) {
  382. _converse.connection.send($build(stat, {xmlns: Strophe.NS.CSI}));
  383. _converse.inactive = (stat === _converse.INACTIVE) ? true : false;
  384. };
  385. this.onUserActivity = function () {
  386. /* Resets counters and flags relating to CSI and auto_away/auto_xa */
  387. if (_converse.idle_seconds > 0) {
  388. _converse.idle_seconds = 0;
  389. }
  390. if (!_converse.connection.authenticated) {
  391. // We can't send out any stanzas when there's no authenticated connection.
  392. // converse can happen when the connection reconnects.
  393. return;
  394. }
  395. if (_converse.inactive) {
  396. _converse.sendCSI(_converse.ACTIVE);
  397. }
  398. if (_converse.auto_changed_status === true) {
  399. _converse.auto_changed_status = false;
  400. // XXX: we should really remember the original state here, and
  401. // then set it back to that...
  402. _converse.xmppstatus.set('status', _converse.default_state);
  403. }
  404. };
  405. this.onEverySecond = function () {
  406. /* An interval handler running every second.
  407. * Used for CSI and the auto_away and auto_xa features.
  408. */
  409. if (!_converse.connection.authenticated) {
  410. // We can't send out any stanzas when there's no authenticated connection.
  411. // This can happen when the connection reconnects.
  412. return;
  413. }
  414. const stat = _converse.xmppstatus.get('status');
  415. _converse.idle_seconds++;
  416. if (_converse.csi_waiting_time > 0 &&
  417. _converse.idle_seconds > _converse.csi_waiting_time &&
  418. !_converse.inactive) {
  419. _converse.sendCSI(_converse.INACTIVE);
  420. }
  421. if (_converse.auto_away > 0 &&
  422. _converse.idle_seconds > _converse.auto_away &&
  423. stat !== 'away' && stat !== 'xa' && stat !== 'dnd') {
  424. _converse.auto_changed_status = true;
  425. _converse.xmppstatus.set('status', 'away');
  426. } else if (_converse.auto_xa > 0 &&
  427. _converse.idle_seconds > _converse.auto_xa &&
  428. stat !== 'xa' && stat !== 'dnd') {
  429. _converse.auto_changed_status = true;
  430. _converse.xmppstatus.set('status', 'xa');
  431. }
  432. };
  433. this.registerIntervalHandler = function () {
  434. /* Set an interval of one second and register a handler for it.
  435. * Required for the auto_away, auto_xa and csi_waiting_time features.
  436. */
  437. if (_converse.auto_away < 1 && _converse.auto_xa < 1 && _converse.csi_waiting_time < 1) {
  438. // Waiting time of less then one second means features aren't used.
  439. return;
  440. }
  441. _converse.idle_seconds = 0
  442. _converse.auto_changed_status = false; // Was the user's status changed by _converse.js?
  443. window.addEventListener('click', _converse.onUserActivity);
  444. window.addEventListener('focus', _converse.onUserActivity);
  445. window.addEventListener('keypress', _converse.onUserActivity);
  446. window.addEventListener('mousemove', _converse.onUserActivity);
  447. const options = {'once': true, 'passive': true};
  448. window.addEventListener(_converse.unloadevent, _converse.onUserActivity, options);
  449. _converse.everySecondTrigger = window.setInterval(_converse.onEverySecond, 1000);
  450. };
  451. this.setConnectionStatus = function (connection_status, message) {
  452. _converse.connfeedback.set({
  453. 'connection_status': connection_status,
  454. 'message': message
  455. });
  456. };
  457. this.rejectPresenceSubscription = function (jid, message) {
  458. /* Reject or cancel another user's subscription to our presence updates.
  459. *
  460. * Parameters:
  461. * (String) jid - The Jabber ID of the user whose subscription
  462. * is being canceled.
  463. * (String) message - An optional message to the user
  464. */
  465. const pres = $pres({to: jid, type: "unsubscribed"});
  466. if (message && message !== "") { pres.c("status").t(message); }
  467. _converse.connection.send(pres);
  468. };
  469. this.reconnect = _.debounce(function () {
  470. _converse.log('RECONNECTING');
  471. _converse.log('The connection has dropped, attempting to reconnect.');
  472. _converse.setConnectionStatus(
  473. Strophe.Status.RECONNECTING,
  474. __('The connection has dropped, attempting to reconnect.')
  475. );
  476. _converse.connection.reconnecting = true;
  477. _converse.tearDown();
  478. _converse.logIn(null, true);
  479. }, 3000, {'leading': true});
  480. this.disconnect = function () {
  481. _converse.log('DISCONNECTED');
  482. delete _converse.connection.reconnecting;
  483. _converse.connection.reset();
  484. _converse.tearDown();
  485. _converse.clearSession();
  486. _converse.emit('disconnected');
  487. };
  488. this.onDisconnected = function () {
  489. /* Gets called once strophe's status reaches Strophe.Status.DISCONNECTED.
  490. * Will either start a teardown process for converse.js or attempt
  491. * to reconnect.
  492. */
  493. const reason = _converse.disconnection_reason;
  494. if (_converse.disconnection_cause === Strophe.Status.AUTHFAIL) {
  495. if (_converse.credentials_url && _converse.auto_reconnect) {
  496. /* In this case, we reconnect, because we might be receiving
  497. * expirable tokens from the credentials_url.
  498. */
  499. _converse.emit('will-reconnect');
  500. return _converse.reconnect();
  501. } else {
  502. return _converse.disconnect();
  503. }
  504. } else if (_converse.disconnection_cause === _converse.LOGOUT ||
  505. (!_.isUndefined(reason) && reason === _.get(Strophe, 'ErrorCondition.NO_AUTH_MECH')) ||
  506. reason === "host-unknown" ||
  507. reason === "remote-connection-failed" ||
  508. !_converse.auto_reconnect) {
  509. return _converse.disconnect();
  510. }
  511. _converse.emit('will-reconnect');
  512. _converse.reconnect();
  513. };
  514. this.setDisconnectionCause = function (cause, reason, override) {
  515. /* Used to keep track of why we got disconnected, so that we can
  516. * decide on what the next appropriate action is (in onDisconnected)
  517. */
  518. if (_.isUndefined(cause)) {
  519. delete _converse.disconnection_cause;
  520. delete _converse.disconnection_reason;
  521. } else if (_.isUndefined(_converse.disconnection_cause) || override) {
  522. _converse.disconnection_cause = cause;
  523. _converse.disconnection_reason = reason;
  524. }
  525. };
  526. this.onConnectStatusChanged = function (status, message) {
  527. /* Callback method called by Strophe as the Strophe.Connection goes
  528. * through various states while establishing or tearing down a
  529. * connection.
  530. */
  531. _converse.log(`Status changed to: ${_converse.CONNECTION_STATUS[status]}`);
  532. if (status === Strophe.Status.CONNECTED || status === Strophe.Status.ATTACHED) {
  533. _converse.setConnectionStatus(status);
  534. // By default we always want to send out an initial presence stanza.
  535. _converse.send_initial_presence = true;
  536. _converse.setDisconnectionCause();
  537. if (_converse.connection.reconnecting) {
  538. _converse.log(status === Strophe.Status.CONNECTED ? 'Reconnected' : 'Reattached');
  539. _converse.onConnected(true);
  540. } else {
  541. _converse.log(status === Strophe.Status.CONNECTED ? 'Connected' : 'Attached');
  542. if (_converse.connection.restored) {
  543. // No need to send an initial presence stanza when
  544. // we're restoring an existing session.
  545. _converse.send_initial_presence = false;
  546. }
  547. _converse.onConnected();
  548. }
  549. } else if (status === Strophe.Status.DISCONNECTED) {
  550. _converse.setDisconnectionCause(status, message);
  551. _converse.onDisconnected();
  552. } else if (status === Strophe.Status.ERROR) {
  553. _converse.setConnectionStatus(
  554. status,
  555. __('An error occurred while connecting to the chat server.')
  556. );
  557. } else if (status === Strophe.Status.CONNECTING) {
  558. _converse.setConnectionStatus(status);
  559. } else if (status === Strophe.Status.AUTHENTICATING) {
  560. _converse.setConnectionStatus(status);
  561. } else if (status === Strophe.Status.AUTHFAIL) {
  562. if (!message) {
  563. message = __('Your Jabber ID and/or password is incorrect. Please try again.');
  564. }
  565. _converse.setConnectionStatus(status, message);
  566. _converse.setDisconnectionCause(status, message, true);
  567. _converse.onDisconnected();
  568. } else if (status === Strophe.Status.CONNFAIL) {
  569. let feedback = message;
  570. if (message === "host-unknown" || message == "remote-connection-failed") {
  571. feedback = __("Sorry, we could not connect to the XMPP host with domain: %1$s",
  572. `\"${Strophe.getDomainFromJid(_converse.connection.jid)}\"`);
  573. } else if (!_.isUndefined(message) && message === _.get(Strophe, 'ErrorCondition.NO_AUTH_MECH')) {
  574. feedback = __("The XMPP server did not offer a supported authentication mechanism");
  575. }
  576. _converse.setConnectionStatus(status, feedback);
  577. _converse.setDisconnectionCause(status, message);
  578. } else if (status === Strophe.Status.DISCONNECTING) {
  579. _converse.setDisconnectionCause(status, message);
  580. }
  581. };
  582. this.incrementMsgCounter = function () {
  583. this.msg_counter += 1;
  584. const unreadMsgCount = this.msg_counter;
  585. let title = document.title;
  586. if (_.isNil(title)) {
  587. return;
  588. }
  589. if (title.search(/^Messages \(\d+\) /) === -1) {
  590. title = `Messages (${unreadMsgCount}) ${title}`;
  591. } else {
  592. title = title.replace(/^Messages \(\d+\) /, `Messages (${unreadMsgCount})`);
  593. }
  594. };
  595. this.clearMsgCounter = function () {
  596. this.msg_counter = 0;
  597. let title = document.title;
  598. if (_.isNil(title)) {
  599. return;
  600. }
  601. if (title.search(/^Messages \(\d+\) /) !== -1) {
  602. title = title.replace(/^Messages \(\d+\) /, "");
  603. }
  604. };
  605. this.initStatus = (reconnecting) => {
  606. // If there's no xmppstatus obj, then we were never connected to
  607. // begin with, so we set reconnecting to false.
  608. reconnecting = _.isUndefined(_converse.xmppstatus) ? false : reconnecting;
  609. if (reconnecting) {
  610. _converse.onStatusInitialized(reconnecting);
  611. } else {
  612. const id = `converse.xmppstatus-${_converse.bare_jid}`;
  613. this.xmppstatus = new this.XMPPStatus({'id': id});
  614. this.xmppstatus.browserStorage = new Backbone.BrowserStorage.session(id);
  615. this.xmppstatus.fetch({
  616. 'success': _.partial(_converse.onStatusInitialized, reconnecting),
  617. 'error': _.partial(_converse.onStatusInitialized, reconnecting)
  618. });
  619. }
  620. }
  621. this.initClientConfig = function () {
  622. /* The client config refers to configuration of the client which is
  623. * independent of any particular user.
  624. * What this means is that config values need to persist across
  625. * user sessions.
  626. */
  627. const id = b64_sha1('converse.client-config');
  628. _converse.config = new Backbone.Model({
  629. 'id': id,
  630. 'trusted': _converse.trusted && true || false,
  631. 'storage': _converse.trusted ? 'local' : 'session'
  632. });
  633. _converse.config.browserStorage = new Backbone.BrowserStorage.session(id);
  634. _converse.config.fetch();
  635. _converse.emit('clientConfigInitialized');
  636. };
  637. this.initSession = function () {
  638. const id = b64_sha1('converse.bosh-session');
  639. _converse.session = new Backbone.Model({'id': id});
  640. _converse.session.browserStorage = new Backbone.BrowserStorage.session(id);
  641. _converse.session.fetch();
  642. _converse.emit('sessionInitialized');
  643. };
  644. this.clearSession = function () {
  645. if (!_converse.config.get('trusted')) {
  646. window.localStorage.clear();
  647. window.sessionStorage.clear();
  648. } else if (!_.isUndefined(this.session) && this.session.browserStorage) {
  649. this.session.browserStorage._clear();
  650. }
  651. _converse.emit('clearSession');
  652. };
  653. this.logOut = function () {
  654. _converse.clearSession();
  655. _converse.setDisconnectionCause(_converse.LOGOUT, undefined, true);
  656. if (!_.isUndefined(_converse.connection)) {
  657. _converse.connection.disconnect();
  658. } else {
  659. _converse.tearDown();
  660. }
  661. // Recreate all the promises
  662. _.each(_.keys(_converse.promises), addPromise);
  663. _converse.emit('logout');
  664. };
  665. this.saveWindowState = function (ev, hidden) {
  666. // XXX: eventually we should be able to just use
  667. // document.visibilityState (when we drop support for older
  668. // browsers).
  669. let state;
  670. const event_map = {
  671. 'focus': "visible",
  672. 'focusin': "visible",
  673. 'pageshow': "visible",
  674. 'blur': "hidden",
  675. 'focusout': "hidden",
  676. 'pagehide': "hidden"
  677. };
  678. ev = ev || document.createEvent('Events');
  679. if (ev.type in event_map) {
  680. state = event_map[ev.type];
  681. } else {
  682. state = document[hidden] ? "hidden" : "visible";
  683. }
  684. if (state === 'visible') {
  685. _converse.clearMsgCounter();
  686. }
  687. _converse.windowState = state;
  688. _converse.emit('windowStateChanged', {state});
  689. };
  690. this.registerGlobalEventHandlers = function () {
  691. // Taken from:
  692. // http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active
  693. let hidden = "hidden";
  694. // Standards:
  695. if (hidden in document) {
  696. document.addEventListener("visibilitychange", _.partial(_converse.saveWindowState, _, hidden));
  697. } else if ((hidden = "mozHidden") in document) {
  698. document.addEventListener("mozvisibilitychange", _.partial(_converse.saveWindowState, _, hidden));
  699. } else if ((hidden = "webkitHidden") in document) {
  700. document.addEventListener("webkitvisibilitychange", _.partial(_converse.saveWindowState, _, hidden));
  701. } else if ((hidden = "msHidden") in document) {
  702. document.addEventListener("msvisibilitychange", _.partial(_converse.saveWindowState, _, hidden));
  703. } else if ("onfocusin" in document) {
  704. // IE 9 and lower:
  705. document.onfocusin = document.onfocusout = _.partial(_converse.saveWindowState, _, hidden);
  706. } else {
  707. // All others:
  708. window.onpageshow = window.onpagehide = window.onfocus = window.onblur = _.partial(_converse.saveWindowState, _, hidden);
  709. }
  710. // set the initial state (but only if browser supports the Page Visibility API)
  711. if( document[hidden] !== undefined ) {
  712. _.partial(_converse.saveWindowState, _, hidden)({type: document[hidden] ? "blur" : "focus"});
  713. }
  714. _converse.emit('registeredGlobalEventHandlers');
  715. };
  716. this.enableCarbons = function () {
  717. /* Ask the XMPP server to enable Message Carbons
  718. * See XEP-0280 https://xmpp.org/extensions/xep-0280.html#enabling
  719. */
  720. if (!this.message_carbons || this.session.get('carbons_enabled')) {
  721. return;
  722. }
  723. const carbons_iq = new Strophe.Builder('iq', {
  724. 'from': this.connection.jid,
  725. 'id': 'enablecarbons',
  726. 'type': 'set'
  727. })
  728. .c('enable', {xmlns: Strophe.NS.CARBONS});
  729. this.connection.addHandler((iq) => {
  730. if (iq.querySelectorAll('error').length > 0) {
  731. _converse.log(
  732. 'An error occurred while trying to enable message carbons.',
  733. Strophe.LogLevel.WARN);
  734. } else {
  735. this.session.save({'carbons_enabled': true});
  736. _converse.log('Message carbons have been enabled.');
  737. }
  738. }, null, "iq", null, "enablecarbons");
  739. this.connection.send(carbons_iq);
  740. };
  741. this.sendInitialPresence = function () {
  742. if (_converse.send_initial_presence) {
  743. _converse.xmppstatus.sendPresence();
  744. }
  745. };
  746. this.onStatusInitialized = function (reconnecting) {
  747. _converse.emit('statusInitialized', reconnecting);
  748. if (reconnecting) {
  749. _converse.emit('reconnected');
  750. } else {
  751. init_promise.resolve();
  752. _converse.emit('initialized');
  753. _converse.emit('connected');
  754. }
  755. };
  756. this.setUserJID = function () {
  757. _converse.jid = _converse.connection.jid;
  758. _converse.bare_jid = Strophe.getBareJidFromJid(_converse.connection.jid);
  759. _converse.resource = Strophe.getResourceFromJid(_converse.connection.jid);
  760. _converse.domain = Strophe.getDomainFromJid(_converse.connection.jid);
  761. _converse.emit('setUserJID');
  762. };
  763. this.onConnected = function (reconnecting) {
  764. /* Called as soon as a new connection has been established, either
  765. * by logging in or by attaching to an existing BOSH session.
  766. */
  767. _converse.connection.flush(); // Solves problem of returned PubSub BOSH response not received by browser
  768. _converse.setUserJID();
  769. _converse.initSession();
  770. _converse.enableCarbons();
  771. _converse.initStatus(reconnecting)
  772. };
  773. this.ConnectionFeedback = Backbone.Model.extend({
  774. defaults: {
  775. 'connection_status': Strophe.Status.DISCONNECTED,
  776. 'message': ''
  777. },
  778. initialize () {
  779. this.on('change', () => {
  780. _converse.emit('connfeedback', _converse.connfeedback);
  781. });
  782. }
  783. });
  784. this.connfeedback = new this.ConnectionFeedback();
  785. this.XMPPStatus = Backbone.Model.extend({
  786. defaults () {
  787. return {
  788. "jid": _converse.bare_jid,
  789. "status": _converse.default_state
  790. }
  791. },
  792. initialize () {
  793. this.vcard = _converse.vcards.findWhere({'jid': this.get('jid')});
  794. if (_.isNil(this.vcard)) {
  795. this.vcard = _converse.vcards.create({'jid': this.get('jid')});
  796. }
  797. this.on('change:status', (item) => {
  798. const status = this.get('status');
  799. this.sendPresence(status);
  800. _converse.emit('statusChanged', status);
  801. });
  802. this.on('change:status_message', () => {
  803. const status_message = this.get('status_message');
  804. this.sendPresence(this.get('status'), status_message);
  805. _converse.emit('statusMessageChanged', status_message);
  806. });
  807. },
  808. constructPresence (type, status_message) {
  809. let presence;
  810. type = _.isString(type) ? type : (this.get('status') || _converse.default_state);
  811. status_message = _.isString(status_message) ? status_message : this.get('status_message');
  812. // Most of these presence types are actually not explicitly sent,
  813. // but I add all of them here for reference and future proofing.
  814. if ((type === 'unavailable') ||
  815. (type === 'probe') ||
  816. (type === 'error') ||
  817. (type === 'unsubscribe') ||
  818. (type === 'unsubscribed') ||
  819. (type === 'subscribe') ||
  820. (type === 'subscribed')) {
  821. presence = $pres({'type': type});
  822. } else if (type === 'offline') {
  823. presence = $pres({'type': 'unavailable'});
  824. } else if (type === 'online') {
  825. presence = $pres();
  826. } else {
  827. presence = $pres().c('show').t(type).up();
  828. }
  829. if (status_message) {
  830. presence.c('status').t(status_message).up();
  831. }
  832. presence.c('priority').t(
  833. _.isNaN(Number(_converse.priority)) ? 0 : _converse.priority
  834. );
  835. return presence;
  836. },
  837. sendPresence (type, status_message) {
  838. _converse.connection.send(this.constructPresence(type, status_message));
  839. }
  840. });
  841. this.setUpXMLLogging = function () {
  842. Strophe.log = function (level, msg) {
  843. _converse.log(msg, level);
  844. };
  845. if (this.debug) {
  846. this.connection.xmlInput = function (body) {
  847. _converse.log(body.outerHTML, Strophe.LogLevel.DEBUG, 'color: darkgoldenrod');
  848. };
  849. this.connection.xmlOutput = function (body) {
  850. _converse.log(body.outerHTML, Strophe.LogLevel.DEBUG, 'color: darkcyan');
  851. };
  852. }
  853. };
  854. this.fetchLoginCredentials = () =>
  855. new Promise((resolve, reject) => {
  856. const xhr = new XMLHttpRequest();
  857. xhr.open('GET', _converse.credentials_url, true);
  858. xhr.setRequestHeader('Accept', "application/json, text/javascript");
  859. xhr.onload = function() {
  860. if (xhr.status >= 200 && xhr.status < 400) {
  861. const data = JSON.parse(xhr.responseText);
  862. resolve({
  863. 'jid': data.jid,
  864. 'password': data.password
  865. });
  866. } else {
  867. xhr.onerror();
  868. }
  869. };
  870. xhr.onerror = function () {
  871. delete _converse.connection;
  872. _converse.emit('noResumeableSession', this);
  873. reject(xhr.responseText);
  874. };
  875. xhr.send();
  876. });
  877. this.startNewBOSHSession = function () {
  878. const xhr = new XMLHttpRequest();
  879. xhr.open('GET', _converse.prebind_url, true);
  880. xhr.setRequestHeader('Accept', "application/json, text/javascript");
  881. xhr.onload = function() {
  882. if (xhr.status >= 200 && xhr.status < 400) {
  883. const data = JSON.parse(xhr.responseText);
  884. _converse.connection.attach(
  885. data.jid, data.sid, data.rid,
  886. _converse.onConnectStatusChanged);
  887. } else {
  888. xhr.onerror();
  889. }
  890. };
  891. xhr.onerror = function () {
  892. delete _converse.connection;
  893. _converse.emit('noResumeableSession', this);
  894. };
  895. xhr.send();
  896. };
  897. this.restoreBOSHSession = function (jid_is_required) {
  898. /* Tries to restore a cached BOSH session. */
  899. if (!this.jid) {
  900. const msg = "restoreBOSHSession: tried to restore a \"keepalive\" session "+
  901. "but we don't have the JID for the user!";
  902. if (jid_is_required) {
  903. throw new Error(msg);
  904. } else {
  905. _converse.log(msg);
  906. }
  907. }
  908. try {
  909. this.connection.restore(this.jid, this.onConnectStatusChanged);
  910. return true;
  911. } catch (e) {
  912. _converse.log(
  913. "Could not restore session for jid: "+
  914. this.jid+" Error message: "+e.message, Strophe.LogLevel.WARN);
  915. this.clearSession(); // We want to clear presences (see #555)
  916. return false;
  917. }
  918. };
  919. this.attemptPreboundSession = function (reconnecting) {
  920. /* Handle session resumption or initialization when prebind is
  921. * being used.
  922. */
  923. if (!reconnecting) {
  924. if (this.keepalive && this.restoreBOSHSession(true)) {
  925. return;
  926. }
  927. // No keepalive, or session resumption has failed.
  928. if (this.jid && this.sid && this.rid) {
  929. return this.connection.attach(
  930. this.jid, this.sid, this.rid,
  931. this.onConnectStatusChanged
  932. );
  933. }
  934. }
  935. if (this.prebind_url) {
  936. return this.startNewBOSHSession();
  937. } else {
  938. throw new Error(
  939. "attemptPreboundSession: If you use prebind and not keepalive, "+
  940. "then you MUST supply JID, RID and SID values or a prebind_url.");
  941. }
  942. };
  943. this.attemptNonPreboundSession = function (credentials, reconnecting) {
  944. /* Handle session resumption or initialization when prebind is not being used.
  945. *
  946. * Two potential options exist and are handled in this method:
  947. * 1. keepalive
  948. * 2. auto_login
  949. */
  950. if (!reconnecting && this.keepalive && this.restoreBOSHSession()) {
  951. return;
  952. }
  953. if (credentials) {
  954. // When credentials are passed in, they override prebinding
  955. // or credentials fetching via HTTP
  956. this.autoLogin(credentials);
  957. } else if (this.auto_login) {
  958. if (this.credentials_url) {
  959. this.fetchLoginCredentials().then(
  960. this.autoLogin.bind(this),
  961. this.autoLogin.bind(this)
  962. );
  963. } else if (!this.jid) {
  964. throw new Error(
  965. "attemptNonPreboundSession: If you use auto_login, "+
  966. "you also need to give either a jid value (and if "+
  967. "applicable a password) or you need to pass in a URL "+
  968. "from where the username and password can be fetched "+
  969. "(via credentials_url)."
  970. );
  971. } else {
  972. this.autoLogin(); // Could be ANONYMOUS or EXTERNAL
  973. }
  974. } else if (reconnecting) {
  975. this.autoLogin();
  976. }
  977. };
  978. this.autoLogin = function (credentials) {
  979. if (credentials) {
  980. // If passed in, the credentials come from credentials_url,
  981. // so we set them on the converse object.
  982. this.jid = credentials.jid;
  983. }
  984. if (this.authentication === _converse.ANONYMOUS || this.authentication === _converse.EXTERNAL) {
  985. if (!this.jid) {
  986. throw new Error("Config Error: when using anonymous login " +
  987. "you need to provide the server's domain via the 'jid' option. " +
  988. "Either when calling converse.initialize, or when calling " +
  989. "_converse.api.user.login.");
  990. }
  991. if (!this.connection.reconnecting) {
  992. this.connection.reset();
  993. }
  994. this.connection.connect(this.jid.toLowerCase(), null, this.onConnectStatusChanged, BOSH_WAIT);
  995. } else if (this.authentication === _converse.LOGIN) {
  996. const password = _.isNil(credentials) ? (_converse.connection.pass || this.password) : credentials.password;
  997. if (!password) {
  998. if (this.auto_login) {
  999. throw new Error("initConnection: If you use auto_login and "+
  1000. "authentication='login' then you also need to provide a password.");
  1001. }
  1002. _converse.setDisconnectionCause(Strophe.Status.AUTHFAIL, undefined, true);
  1003. _converse.disconnect();
  1004. return;
  1005. }
  1006. const resource = Strophe.getResourceFromJid(this.jid);
  1007. if (!resource) {
  1008. this.jid = this.jid.toLowerCase() + _converse.generateResource();
  1009. } else {
  1010. this.jid = Strophe.getBareJidFromJid(this.jid).toLowerCase()+'/'+resource;
  1011. }
  1012. if (!this.connection.reconnecting) {
  1013. this.connection.reset();
  1014. }
  1015. this.connection.connect(this.jid, password, this.onConnectStatusChanged, BOSH_WAIT);
  1016. }
  1017. };
  1018. this.logIn = function (credentials, reconnecting) {
  1019. // We now try to resume or automatically set up a new session.
  1020. // Otherwise the user will be shown a login form.
  1021. if (this.authentication === _converse.PREBIND) {
  1022. this.attemptPreboundSession(reconnecting);
  1023. } else {
  1024. this.attemptNonPreboundSession(credentials, reconnecting);
  1025. }
  1026. };
  1027. this.initConnection = function () {
  1028. /* Creates a new Strophe.Connection instance if we don't already have one.
  1029. */
  1030. if (!this.connection) {
  1031. if (!this.bosh_service_url && ! this.websocket_url) {
  1032. throw new Error("initConnection: you must supply a value for either the bosh_service_url or websocket_url or both.");
  1033. }
  1034. if (('WebSocket' in window || 'MozWebSocket' in window) && this.websocket_url) {
  1035. this.connection = new Strophe.Connection(this.websocket_url, this.connection_options);
  1036. } else if (this.bosh_service_url) {
  1037. this.connection = new Strophe.Connection(
  1038. this.bosh_service_url,
  1039. _.assignIn(this.connection_options, {'keepalive': this.keepalive})
  1040. );
  1041. } else {
  1042. throw new Error("initConnection: this browser does not support websockets and bosh_service_url wasn't specified.");
  1043. }
  1044. }
  1045. _converse.emit('connectionInitialized');
  1046. };
  1047. this.tearDown = function () {
  1048. /* Remove those views which are only allowed with a valid
  1049. * connection.
  1050. */
  1051. _converse.emit('beforeTearDown');
  1052. if (!_.isUndefined(_converse.session)) {
  1053. _converse.session.destroy();
  1054. }
  1055. window.removeEventListener('click', _converse.onUserActivity);
  1056. window.removeEventListener('focus', _converse.onUserActivity);
  1057. window.removeEventListener('keypress', _converse.onUserActivity);
  1058. window.removeEventListener('mousemove', _converse.onUserActivity);
  1059. window.removeEventListener(_converse.unloadevent, _converse.onUserActivity);
  1060. window.clearInterval(_converse.everySecondTrigger);
  1061. _converse.emit('afterTearDown');
  1062. return _converse;
  1063. };
  1064. this.initPlugins = function () {
  1065. // If initialize gets called a second time (e.g. during tests), then we
  1066. // need to re-apply all plugins (for a new converse instance), and we
  1067. // therefore need to clear this array that prevents plugins from being
  1068. // initialized twice.
  1069. // If initialize is called for the first time, then this array is empty
  1070. // in any case.
  1071. _converse.pluggable.initialized_plugins = [];
  1072. const whitelist = _converse.core_plugins.concat(
  1073. _converse.whitelisted_plugins);
  1074. if (_converse.view_mode === 'embedded') {
  1075. _.forEach([ // eslint-disable-line lodash/prefer-map
  1076. "converse-bookmarks",
  1077. "converse-controlbox",
  1078. "converse-headline",
  1079. "converse-register"
  1080. ], (name) => {
  1081. _converse.blacklisted_plugins.push(name)
  1082. });
  1083. }
  1084. _converse.pluggable.initializePlugins({
  1085. 'updateSettings' () {
  1086. _converse.log(
  1087. "(DEPRECATION) "+
  1088. "The `updateSettings` method has been deprecated. "+
  1089. "Please use `_converse.api.settings.update` instead.",
  1090. Strophe.LogLevel.WARN
  1091. )
  1092. _converse.api.settings.update.apply(_converse, arguments);
  1093. },
  1094. '_converse': _converse
  1095. }, whitelist, _converse.blacklisted_plugins);
  1096. _converse.emit('pluginsInitialized');
  1097. };
  1098. // Initialization
  1099. // --------------
  1100. // This is the end of the initialize method.
  1101. if (settings.connection) {
  1102. this.connection = settings.connection;
  1103. }
  1104. function finishInitialization () {
  1105. _converse.initPlugins();
  1106. _converse.initClientConfig();
  1107. _converse.initConnection();
  1108. _converse.setUpXMLLogging();
  1109. _converse.logIn();
  1110. _converse.registerGlobalEventHandlers();
  1111. if (!Backbone.history.started) {
  1112. Backbone.history.start();
  1113. }
  1114. }
  1115. if (!_.isUndefined(_converse.connection) &&
  1116. _converse.connection.service === 'jasmine tests') {
  1117. finishInitialization();
  1118. return _converse;
  1119. } else if (_.isUndefined(i18n)) {
  1120. finishInitialization();
  1121. } else {
  1122. i18n.fetchTranslations(
  1123. _converse.locale,
  1124. _converse.locales,
  1125. u.interpolate(_converse.locales_url, {'locale': _converse.locale}))
  1126. .catch(e => _converse.log(e.message, Strophe.LogLevel.FATAL))
  1127. .then(finishInitialization)
  1128. .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
  1129. }
  1130. return init_promise;
  1131. };
  1132. /**
  1133. * ### The private API
  1134. *
  1135. * The private API methods are only accessible via the closured {@link _converse}
  1136. * object, which is only available to plugins.
  1137. *
  1138. * These methods are kept private (i.e. not global) because they may return
  1139. * sensitive data which should be kept off-limits to other 3rd-party scripts
  1140. * that might be running in the page.
  1141. *
  1142. * @namespace _converse.api
  1143. * @memberOf _converse
  1144. */
  1145. _converse.api = {
  1146. /**
  1147. * This grouping collects API functions related to the XMPP connection.
  1148. *
  1149. * @namespace _converse.api.connection
  1150. * @memberOf _converse.api
  1151. */
  1152. 'connection': {
  1153. /**
  1154. * @method _converse.api.connection.connected
  1155. * @memberOf _converse.api.connection
  1156. * @returns {boolean} Whether there is an established connection or not.
  1157. */
  1158. 'connected' () {
  1159. return _converse.connection && _converse.connection.connected || false;
  1160. },
  1161. /**
  1162. * Terminates the connection.
  1163. *
  1164. * @method _converse.api.connection.disconnect
  1165. * @memberOf _converse.api.connection
  1166. */
  1167. 'disconnect' () {
  1168. _converse.connection.disconnect();
  1169. },
  1170. },
  1171. /**
  1172. * Lets you emit (i.e. trigger) events, which can be listened to via
  1173. * {@link _converse.api.listen.on} or {@link _converse.api.listen.once}
  1174. * (see [_converse.api.listen](http://localhost:8000/docs/html/api/-_converse.api.listen.html)).
  1175. *
  1176. * @method _converse.api.emit
  1177. */
  1178. 'emit' () {
  1179. _converse.emit.apply(_converse, arguments);
  1180. },
  1181. /**
  1182. * This grouping collects API functions related to the current logged in user.
  1183. *
  1184. * @namespace _converse.api.user
  1185. * @memberOf _converse.api
  1186. */
  1187. 'user': {
  1188. /**
  1189. * @method _converse.api.user.jid
  1190. * @returns {string} The current user's full JID (Jabber ID)
  1191. * @example _converse.api.user.jid())
  1192. */
  1193. 'jid' () {
  1194. return _converse.connection.jid;
  1195. },
  1196. /**
  1197. * Logs the user in.
  1198. *
  1199. * If called without any parameters, Converse will try
  1200. * to log the user in by calling the `prebind_url` or `credentials_url` depending
  1201. * on whether prebinding is used or not.
  1202. *
  1203. * @method _converse.api.user.login
  1204. * @param {object} [credentials] An object with the credentials.
  1205. * @example
  1206. * converse.plugins.add('myplugin', {
  1207. * initialize: function () {
  1208. *
  1209. * this._converse.api.user.login({
  1210. * 'jid': 'dummy@example.com',
  1211. * 'password': 'secret'
  1212. * });
  1213. *
  1214. * }
  1215. * });
  1216. */
  1217. 'login' (credentials) {
  1218. _converse.logIn(credentials);
  1219. },
  1220. /**
  1221. * Logs the user out of the current XMPP session.
  1222. *
  1223. * @method _converse.api.user.logout
  1224. * @example _converse.api.user.logout();
  1225. */
  1226. 'logout' () {
  1227. _converse.logOut();
  1228. },
  1229. /**
  1230. * Set and get the user's chat status, also called their *availability*.
  1231. *
  1232. * @namespace _converse.api.user.status
  1233. * @memberOf _converse.api.user
  1234. */
  1235. 'status': {
  1236. /** Return the current user's availability status.
  1237. *
  1238. * @method _converse.api.user.status.get
  1239. * @example _converse.api.user.status.get();
  1240. */
  1241. 'get' () {
  1242. return _converse.xmppstatus.get('status');
  1243. },
  1244. /**
  1245. * The user's status can be set to one of the following values:
  1246. *
  1247. * @method _converse.api.user.status.set
  1248. * @param {string} value The user's chat status (e.g. 'away', 'dnd', 'offline', 'online', 'unavailable' or 'xa')
  1249. * @param {string} [message] A custom status message
  1250. *
  1251. * @example this._converse.api.user.status.set('dnd');
  1252. * @example this._converse.api.user.status.set('dnd', 'In a meeting');
  1253. */
  1254. 'set' (value, message) {
  1255. const data = {'status': value};
  1256. if (!_.includes(_.keys(_converse.STATUS_WEIGHTS), value)) {
  1257. throw new Error('Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1');
  1258. }
  1259. if (_.isString(message)) {
  1260. data.status_message = message;
  1261. }
  1262. _converse.xmppstatus.sendPresence(value);
  1263. _converse.xmppstatus.save(data);
  1264. },
  1265. /**
  1266. * Set and retrieve the user's custom status message.
  1267. *
  1268. * @namespace _converse.api.user.status.message
  1269. * @memberOf _converse.api.user.status
  1270. */
  1271. 'message': {
  1272. /**
  1273. * @method _converse.api.user.status.message.get
  1274. * @returns {string} The status message
  1275. * @example const message = _converse.api.user.status.message.get()
  1276. */
  1277. 'get' () {
  1278. return _converse.xmppstatus.get('status_message');
  1279. },
  1280. /**
  1281. * @method _converse.api.user.status.message.set
  1282. * @param {string} status The status message
  1283. * @example _converse.api.user.status.message.set('In a meeting');
  1284. */
  1285. 'set' (status) {
  1286. _converse.xmppstatus.save({'status_message': status});
  1287. }
  1288. }
  1289. },
  1290. },
  1291. /**
  1292. * This grouping allows access to the
  1293. * [configuration settings](/docs/html/configuration.html#configuration-settings)
  1294. * of Converse.
  1295. *
  1296. * @namespace _converse.api.settings
  1297. * @memberOf _converse.api
  1298. */
  1299. 'settings': {
  1300. /**
  1301. * Allows new configuration settings to be specified, or new default values for
  1302. * existing configuration settings to be specified.
  1303. *
  1304. * @method _converse.api.settings.update
  1305. * @param {object} settings The configuration settings
  1306. * @example
  1307. * _converse.api.settings.update({
  1308. * 'enable_foo': true
  1309. * });
  1310. *
  1311. * // The user can then override the default value of the configuration setting when
  1312. * // calling `converse.initialize`.
  1313. * converse.initialize({
  1314. * 'enable_foo': false
  1315. * });
  1316. */
  1317. 'update' (settings) {
  1318. u.merge(_converse.default_settings, settings);
  1319. u.merge(_converse, settings);
  1320. u.applyUserSettings(_converse, settings, _converse.user_settings);
  1321. },
  1322. /**
  1323. * @method _converse.api.settings.get
  1324. * @returns {*} Value of the particular configuration setting.
  1325. * @example _converse.api.settings.get("play_sounds");
  1326. */
  1327. 'get' (key) {
  1328. if (_.includes(_.keys(_converse.default_settings), key)) {
  1329. return _converse[key];
  1330. }
  1331. },
  1332. /**
  1333. * Set one or many configuration settings.
  1334. *
  1335. * Note, this is not an alternative to calling {@link converse.initialize}, which still needs
  1336. * to be called. Generally, you'd use this method after Converse is already
  1337. * running and you want to change the configuration on-the-fly.
  1338. *
  1339. * @method _converse.api.settings.set
  1340. * @param {Object} [settings] An object containing configuration settings.
  1341. * @param {string} [key] Alternatively to passing in an object, you can pass in a key and a value.
  1342. * @param {string} [value]
  1343. * @example _converse.api.settings.set("play_sounds", true);
  1344. * @example
  1345. * _converse.api.settings.set({
  1346. * "play_sounds", true,
  1347. * "hide_offline_users" true
  1348. * });
  1349. */
  1350. 'set' (key, val) {
  1351. const o = {};
  1352. if (_.isObject(key)) {
  1353. _.assignIn(_converse, _.pick(key, _.keys(_converse.default_settings)));
  1354. } else if (_.isString("string")) {
  1355. o[key] = val;
  1356. _.assignIn(_converse, _.pick(o, _.keys(_converse.default_settings)));
  1357. }
  1358. }
  1359. },
  1360. /**
  1361. * Converse and its plugins emit various events which you can listen to via the
  1362. * {@link _converse.api.listen} namespace.
  1363. *
  1364. * Some of these events are also available as [ES2015 Promises](http://es6-features.org/#PromiseUsage)
  1365. * although not all of them could logically act as promises, since some events
  1366. * might be fired multpile times whereas promises are to be resolved (or
  1367. * rejected) only once.
  1368. *
  1369. * Events which are also promises include:
  1370. *
  1371. * * [cachedRoster](/docs/html/events.html#cachedroster)
  1372. * * [chatBoxesFetched](/docs/html/events.html#chatBoxesFetched)
  1373. * * [pluginsInitialized](/docs/html/events.html#pluginsInitialized)
  1374. * * [roster](/docs/html/events.html#roster)
  1375. * * [rosterContactsFetched](/docs/html/events.html#rosterContactsFetched)
  1376. * * [rosterGroupsFetched](/docs/html/events.html#rosterGroupsFetched)
  1377. * * [rosterInitialized](/docs/html/events.html#rosterInitialized)
  1378. * * [statusInitialized](/docs/html/events.html#statusInitialized)
  1379. * * [roomsPanelRendered](/docs/html/events.html#roomsPanelRendered)
  1380. *
  1381. * The various plugins might also provide promises, and they do this by using the
  1382. * `promises.add` api method.
  1383. *
  1384. * @namespace _converse.api.promises
  1385. * @memberOf _converse.api
  1386. */
  1387. 'promises': {
  1388. /**
  1389. * By calling `promises.add`, a new [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
  1390. * is made available for other code or plugins to depend on via the
  1391. * {@link _converse.api.waitUntil} method.
  1392. *
  1393. * Generally, it's the responsibility of the plugin which adds the promise to
  1394. * also resolve it.
  1395. *
  1396. * This is done by calling {@link _converse.api.emit}, which not only resolves the
  1397. * promise, but also emits an event with the same name (which can be listened to
  1398. * via {@link _converse.api.listen}).
  1399. *
  1400. * @method _converse.api.promises.add
  1401. * @param {string|array} [name|names] The name or an array of names for the promise(s) to be added
  1402. * @example _converse.api.promises.add('foo-completed');
  1403. */
  1404. 'add' (promises) {
  1405. promises = _.isArray(promises) ? promises : [promises]
  1406. _.each(promises, addPromise);
  1407. }
  1408. },
  1409. /**
  1410. * This namespace lets you access the BOSH tokens
  1411. *
  1412. * @namespace _converse.api.tokens
  1413. * @memberOf _converse.api
  1414. */
  1415. 'tokens': {
  1416. /**
  1417. * @method _converse.api.tokens.get
  1418. * @param {string} [id] The type of token to return ('rid' or 'sid').
  1419. * @returns 'string' A token, either the RID or SID token depending on what's asked for.
  1420. * @example _converse.api.tokens.get('rid');
  1421. */
  1422. 'get' (id) {
  1423. if (!_converse.expose_rid_and_sid || _.isUndefined(_converse.connection)) {
  1424. return null;
  1425. }
  1426. if (id.toLowerCase() === 'rid') {
  1427. return _converse.connection.rid || _converse.connection._proto.rid;
  1428. } else if (id.toLowerCase() === 'sid') {
  1429. return _converse.connection.sid || _converse.connection._proto.sid;
  1430. }
  1431. }
  1432. },
  1433. /**
  1434. * Converse emits events to which you can subscribe to.
  1435. *
  1436. * The `listen` namespace exposes methods for creating event listeners
  1437. * (aka handlers) for these events.
  1438. *
  1439. * @namespace _converse.api.listen
  1440. * @memberOf _converse
  1441. */
  1442. 'listen': {
  1443. /**
  1444. * Lets you listen to an event exactly once.
  1445. *
  1446. * @method _converse.api.listen.once
  1447. * @param {string} name The event's name
  1448. * @param {function} callback The callback method to be called when the event is emitted.
  1449. * @param {object} [context] The value of the `this` parameter for the callback.
  1450. * @example _converse.api.listen.once('message', function (messageXML) { ... });
  1451. */
  1452. 'once': _converse.once.bind(_converse),
  1453. /**
  1454. * Lets you subscribe to an event.
  1455. *
  1456. * Every time the event fires, the callback method specified by `callback` will be called.
  1457. *
  1458. * @method _converse.api.listen.on
  1459. * @param {string} name The event's name
  1460. * @param {function} callback The callback method to be called when the event is emitted.
  1461. * @param {object} [context] The value of the `this` parameter for the callback.
  1462. * @example _converse.api.listen.on('message', function (messageXML) { ... });
  1463. */
  1464. 'on': _converse.on.bind(_converse),
  1465. /**
  1466. * To stop listening to an event, you can use the `not` method.
  1467. *
  1468. * Every time the event fires, the callback method specified by `callback` will be called.
  1469. *
  1470. * @method _converse.api.listen.not
  1471. * @param {string} name The event's name
  1472. * @param {function} callback The callback method that is to no longer be called when the event fires
  1473. * @example _converse.api.listen.not('message', function (messageXML);
  1474. */
  1475. 'not': _converse.off.bind(_converse),
  1476. /**
  1477. * Subscribe to an incoming stanza
  1478. *
  1479. * Every a matched stanza is received, the callback method specified by `callback` will be called.
  1480. *
  1481. * @method _converse.api.listen.stanza
  1482. * @param {string} name The stanza's name
  1483. * @param {object} options Matching options
  1484. * (e.g. 'ns' for namespace, 'type' for stanza type, also 'id' and 'from');
  1485. * @param {function} handler The callback method to be called when the stanza appears
  1486. */
  1487. 'stanza' (name, options, handler) {
  1488. if (_.isFunction(options)) {
  1489. handler = options;
  1490. options = {};
  1491. } else {
  1492. options = options || {};
  1493. }
  1494. _converse.connection.addHandler(
  1495. handler,
  1496. options.ns,
  1497. name,
  1498. options.type,
  1499. options.id,
  1500. options.from,
  1501. options
  1502. );
  1503. },
  1504. },
  1505. /**
  1506. * Wait until a promise is resolved
  1507. *
  1508. * @method _converse.api.waitUntil
  1509. * @param {string} name The name of the promise
  1510. * @returns {Promise}
  1511. */
  1512. 'waitUntil' (name) {
  1513. const promise = _converse.promises[name];
  1514. if (_.isUndefined(promise)) {
  1515. return null;
  1516. }
  1517. return promise;
  1518. },
  1519. /**
  1520. * Allows you to send XML stanzas.
  1521. *
  1522. * @method _converse.api.send
  1523. * @example
  1524. * const msg = converse.env.$msg({
  1525. * 'from': 'juliet@example.com/balcony',
  1526. * 'to': 'romeo@example.net',
  1527. * 'type':'chat'
  1528. * });
  1529. * _converse.api.send(msg);
  1530. */
  1531. 'send' (stanza) {
  1532. _converse.connection.send(stanza);
  1533. },
  1534. /**
  1535. * Send an IQ stanza and receive a promise
  1536. *
  1537. * @method _converse.api.sendIQ
  1538. * @returns {Promise} A promise which resolves when we receive a `result` stanza
  1539. * or is rejected when we receive an `error` stanza.
  1540. */
  1541. 'sendIQ' (stanza) {
  1542. return new Promise((resolve, reject) => {
  1543. _converse.connection.sendIQ(stanza, resolve, reject, _converse.IQ_TIMEOUT);
  1544. });
  1545. }
  1546. };
  1547. /**
  1548. * ### The Public API
  1549. *
  1550. * This namespace contains public API methods which are are
  1551. * accessible on the global `converse` object.
  1552. * They are public, because any JavaScript in the
  1553. * page can call them. Public methods therefore don’t expose any sensitive
  1554. * or closured data. To do that, you’ll need to create a plugin, which has
  1555. * access to the private API method.
  1556. *
  1557. * @namespace converse
  1558. */
  1559. const converse = {
  1560. /**
  1561. * Public API method which initializes Converse.
  1562. * This method must always be called when using Converse.
  1563. *
  1564. * @memberOf converse
  1565. * @method initialize
  1566. * @param {object} config A map of [configuration-settings](https://conversejs.org/docs/html/configuration.html#configuration-settings).
  1567. *
  1568. * @example
  1569. * converse.initialize({
  1570. * allow_otr: true,
  1571. * auto_list_rooms: false,
  1572. * auto_subscribe: false,
  1573. * bosh_service_url: 'https://bind.example.com',
  1574. * hide_muc_server: false,
  1575. * i18n: locales['en'],
  1576. * keepalive: true,
  1577. * play_sounds: true,
  1578. * prebind: false,
  1579. * show_controlbox_by_default: true,
  1580. * debug: false,
  1581. * roster_groups: true
  1582. * });
  1583. */
  1584. 'initialize' (settings, callback) {
  1585. return _converse.initialize(settings, callback);
  1586. },
  1587. /**
  1588. * Exposes methods for adding and removing plugins. You'll need to write a plugin
  1589. * if you want to have access to the private API methods defined further down below.
  1590. *
  1591. * For more information on plugins, read the documentation on [writing a plugin](/docs/html/plugin_development.html).
  1592. *
  1593. * @namespace plugins
  1594. * @memberOf converse
  1595. */
  1596. 'plugins': {
  1597. /** Registers a new plugin.
  1598. *
  1599. * @method converse.plugins.add
  1600. * @param {string} name The name of the plugin
  1601. * @param {object} plugin The plugin object
  1602. *
  1603. * @example
  1604. *
  1605. * const plugin = {
  1606. * initialize: function () {
  1607. * // Gets called as soon as the plugin has been loaded.
  1608. *
  1609. * // Inside this method, you have access to the private
  1610. * // API via `_covnerse.api`.
  1611. *
  1612. * // The private _converse object contains the core logic
  1613. * // and data-structures of Converse.
  1614. * }
  1615. * }
  1616. * converse.plugins.add('myplugin', plugin);
  1617. */
  1618. 'add' (name, plugin) {
  1619. plugin.__name__ = name;
  1620. if (!_.isUndefined(_converse.pluggable.plugins[name])) {
  1621. throw new TypeError(
  1622. `Error: plugin with name "${name}" has already been `+
  1623. 'registered!');
  1624. } else {
  1625. _converse.pluggable.plugins[name] = plugin;
  1626. }
  1627. }
  1628. },
  1629. /**
  1630. * Utility methods and globals from bundled 3rd party libraries.
  1631. * @memberOf converse
  1632. *
  1633. * @property {function} converse.env.$build - Creates a Strophe.Builder, for creating stanza objects.
  1634. * @property {function} converse.env.$iq - Creates a Strophe.Builder with an <iq/> element as the root.
  1635. * @property {function} converse.env.$msg - Creates a Strophe.Builder with an <message/> element as the root.
  1636. * @property {function} converse.env.$pres - Creates a Strophe.Builder with an <presence/> element as the root.
  1637. * @property {object} converse.env.Backbone - The [Backbone](http://backbonejs.org) object used by Converse to create models and views.
  1638. * @property {function} converse.env.Promise - The Promise implementation used by Converse.
  1639. * @property {function} converse.env.Strophe - The [Strophe](http://strophe.im/strophejs) XMPP library used by Converse.
  1640. * @property {object} converse.env._ - The instance of [lodash](http://lodash.com) used by Converse.
  1641. * @property {function} converse.env.f - And instance of Lodash with its methods wrapped to produce immutable auto-curried iteratee-first data-last methods.
  1642. * @property {function} converse.env.b64_sha1 - Utility method from Strophe for creating base64 encoded sha1 hashes.
  1643. * @property {object} converse.env.moment - [Moment](https://momentjs.com) date manipulation library.
  1644. * @property {function} converse.env.sizzle - [Sizzle](https://sizzlejs.com) CSS selector engine.
  1645. * @property {object} converse.env.utils - Module containing common utility methods used by Converse.
  1646. */
  1647. 'env': {
  1648. '$build': $build,
  1649. '$iq': $iq,
  1650. '$msg': $msg,
  1651. '$pres': $pres,
  1652. 'Backbone': Backbone,
  1653. 'Promise': Promise,
  1654. 'Strophe': Strophe,
  1655. '_': _,
  1656. 'f': f,
  1657. 'b64_sha1': b64_sha1,
  1658. 'moment': moment,
  1659. 'sizzle': sizzle,
  1660. 'utils': u
  1661. }
  1662. };
  1663. window.converse = converse;
  1664. window.dispatchEvent(new CustomEvent('converse-loaded'));
  1665. return converse;
  1666. }));