12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784 |
- // Converse.js
- // https://conversejs.org
- //
- // Copyright (c) 2013-2018, the Converse.js developers
- // Licensed under the Mozilla Public License (MPLv2)
- (function (root, factory) {
- define(["sizzle",
- "es6-promise/dist/es6-promise.auto",
- "./lodash.noconflict",
- "./lodash.fp",
- "./polyfill",
- "./i18n",
- "./utils/core",
- "moment",
- "strophe.js",
- "pluggable.js/dist/pluggable",
- "./backbone.noconflict",
- "backbone.nativeview",
- "backbone.browserStorage"
- ], factory);
- }(this, function (sizzle, Promise, _, f, polyfill, i18n, u, moment, Strophe, pluggable, Backbone) {
- "use strict";
- // Strophe globals
- const { $build, $iq, $msg, $pres } = Strophe;
- const b64_sha1 = Strophe.SHA1.b64_sha1;
- Strophe = Strophe.Strophe;
- // Add Strophe Namespaces
- Strophe.addNamespace('CARBONS', 'urn:xmpp:carbons:2');
- Strophe.addNamespace('CHATSTATES', 'http://jabber.org/protocol/chatstates');
- Strophe.addNamespace('CSI', 'urn:xmpp:csi:0');
- Strophe.addNamespace('DELAY', 'urn:xmpp:delay');
- Strophe.addNamespace('FORWARD', 'urn:xmpp:forward:0');
- Strophe.addNamespace('HINTS', 'urn:xmpp:hints');
- Strophe.addNamespace('HTTPUPLOAD', 'urn:xmpp:http:upload:0');
- Strophe.addNamespace('MAM', 'urn:xmpp:mam:2');
- Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
- Strophe.addNamespace('OMEMO', "eu.siacs.conversations.axolotl");
- Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
- Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
- Strophe.addNamespace('REGISTER', 'jabber:iq:register');
- Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
- Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
- Strophe.addNamespace('SID', 'urn:xmpp:sid:0');
- Strophe.addNamespace('SPOILER', 'urn:xmpp:spoiler:0');
- Strophe.addNamespace('VCARD', 'vcard-temp');
- Strophe.addNamespace('VCARDUPDATE', 'vcard-temp:x:update');
- Strophe.addNamespace('XFORM', 'jabber:x:data');
- // Use Mustache style syntax for variable interpolation
- /* Configuration of Lodash templates (this config is distinct to the
- * config of requirejs-tpl in main.js). This one is for normal inline templates.
- */
- _.templateSettings = {
- 'escape': /\{\{\{([\s\S]+?)\}\}\}/g,
- 'evaluate': /\{\[([\s\S]+?)\]\}/g,
- 'interpolate': /\{\{([\s\S]+?)\}\}/g,
- 'imports': { '_': _ }
- };
- /**
- * A private, closured object containing the private api (via `_converse.api`)
- * as well as private methods and internal data-structures.
- *
- * @namespace _converse
- */
- const _converse = {
- 'templates': {},
- 'promises': {}
- }
- _.extend(_converse, Backbone.Events);
- // Core plugins are whitelisted automatically
- _converse.core_plugins = [
- 'converse-autocomplete',
- 'converse-bookmarks',
- 'converse-caps',
- 'converse-chatboxes',
- 'converse-chatboxviews',
- 'converse-chatview',
- 'converse-controlbox',
- 'converse-core',
- 'converse-disco',
- 'converse-dragresize',
- 'converse-embedded',
- 'converse-fullscreen',
- 'converse-headline',
- 'converse-mam',
- 'converse-message-view',
- 'converse-minimize',
- 'converse-modal',
- 'converse-muc',
- 'converse-muc-views',
- 'converse-notification',
- 'converse-omemo',
- 'converse-ping',
- 'converse-profile',
- 'converse-push',
- 'converse-register',
- 'converse-roomslist',
- 'converse-roster',
- 'converse-rosterview',
- 'converse-singleton',
- 'converse-spoilers',
- 'converse-vcard'
- ];
- // Setting wait to 59 instead of 60 to avoid timing conflicts with the
- // webserver, which is often also set to 60 and might therefore sometimes
- // return a 504 error page instead of passing through to the BOSH proxy.
- const BOSH_WAIT = 59;
- // Make converse pluggable
- pluggable.enable(_converse, '_converse', 'pluggable');
- _converse.keycodes = {
- TAB: 9,
- ENTER: 13,
- SHIFT: 16,
- CTRL: 17,
- ALT: 18,
- ESCAPE: 27,
- UP_ARROW: 38,
- DOWN_ARROW: 40,
- FORWARD_SLASH: 47,
- AT: 50,
- META: 91,
- META_RIGHT: 93
- };
- // Module-level constants
- _converse.STATUS_WEIGHTS = {
- 'offline': 6,
- 'unavailable': 5,
- 'xa': 4,
- 'away': 3,
- 'dnd': 2,
- 'chat': 1, // We currently don't differentiate between "chat" and "online"
- 'online': 1
- };
- _converse.PRETTY_CHAT_STATUS = {
- 'offline': 'Offline',
- 'unavailable': 'Unavailable',
- 'xa': 'Extended Away',
- 'away': 'Away',
- 'dnd': 'Do not disturb',
- 'chat': 'Chattty',
- 'online': 'Online'
- };
- _converse.ANONYMOUS = "anonymous";
- _converse.CLOSED = 'closed';
- _converse.EXTERNAL = "external";
- _converse.LOGIN = "login";
- _converse.LOGOUT = "logout";
- _converse.OPENED = 'opened';
- _converse.PREBIND = "prebind";
- _converse.IQ_TIMEOUT = 20000;
- _converse.CONNECTION_STATUS = {
- 0: 'ERROR',
- 1: 'CONNECTING',
- 2: 'CONNFAIL',
- 3: 'AUTHENTICATING',
- 4: 'AUTHFAIL',
- 5: 'CONNECTED',
- 6: 'DISCONNECTED',
- 7: 'DISCONNECTING',
- 8: 'ATTACHED',
- 9: 'REDIRECT',
- 10: 'RECONNECTING',
- };
- _converse.SUCCESS = 'success';
- _converse.FAILURE = 'failure';
- _converse.DEFAULT_IMAGE_TYPE = 'image/png';
- _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==";
- _converse.TIMEOUTS = { // Set as module attr so that we can override in tests.
- 'PAUSED': 10000,
- 'INACTIVE': 90000
- };
- // XEP-0085 Chat states
- // http://xmpp.org/extensions/xep-0085.html
- _converse.INACTIVE = 'inactive';
- _converse.ACTIVE = 'active';
- _converse.COMPOSING = 'composing';
- _converse.PAUSED = 'paused';
- _converse.GONE = 'gone';
- // Chat types
- _converse.PRIVATE_CHAT_TYPE = 'chatbox';
- _converse.CHATROOMS_TYPE = 'chatroom';
- _converse.HEADLINES_TYPE = 'headline';
- _converse.CONTROLBOX_TYPE = 'controlbox';
- // Default configuration values
- // ----------------------------
- _converse.default_settings = {
- allow_non_roster_messaging: false,
- animate: true,
- authentication: 'login', // Available values are "login", "prebind", "anonymous" and "external".
- auto_away: 0, // Seconds after which user status is set to 'away'
- auto_login: false, // Currently only used in connection with anonymous login
- auto_reconnect: true,
- auto_xa: 0, // Seconds after which user status is set to 'xa'
- blacklisted_plugins: [],
- bosh_service_url: undefined,
- connection_options: {},
- credentials_url: null, // URL from where login credentials can be fetched
- csi_waiting_time: 0, // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out.
- debug: false,
- default_state: 'online',
- expose_rid_and_sid: false,
- geouri_regex: /https:\/\/www.openstreetmap.org\/.*#map=[0-9]+\/([\-0-9.]+)\/([\-0-9.]+)\S*/g,
- geouri_replacement: 'https://www.openstreetmap.org/?mlat=$1&mlon=$2#map=18/$1/$2',
- jid: undefined,
- keepalive: true,
- locales_url: 'locale/{{{locale}}}/LC_MESSAGES/converse.json',
- locales: [
- 'af', 'ar', 'bg', 'ca', 'cs', 'de', 'es', 'eu', 'en', 'fr', 'he',
- 'hi', 'hu', 'id', 'it', 'ja', 'nb', 'nl',
- 'pl', 'pt_BR', 'ro', 'ru', 'tr', 'uk', 'zh_CN', 'zh_TW'
- ],
- message_carbons: true,
- nickname: undefined,
- password: undefined,
- prebind_url: null,
- priority: 0,
- rid: undefined,
- root: window.document,
- sid: undefined,
- strict_plugin_dependencies: false,
- trusted: true,
- view_mode: 'overlayed', // Choices are 'overlayed', 'fullscreen', 'mobile'
- websocket_url: undefined,
- whitelisted_plugins: []
- };
- _converse.log = function (message, level, style='') {
- /* Logs messages to the browser's developer console.
- *
- * Parameters:
- * (String) message - The message to be logged.
- * (Integer) level - The loglevel which allows for filtering of log
- * messages.
- *
- * Available loglevels are 0 for 'debug', 1 for 'info', 2 for 'warn',
- * 3 for 'error' and 4 for 'fatal'.
- *
- * When using the 'error' or 'warn' loglevels, a full stacktrace will be
- * logged as well.
- */
- if (level === Strophe.LogLevel.ERROR || level === Strophe.LogLevel.FATAL) {
- style = style || 'color: maroon';
- }
- if (message instanceof Error) {
- message = message.stack;
- } else if (_.isElement(message)) {
- message = message.outerHTML;
- }
- const prefix = style ? '%c' : '';
- const logger = _.assign({
- 'debug': _.get(console, 'log') ? console.log.bind(console) : _.noop,
- 'error': _.get(console, 'log') ? console.log.bind(console) : _.noop,
- 'info': _.get(console, 'log') ? console.log.bind(console) : _.noop,
- 'warn': _.get(console, 'log') ? console.log.bind(console) : _.noop
- }, console);
- if (level === Strophe.LogLevel.ERROR) {
- logger.error(`${prefix} ERROR: ${message}`, style);
- } else if (level === Strophe.LogLevel.WARN) {
- if (_converse.debug) {
- logger.warn(`${prefix} ${moment().format()} WARNING: ${message}`, style);
- }
- } else if (level === Strophe.LogLevel.FATAL) {
- logger.error(`${prefix} FATAL: ${message}`, style);
- } else if (_converse.debug) {
- if (level === Strophe.LogLevel.DEBUG) {
- logger.debug(`${prefix} ${moment().format()} DEBUG: ${message}`, style);
- } else {
- logger.info(`${prefix} ${moment().format()} INFO: ${message}`, style);
- }
- }
- };
- Strophe.log = function (level, msg) { _converse.log(level+' '+msg, level); };
- Strophe.error = function (msg) { _converse.log(msg, Strophe.LogLevel.ERROR); };
- _converse.__ = function (str) {
- /* Translate the given string based on the current locale.
- *
- * Parameters:
- * (String) str - The string to translate.
- */
- if (_.isUndefined(i18n)) {
- return str;
- }
- return i18n.translate.apply(i18n, arguments);
- }
- const __ = _converse.__;
- const PROMISES = [
- 'initialized',
- 'connectionInitialized',
- 'pluginsInitialized',
- 'statusInitialized'
- ];
- function addPromise (promise) {
- /* Private function, used to add a new promise to the ones already
- * available via the `waitUntil` api method.
- */
- _converse.promises[promise] = u.getResolveablePromise();
- }
- _converse.emit = function (name) {
- /* Event emitter and promise resolver */
- _converse.trigger.apply(this, arguments);
- const promise = _converse.promises[name];
- if (!_.isUndefined(promise)) {
- promise.resolve();
- }
- };
- _converse.isSingleton = function () {
- return _.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode);
- }
- _converse.router = new Backbone.Router();
- _converse.initialize = function (settings, callback) {
- settings = !_.isUndefined(settings) ? settings : {};
- const init_promise = u.getResolveablePromise();
- _.each(PROMISES, addPromise);
- if (!_.isUndefined(_converse.connection)) {
- // Looks like _converse.initialized was called again without logging
- // out or disconnecting in the previous session.
- // This happens in tests. We therefore first clean up.
- Backbone.history.stop();
- _converse.chatboxviews.closeAllChatBoxes();
- if (_converse.bookmarks) {
- _converse.bookmarks.reset();
- }
- delete _converse.controlboxtoggle;
- delete _converse.chatboxviews;
- _converse.connection.reset();
- _converse.stopListening();
- _converse.tearDown();
- delete _converse.config;
- _converse.initClientConfig();
- _converse.off();
- }
- if ('onpagehide' in window) {
- // Pagehide gets thrown in more cases than unload. Specifically it
- // gets thrown when the page is cached and not just
- // closed/destroyed. It's the only viable event on mobile Safari.
- // https://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/
- _converse.unloadevent = 'pagehide';
- } else if ('onbeforeunload' in window) {
- _converse.unloadevent = 'beforeunload';
- } else if ('onunload' in window) {
- _converse.unloadevent = 'unload';
- }
- _.assignIn(this, this.default_settings);
- // Allow only whitelisted configuration attributes to be overwritten
- _.assignIn(this, _.pick(settings, _.keys(this.default_settings)));
- if (this.authentication === _converse.ANONYMOUS) {
- if (this.auto_login && !this.jid) {
- throw new Error("Config Error: you need to provide the server's " +
- "domain via the 'jid' option when using anonymous " +
- "authentication with auto_login.");
- }
- }
- /* Localisation */
- if (!_.isUndefined(i18n)) {
- i18n.setLocales(settings.i18n, _converse);
- } else {
- _converse.locale = 'en';
- }
- // Module-level variables
- // ----------------------
- this.callback = callback || _.noop;
- /* When reloading the page:
- * For new sessions, we need to send out a presence stanza to notify
- * the server/network that we're online.
- * When re-attaching to an existing session (e.g. via the keepalive
- * option), we don't need to again send out a presence stanza, because
- * it's as if "we never left" (see onConnectStatusChanged).
- * https://github.com/jcbrand/converse.js/issues/521
- */
- this.send_initial_presence = true;
- this.msg_counter = 0;
- this.user_settings = settings; // Save the user settings so that they can be used by plugins
- // Module-level functions
- // ----------------------
- this.generateResource = () => `/converse.js-${Math.floor(Math.random()*139749528).toString()}`;
- this.sendCSI = function (stat) {
- /* Send out a Chat Status Notification (XEP-0352)
- *
- * Parameters:
- * (String) stat: The user's chat status
- */
- /* Send out a Chat Status Notification (XEP-0352) */
- // XXX if (converse.features[Strophe.NS.CSI] || true) {
- _converse.connection.send($build(stat, {xmlns: Strophe.NS.CSI}));
- _converse.inactive = (stat === _converse.INACTIVE) ? true : false;
- };
- this.onUserActivity = function () {
- /* Resets counters and flags relating to CSI and auto_away/auto_xa */
- if (_converse.idle_seconds > 0) {
- _converse.idle_seconds = 0;
- }
- if (!_converse.connection.authenticated) {
- // We can't send out any stanzas when there's no authenticated connection.
- // converse can happen when the connection reconnects.
- return;
- }
- if (_converse.inactive) {
- _converse.sendCSI(_converse.ACTIVE);
- }
- if (_converse.auto_changed_status === true) {
- _converse.auto_changed_status = false;
- // XXX: we should really remember the original state here, and
- // then set it back to that...
- _converse.xmppstatus.set('status', _converse.default_state);
- }
- };
- this.onEverySecond = function () {
- /* An interval handler running every second.
- * Used for CSI and the auto_away and auto_xa features.
- */
- if (!_converse.connection.authenticated) {
- // We can't send out any stanzas when there's no authenticated connection.
- // This can happen when the connection reconnects.
- return;
- }
- const stat = _converse.xmppstatus.get('status');
- _converse.idle_seconds++;
- if (_converse.csi_waiting_time > 0 &&
- _converse.idle_seconds > _converse.csi_waiting_time &&
- !_converse.inactive) {
- _converse.sendCSI(_converse.INACTIVE);
- }
- if (_converse.auto_away > 0 &&
- _converse.idle_seconds > _converse.auto_away &&
- stat !== 'away' && stat !== 'xa' && stat !== 'dnd') {
- _converse.auto_changed_status = true;
- _converse.xmppstatus.set('status', 'away');
- } else if (_converse.auto_xa > 0 &&
- _converse.idle_seconds > _converse.auto_xa &&
- stat !== 'xa' && stat !== 'dnd') {
- _converse.auto_changed_status = true;
- _converse.xmppstatus.set('status', 'xa');
- }
- };
- this.registerIntervalHandler = function () {
- /* Set an interval of one second and register a handler for it.
- * Required for the auto_away, auto_xa and csi_waiting_time features.
- */
- if (_converse.auto_away < 1 && _converse.auto_xa < 1 && _converse.csi_waiting_time < 1) {
- // Waiting time of less then one second means features aren't used.
- return;
- }
- _converse.idle_seconds = 0
- _converse.auto_changed_status = false; // Was the user's status changed by _converse.js?
- window.addEventListener('click', _converse.onUserActivity);
- window.addEventListener('focus', _converse.onUserActivity);
- window.addEventListener('keypress', _converse.onUserActivity);
- window.addEventListener('mousemove', _converse.onUserActivity);
- const options = {'once': true, 'passive': true};
- window.addEventListener(_converse.unloadevent, _converse.onUserActivity, options);
- _converse.everySecondTrigger = window.setInterval(_converse.onEverySecond, 1000);
- };
- this.setConnectionStatus = function (connection_status, message) {
- _converse.connfeedback.set({
- 'connection_status': connection_status,
- 'message': message
- });
- };
- this.rejectPresenceSubscription = function (jid, message) {
- /* Reject or cancel another user's subscription to our presence updates.
- *
- * Parameters:
- * (String) jid - The Jabber ID of the user whose subscription
- * is being canceled.
- * (String) message - An optional message to the user
- */
- const pres = $pres({to: jid, type: "unsubscribed"});
- if (message && message !== "") { pres.c("status").t(message); }
- _converse.connection.send(pres);
- };
- this.reconnect = _.debounce(function () {
- _converse.log('RECONNECTING');
- _converse.log('The connection has dropped, attempting to reconnect.');
- _converse.setConnectionStatus(
- Strophe.Status.RECONNECTING,
- __('The connection has dropped, attempting to reconnect.')
- );
- _converse.connection.reconnecting = true;
- _converse.tearDown();
- _converse.logIn(null, true);
- }, 3000, {'leading': true});
- this.disconnect = function () {
- _converse.log('DISCONNECTED');
- delete _converse.connection.reconnecting;
- _converse.connection.reset();
- _converse.tearDown();
- _converse.clearSession();
- _converse.emit('disconnected');
- };
- this.onDisconnected = function () {
- /* Gets called once strophe's status reaches Strophe.Status.DISCONNECTED.
- * Will either start a teardown process for converse.js or attempt
- * to reconnect.
- */
- const reason = _converse.disconnection_reason;
- if (_converse.disconnection_cause === Strophe.Status.AUTHFAIL) {
- if (_converse.credentials_url && _converse.auto_reconnect) {
- /* In this case, we reconnect, because we might be receiving
- * expirable tokens from the credentials_url.
- */
- _converse.emit('will-reconnect');
- return _converse.reconnect();
- } else {
- return _converse.disconnect();
- }
- } else if (_converse.disconnection_cause === _converse.LOGOUT ||
- (!_.isUndefined(reason) && reason === _.get(Strophe, 'ErrorCondition.NO_AUTH_MECH')) ||
- reason === "host-unknown" ||
- reason === "remote-connection-failed" ||
- !_converse.auto_reconnect) {
- return _converse.disconnect();
- }
- _converse.emit('will-reconnect');
- _converse.reconnect();
- };
- this.setDisconnectionCause = function (cause, reason, override) {
- /* Used to keep track of why we got disconnected, so that we can
- * decide on what the next appropriate action is (in onDisconnected)
- */
- if (_.isUndefined(cause)) {
- delete _converse.disconnection_cause;
- delete _converse.disconnection_reason;
- } else if (_.isUndefined(_converse.disconnection_cause) || override) {
- _converse.disconnection_cause = cause;
- _converse.disconnection_reason = reason;
- }
- };
- this.onConnectStatusChanged = function (status, message) {
- /* Callback method called by Strophe as the Strophe.Connection goes
- * through various states while establishing or tearing down a
- * connection.
- */
- _converse.log(`Status changed to: ${_converse.CONNECTION_STATUS[status]}`);
- if (status === Strophe.Status.CONNECTED || status === Strophe.Status.ATTACHED) {
- _converse.setConnectionStatus(status);
- // By default we always want to send out an initial presence stanza.
- _converse.send_initial_presence = true;
- _converse.setDisconnectionCause();
- if (_converse.connection.reconnecting) {
- _converse.log(status === Strophe.Status.CONNECTED ? 'Reconnected' : 'Reattached');
- _converse.onConnected(true);
- } else {
- _converse.log(status === Strophe.Status.CONNECTED ? 'Connected' : 'Attached');
- if (_converse.connection.restored) {
- // No need to send an initial presence stanza when
- // we're restoring an existing session.
- _converse.send_initial_presence = false;
- }
- _converse.onConnected();
- }
- } else if (status === Strophe.Status.DISCONNECTED) {
- _converse.setDisconnectionCause(status, message);
- _converse.onDisconnected();
- } else if (status === Strophe.Status.ERROR) {
- _converse.setConnectionStatus(
- status,
- __('An error occurred while connecting to the chat server.')
- );
- } else if (status === Strophe.Status.CONNECTING) {
- _converse.setConnectionStatus(status);
- } else if (status === Strophe.Status.AUTHENTICATING) {
- _converse.setConnectionStatus(status);
- } else if (status === Strophe.Status.AUTHFAIL) {
- if (!message) {
- message = __('Your Jabber ID and/or password is incorrect. Please try again.');
- }
- _converse.setConnectionStatus(status, message);
- _converse.setDisconnectionCause(status, message, true);
- _converse.onDisconnected();
- } else if (status === Strophe.Status.CONNFAIL) {
- let feedback = message;
- if (message === "host-unknown" || message == "remote-connection-failed") {
- feedback = __("Sorry, we could not connect to the XMPP host with domain: %1$s",
- `\"${Strophe.getDomainFromJid(_converse.connection.jid)}\"`);
- } else if (!_.isUndefined(message) && message === _.get(Strophe, 'ErrorCondition.NO_AUTH_MECH')) {
- feedback = __("The XMPP server did not offer a supported authentication mechanism");
- }
- _converse.setConnectionStatus(status, feedback);
- _converse.setDisconnectionCause(status, message);
- } else if (status === Strophe.Status.DISCONNECTING) {
- _converse.setDisconnectionCause(status, message);
- }
- };
- this.incrementMsgCounter = function () {
- this.msg_counter += 1;
- const unreadMsgCount = this.msg_counter;
- let title = document.title;
- if (_.isNil(title)) {
- return;
- }
- if (title.search(/^Messages \(\d+\) /) === -1) {
- title = `Messages (${unreadMsgCount}) ${title}`;
- } else {
- title = title.replace(/^Messages \(\d+\) /, `Messages (${unreadMsgCount})`);
- }
- };
- this.clearMsgCounter = function () {
- this.msg_counter = 0;
- let title = document.title;
- if (_.isNil(title)) {
- return;
- }
- if (title.search(/^Messages \(\d+\) /) !== -1) {
- title = title.replace(/^Messages \(\d+\) /, "");
- }
- };
- this.initStatus = (reconnecting) => {
- // If there's no xmppstatus obj, then we were never connected to
- // begin with, so we set reconnecting to false.
- reconnecting = _.isUndefined(_converse.xmppstatus) ? false : reconnecting;
- if (reconnecting) {
- _converse.onStatusInitialized(reconnecting);
- } else {
- const id = `converse.xmppstatus-${_converse.bare_jid}`;
- this.xmppstatus = new this.XMPPStatus({'id': id});
- this.xmppstatus.browserStorage = new Backbone.BrowserStorage.session(id);
- this.xmppstatus.fetch({
- 'success': _.partial(_converse.onStatusInitialized, reconnecting),
- 'error': _.partial(_converse.onStatusInitialized, reconnecting)
- });
- }
- }
- this.initClientConfig = function () {
- /* The client config refers to configuration of the client which is
- * independent of any particular user.
- * What this means is that config values need to persist across
- * user sessions.
- */
- const id = b64_sha1('converse.client-config');
- _converse.config = new Backbone.Model({
- 'id': id,
- 'trusted': _converse.trusted && true || false,
- 'storage': _converse.trusted ? 'local' : 'session'
- });
- _converse.config.browserStorage = new Backbone.BrowserStorage.session(id);
- _converse.config.fetch();
- _converse.emit('clientConfigInitialized');
- };
- this.initSession = function () {
- const id = b64_sha1('converse.bosh-session');
- _converse.session = new Backbone.Model({'id': id});
- _converse.session.browserStorage = new Backbone.BrowserStorage.session(id);
- _converse.session.fetch();
- _converse.emit('sessionInitialized');
- };
- this.clearSession = function () {
- if (!_converse.config.get('trusted')) {
- window.localStorage.clear();
- window.sessionStorage.clear();
- } else if (!_.isUndefined(this.session) && this.session.browserStorage) {
- this.session.browserStorage._clear();
- }
- _converse.emit('clearSession');
- };
- this.logOut = function () {
- _converse.clearSession();
- _converse.setDisconnectionCause(_converse.LOGOUT, undefined, true);
- if (!_.isUndefined(_converse.connection)) {
- _converse.connection.disconnect();
- } else {
- _converse.tearDown();
- }
- // Recreate all the promises
- _.each(_.keys(_converse.promises), addPromise);
- _converse.emit('logout');
- };
- this.saveWindowState = function (ev, hidden) {
- // XXX: eventually we should be able to just use
- // document.visibilityState (when we drop support for older
- // browsers).
- let state;
- const event_map = {
- 'focus': "visible",
- 'focusin': "visible",
- 'pageshow': "visible",
- 'blur': "hidden",
- 'focusout': "hidden",
- 'pagehide': "hidden"
- };
- ev = ev || document.createEvent('Events');
- if (ev.type in event_map) {
- state = event_map[ev.type];
- } else {
- state = document[hidden] ? "hidden" : "visible";
- }
- if (state === 'visible') {
- _converse.clearMsgCounter();
- }
- _converse.windowState = state;
- _converse.emit('windowStateChanged', {state});
- };
- this.registerGlobalEventHandlers = function () {
- // Taken from:
- // http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active
- let hidden = "hidden";
- // Standards:
- if (hidden in document) {
- document.addEventListener("visibilitychange", _.partial(_converse.saveWindowState, _, hidden));
- } else if ((hidden = "mozHidden") in document) {
- document.addEventListener("mozvisibilitychange", _.partial(_converse.saveWindowState, _, hidden));
- } else if ((hidden = "webkitHidden") in document) {
- document.addEventListener("webkitvisibilitychange", _.partial(_converse.saveWindowState, _, hidden));
- } else if ((hidden = "msHidden") in document) {
- document.addEventListener("msvisibilitychange", _.partial(_converse.saveWindowState, _, hidden));
- } else if ("onfocusin" in document) {
- // IE 9 and lower:
- document.onfocusin = document.onfocusout = _.partial(_converse.saveWindowState, _, hidden);
- } else {
- // All others:
- window.onpageshow = window.onpagehide = window.onfocus = window.onblur = _.partial(_converse.saveWindowState, _, hidden);
- }
- // set the initial state (but only if browser supports the Page Visibility API)
- if( document[hidden] !== undefined ) {
- _.partial(_converse.saveWindowState, _, hidden)({type: document[hidden] ? "blur" : "focus"});
- }
- _converse.emit('registeredGlobalEventHandlers');
- };
- this.enableCarbons = function () {
- /* Ask the XMPP server to enable Message Carbons
- * See XEP-0280 https://xmpp.org/extensions/xep-0280.html#enabling
- */
- if (!this.message_carbons || this.session.get('carbons_enabled')) {
- return;
- }
- const carbons_iq = new Strophe.Builder('iq', {
- 'from': this.connection.jid,
- 'id': 'enablecarbons',
- 'type': 'set'
- })
- .c('enable', {xmlns: Strophe.NS.CARBONS});
- this.connection.addHandler((iq) => {
- if (iq.querySelectorAll('error').length > 0) {
- _converse.log(
- 'An error occurred while trying to enable message carbons.',
- Strophe.LogLevel.WARN);
- } else {
- this.session.save({'carbons_enabled': true});
- _converse.log('Message carbons have been enabled.');
- }
- }, null, "iq", null, "enablecarbons");
- this.connection.send(carbons_iq);
- };
- this.sendInitialPresence = function () {
- if (_converse.send_initial_presence) {
- _converse.xmppstatus.sendPresence();
- }
- };
- this.onStatusInitialized = function (reconnecting) {
- _converse.emit('statusInitialized', reconnecting);
- if (reconnecting) {
- _converse.emit('reconnected');
- } else {
- init_promise.resolve();
- _converse.emit('initialized');
- _converse.emit('connected');
- }
- };
- this.setUserJID = function () {
- _converse.jid = _converse.connection.jid;
- _converse.bare_jid = Strophe.getBareJidFromJid(_converse.connection.jid);
- _converse.resource = Strophe.getResourceFromJid(_converse.connection.jid);
- _converse.domain = Strophe.getDomainFromJid(_converse.connection.jid);
- _converse.emit('setUserJID');
- };
- this.onConnected = function (reconnecting) {
- /* Called as soon as a new connection has been established, either
- * by logging in or by attaching to an existing BOSH session.
- */
- _converse.connection.flush(); // Solves problem of returned PubSub BOSH response not received by browser
- _converse.setUserJID();
- _converse.initSession();
- _converse.enableCarbons();
- _converse.initStatus(reconnecting)
- };
- this.ConnectionFeedback = Backbone.Model.extend({
- defaults: {
- 'connection_status': Strophe.Status.DISCONNECTED,
- 'message': ''
- },
- initialize () {
- this.on('change', () => {
- _converse.emit('connfeedback', _converse.connfeedback);
- });
- }
- });
- this.connfeedback = new this.ConnectionFeedback();
- this.XMPPStatus = Backbone.Model.extend({
- defaults () {
- return {
- "jid": _converse.bare_jid,
- "status": _converse.default_state
- }
- },
- initialize () {
- this.vcard = _converse.vcards.findWhere({'jid': this.get('jid')});
- if (_.isNil(this.vcard)) {
- this.vcard = _converse.vcards.create({'jid': this.get('jid')});
- }
- this.on('change:status', (item) => {
- const status = this.get('status');
- this.sendPresence(status);
- _converse.emit('statusChanged', status);
- });
- this.on('change:status_message', () => {
- const status_message = this.get('status_message');
- this.sendPresence(this.get('status'), status_message);
- _converse.emit('statusMessageChanged', status_message);
- });
- },
- constructPresence (type, status_message) {
- let presence;
- type = _.isString(type) ? type : (this.get('status') || _converse.default_state);
- status_message = _.isString(status_message) ? status_message : this.get('status_message');
- // Most of these presence types are actually not explicitly sent,
- // but I add all of them here for reference and future proofing.
- if ((type === 'unavailable') ||
- (type === 'probe') ||
- (type === 'error') ||
- (type === 'unsubscribe') ||
- (type === 'unsubscribed') ||
- (type === 'subscribe') ||
- (type === 'subscribed')) {
- presence = $pres({'type': type});
- } else if (type === 'offline') {
- presence = $pres({'type': 'unavailable'});
- } else if (type === 'online') {
- presence = $pres();
- } else {
- presence = $pres().c('show').t(type).up();
- }
- if (status_message) {
- presence.c('status').t(status_message).up();
- }
- presence.c('priority').t(
- _.isNaN(Number(_converse.priority)) ? 0 : _converse.priority
- );
- return presence;
- },
- sendPresence (type, status_message) {
- _converse.connection.send(this.constructPresence(type, status_message));
- }
- });
- this.setUpXMLLogging = function () {
- Strophe.log = function (level, msg) {
- _converse.log(msg, level);
- };
- if (this.debug) {
- this.connection.xmlInput = function (body) {
- _converse.log(body.outerHTML, Strophe.LogLevel.DEBUG, 'color: darkgoldenrod');
- };
- this.connection.xmlOutput = function (body) {
- _converse.log(body.outerHTML, Strophe.LogLevel.DEBUG, 'color: darkcyan');
- };
- }
- };
- this.fetchLoginCredentials = () =>
- new Promise((resolve, reject) => {
- const xhr = new XMLHttpRequest();
- xhr.open('GET', _converse.credentials_url, true);
- xhr.setRequestHeader('Accept', "application/json, text/javascript");
- xhr.onload = function() {
- if (xhr.status >= 200 && xhr.status < 400) {
- const data = JSON.parse(xhr.responseText);
- resolve({
- 'jid': data.jid,
- 'password': data.password
- });
- } else {
- xhr.onerror();
- }
- };
- xhr.onerror = function () {
- delete _converse.connection;
- _converse.emit('noResumeableSession', this);
- reject(xhr.responseText);
- };
- xhr.send();
- });
- this.startNewBOSHSession = function () {
- const xhr = new XMLHttpRequest();
- xhr.open('GET', _converse.prebind_url, true);
- xhr.setRequestHeader('Accept', "application/json, text/javascript");
- xhr.onload = function() {
- if (xhr.status >= 200 && xhr.status < 400) {
- const data = JSON.parse(xhr.responseText);
- _converse.connection.attach(
- data.jid, data.sid, data.rid,
- _converse.onConnectStatusChanged);
- } else {
- xhr.onerror();
- }
- };
- xhr.onerror = function () {
- delete _converse.connection;
- _converse.emit('noResumeableSession', this);
- };
- xhr.send();
- };
- this.restoreBOSHSession = function (jid_is_required) {
- /* Tries to restore a cached BOSH session. */
- if (!this.jid) {
- const msg = "restoreBOSHSession: tried to restore a \"keepalive\" session "+
- "but we don't have the JID for the user!";
- if (jid_is_required) {
- throw new Error(msg);
- } else {
- _converse.log(msg);
- }
- }
- try {
- this.connection.restore(this.jid, this.onConnectStatusChanged);
- return true;
- } catch (e) {
- _converse.log(
- "Could not restore session for jid: "+
- this.jid+" Error message: "+e.message, Strophe.LogLevel.WARN);
- this.clearSession(); // We want to clear presences (see #555)
- return false;
- }
- };
- this.attemptPreboundSession = function (reconnecting) {
- /* Handle session resumption or initialization when prebind is
- * being used.
- */
- if (!reconnecting) {
- if (this.keepalive && this.restoreBOSHSession(true)) {
- return;
- }
- // No keepalive, or session resumption has failed.
- if (this.jid && this.sid && this.rid) {
- return this.connection.attach(
- this.jid, this.sid, this.rid,
- this.onConnectStatusChanged
- );
- }
- }
- if (this.prebind_url) {
- return this.startNewBOSHSession();
- } else {
- throw new Error(
- "attemptPreboundSession: If you use prebind and not keepalive, "+
- "then you MUST supply JID, RID and SID values or a prebind_url.");
- }
- };
- this.attemptNonPreboundSession = function (credentials, reconnecting) {
- /* Handle session resumption or initialization when prebind is not being used.
- *
- * Two potential options exist and are handled in this method:
- * 1. keepalive
- * 2. auto_login
- */
- if (!reconnecting && this.keepalive && this.restoreBOSHSession()) {
- return;
- }
- if (credentials) {
- // When credentials are passed in, they override prebinding
- // or credentials fetching via HTTP
- this.autoLogin(credentials);
- } else if (this.auto_login) {
- if (this.credentials_url) {
- this.fetchLoginCredentials().then(
- this.autoLogin.bind(this),
- this.autoLogin.bind(this)
- );
- } else if (!this.jid) {
- throw new Error(
- "attemptNonPreboundSession: If you use auto_login, "+
- "you also need to give either a jid value (and if "+
- "applicable a password) or you need to pass in a URL "+
- "from where the username and password can be fetched "+
- "(via credentials_url)."
- );
- } else {
- this.autoLogin(); // Could be ANONYMOUS or EXTERNAL
- }
- } else if (reconnecting) {
- this.autoLogin();
- }
- };
- this.autoLogin = function (credentials) {
- if (credentials) {
- // If passed in, the credentials come from credentials_url,
- // so we set them on the converse object.
- this.jid = credentials.jid;
- }
- if (this.authentication === _converse.ANONYMOUS || this.authentication === _converse.EXTERNAL) {
- if (!this.jid) {
- throw new Error("Config Error: when using anonymous login " +
- "you need to provide the server's domain via the 'jid' option. " +
- "Either when calling converse.initialize, or when calling " +
- "_converse.api.user.login.");
- }
- if (!this.connection.reconnecting) {
- this.connection.reset();
- }
- this.connection.connect(this.jid.toLowerCase(), null, this.onConnectStatusChanged, BOSH_WAIT);
- } else if (this.authentication === _converse.LOGIN) {
- const password = _.isNil(credentials) ? (_converse.connection.pass || this.password) : credentials.password;
- if (!password) {
- if (this.auto_login) {
- throw new Error("initConnection: If you use auto_login and "+
- "authentication='login' then you also need to provide a password.");
- }
- _converse.setDisconnectionCause(Strophe.Status.AUTHFAIL, undefined, true);
- _converse.disconnect();
- return;
- }
- const resource = Strophe.getResourceFromJid(this.jid);
- if (!resource) {
- this.jid = this.jid.toLowerCase() + _converse.generateResource();
- } else {
- this.jid = Strophe.getBareJidFromJid(this.jid).toLowerCase()+'/'+resource;
- }
- if (!this.connection.reconnecting) {
- this.connection.reset();
- }
- this.connection.connect(this.jid, password, this.onConnectStatusChanged, BOSH_WAIT);
- }
- };
- this.logIn = function (credentials, reconnecting) {
- // We now try to resume or automatically set up a new session.
- // Otherwise the user will be shown a login form.
- if (this.authentication === _converse.PREBIND) {
- this.attemptPreboundSession(reconnecting);
- } else {
- this.attemptNonPreboundSession(credentials, reconnecting);
- }
- };
- this.initConnection = function () {
- /* Creates a new Strophe.Connection instance if we don't already have one.
- */
- if (!this.connection) {
- if (!this.bosh_service_url && ! this.websocket_url) {
- throw new Error("initConnection: you must supply a value for either the bosh_service_url or websocket_url or both.");
- }
- if (('WebSocket' in window || 'MozWebSocket' in window) && this.websocket_url) {
- this.connection = new Strophe.Connection(this.websocket_url, this.connection_options);
- } else if (this.bosh_service_url) {
- this.connection = new Strophe.Connection(
- this.bosh_service_url,
- _.assignIn(this.connection_options, {'keepalive': this.keepalive})
- );
- } else {
- throw new Error("initConnection: this browser does not support websockets and bosh_service_url wasn't specified.");
- }
- }
- _converse.emit('connectionInitialized');
- };
- this.tearDown = function () {
- /* Remove those views which are only allowed with a valid
- * connection.
- */
- _converse.emit('beforeTearDown');
- if (!_.isUndefined(_converse.session)) {
- _converse.session.destroy();
- }
- window.removeEventListener('click', _converse.onUserActivity);
- window.removeEventListener('focus', _converse.onUserActivity);
- window.removeEventListener('keypress', _converse.onUserActivity);
- window.removeEventListener('mousemove', _converse.onUserActivity);
- window.removeEventListener(_converse.unloadevent, _converse.onUserActivity);
- window.clearInterval(_converse.everySecondTrigger);
- _converse.emit('afterTearDown');
- return _converse;
- };
- this.initPlugins = function () {
- // If initialize gets called a second time (e.g. during tests), then we
- // need to re-apply all plugins (for a new converse instance), and we
- // therefore need to clear this array that prevents plugins from being
- // initialized twice.
- // If initialize is called for the first time, then this array is empty
- // in any case.
- _converse.pluggable.initialized_plugins = [];
- const whitelist = _converse.core_plugins.concat(
- _converse.whitelisted_plugins);
- if (_converse.view_mode === 'embedded') {
- _.forEach([ // eslint-disable-line lodash/prefer-map
- "converse-bookmarks",
- "converse-controlbox",
- "converse-headline",
- "converse-register"
- ], (name) => {
- _converse.blacklisted_plugins.push(name)
- });
- }
- _converse.pluggable.initializePlugins({
- 'updateSettings' () {
- _converse.log(
- "(DEPRECATION) "+
- "The `updateSettings` method has been deprecated. "+
- "Please use `_converse.api.settings.update` instead.",
- Strophe.LogLevel.WARN
- )
- _converse.api.settings.update.apply(_converse, arguments);
- },
- '_converse': _converse
- }, whitelist, _converse.blacklisted_plugins);
- _converse.emit('pluginsInitialized');
- };
- // Initialization
- // --------------
- // This is the end of the initialize method.
- if (settings.connection) {
- this.connection = settings.connection;
- }
- function finishInitialization () {
- _converse.initPlugins();
- _converse.initClientConfig();
- _converse.initConnection();
- _converse.setUpXMLLogging();
- _converse.logIn();
- _converse.registerGlobalEventHandlers();
- if (!Backbone.history.started) {
- Backbone.history.start();
- }
- }
- if (!_.isUndefined(_converse.connection) &&
- _converse.connection.service === 'jasmine tests') {
- finishInitialization();
- return _converse;
- } else if (_.isUndefined(i18n)) {
- finishInitialization();
- } else {
- i18n.fetchTranslations(
- _converse.locale,
- _converse.locales,
- u.interpolate(_converse.locales_url, {'locale': _converse.locale}))
- .catch(e => _converse.log(e.message, Strophe.LogLevel.FATAL))
- .then(finishInitialization)
- .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
- }
- return init_promise;
- };
- /**
- * ### The private API
- *
- * The private API methods are only accessible via the closured {@link _converse}
- * object, which is only available to plugins.
- *
- * These methods are kept private (i.e. not global) because they may return
- * sensitive data which should be kept off-limits to other 3rd-party scripts
- * that might be running in the page.
- *
- * @namespace _converse.api
- * @memberOf _converse
- */
- _converse.api = {
- /**
- * This grouping collects API functions related to the XMPP connection.
- *
- * @namespace _converse.api.connection
- * @memberOf _converse.api
- */
- 'connection': {
- /**
- * @method _converse.api.connection.connected
- * @memberOf _converse.api.connection
- * @returns {boolean} Whether there is an established connection or not.
- */
- 'connected' () {
- return _converse.connection && _converse.connection.connected || false;
- },
- /**
- * Terminates the connection.
- *
- * @method _converse.api.connection.disconnect
- * @memberOf _converse.api.connection
- */
- 'disconnect' () {
- _converse.connection.disconnect();
- },
- },
- /**
- * Lets you emit (i.e. trigger) events, which can be listened to via
- * {@link _converse.api.listen.on} or {@link _converse.api.listen.once}
- * (see [_converse.api.listen](http://localhost:8000/docs/html/api/-_converse.api.listen.html)).
- *
- * @method _converse.api.emit
- */
- 'emit' () {
- _converse.emit.apply(_converse, arguments);
- },
- /**
- * This grouping collects API functions related to the current logged in user.
- *
- * @namespace _converse.api.user
- * @memberOf _converse.api
- */
- 'user': {
- /**
- * @method _converse.api.user.jid
- * @returns {string} The current user's full JID (Jabber ID)
- * @example _converse.api.user.jid())
- */
- 'jid' () {
- return _converse.connection.jid;
- },
- /**
- * Logs the user in.
- *
- * If called without any parameters, Converse will try
- * to log the user in by calling the `prebind_url` or `credentials_url` depending
- * on whether prebinding is used or not.
- *
- * @method _converse.api.user.login
- * @param {object} [credentials] An object with the credentials.
- * @example
- * converse.plugins.add('myplugin', {
- * initialize: function () {
- *
- * this._converse.api.user.login({
- * 'jid': 'dummy@example.com',
- * 'password': 'secret'
- * });
- *
- * }
- * });
- */
- 'login' (credentials) {
- _converse.logIn(credentials);
- },
- /**
- * Logs the user out of the current XMPP session.
- *
- * @method _converse.api.user.logout
- * @example _converse.api.user.logout();
- */
- 'logout' () {
- _converse.logOut();
- },
- /**
- * Set and get the user's chat status, also called their *availability*.
- *
- * @namespace _converse.api.user.status
- * @memberOf _converse.api.user
- */
- 'status': {
- /** Return the current user's availability status.
- *
- * @method _converse.api.user.status.get
- * @example _converse.api.user.status.get();
- */
- 'get' () {
- return _converse.xmppstatus.get('status');
- },
- /**
- * The user's status can be set to one of the following values:
- *
- * @method _converse.api.user.status.set
- * @param {string} value The user's chat status (e.g. 'away', 'dnd', 'offline', 'online', 'unavailable' or 'xa')
- * @param {string} [message] A custom status message
- *
- * @example this._converse.api.user.status.set('dnd');
- * @example this._converse.api.user.status.set('dnd', 'In a meeting');
- */
- 'set' (value, message) {
- const data = {'status': value};
- if (!_.includes(_.keys(_converse.STATUS_WEIGHTS), value)) {
- throw new Error('Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1');
- }
- if (_.isString(message)) {
- data.status_message = message;
- }
- _converse.xmppstatus.sendPresence(value);
- _converse.xmppstatus.save(data);
- },
- /**
- * Set and retrieve the user's custom status message.
- *
- * @namespace _converse.api.user.status.message
- * @memberOf _converse.api.user.status
- */
- 'message': {
- /**
- * @method _converse.api.user.status.message.get
- * @returns {string} The status message
- * @example const message = _converse.api.user.status.message.get()
- */
- 'get' () {
- return _converse.xmppstatus.get('status_message');
- },
- /**
- * @method _converse.api.user.status.message.set
- * @param {string} status The status message
- * @example _converse.api.user.status.message.set('In a meeting');
- */
- 'set' (status) {
- _converse.xmppstatus.save({'status_message': status});
- }
- }
- },
- },
- /**
- * This grouping allows access to the
- * [configuration settings](/docs/html/configuration.html#configuration-settings)
- * of Converse.
- *
- * @namespace _converse.api.settings
- * @memberOf _converse.api
- */
- 'settings': {
- /**
- * Allows new configuration settings to be specified, or new default values for
- * existing configuration settings to be specified.
- *
- * @method _converse.api.settings.update
- * @param {object} settings The configuration settings
- * @example
- * _converse.api.settings.update({
- * 'enable_foo': true
- * });
- *
- * // The user can then override the default value of the configuration setting when
- * // calling `converse.initialize`.
- * converse.initialize({
- * 'enable_foo': false
- * });
- */
- 'update' (settings) {
- u.merge(_converse.default_settings, settings);
- u.merge(_converse, settings);
- u.applyUserSettings(_converse, settings, _converse.user_settings);
- },
- /**
- * @method _converse.api.settings.get
- * @returns {*} Value of the particular configuration setting.
- * @example _converse.api.settings.get("play_sounds");
- */
- 'get' (key) {
- if (_.includes(_.keys(_converse.default_settings), key)) {
- return _converse[key];
- }
- },
- /**
- * Set one or many configuration settings.
- *
- * Note, this is not an alternative to calling {@link converse.initialize}, which still needs
- * to be called. Generally, you'd use this method after Converse is already
- * running and you want to change the configuration on-the-fly.
- *
- * @method _converse.api.settings.set
- * @param {Object} [settings] An object containing configuration settings.
- * @param {string} [key] Alternatively to passing in an object, you can pass in a key and a value.
- * @param {string} [value]
- * @example _converse.api.settings.set("play_sounds", true);
- * @example
- * _converse.api.settings.set({
- * "play_sounds", true,
- * "hide_offline_users" true
- * });
- */
- 'set' (key, val) {
- const o = {};
- if (_.isObject(key)) {
- _.assignIn(_converse, _.pick(key, _.keys(_converse.default_settings)));
- } else if (_.isString("string")) {
- o[key] = val;
- _.assignIn(_converse, _.pick(o, _.keys(_converse.default_settings)));
- }
- }
- },
- /**
- * Converse and its plugins emit various events which you can listen to via the
- * {@link _converse.api.listen} namespace.
- *
- * Some of these events are also available as [ES2015 Promises](http://es6-features.org/#PromiseUsage)
- * although not all of them could logically act as promises, since some events
- * might be fired multpile times whereas promises are to be resolved (or
- * rejected) only once.
- *
- * Events which are also promises include:
- *
- * * [cachedRoster](/docs/html/events.html#cachedroster)
- * * [chatBoxesFetched](/docs/html/events.html#chatBoxesFetched)
- * * [pluginsInitialized](/docs/html/events.html#pluginsInitialized)
- * * [roster](/docs/html/events.html#roster)
- * * [rosterContactsFetched](/docs/html/events.html#rosterContactsFetched)
- * * [rosterGroupsFetched](/docs/html/events.html#rosterGroupsFetched)
- * * [rosterInitialized](/docs/html/events.html#rosterInitialized)
- * * [statusInitialized](/docs/html/events.html#statusInitialized)
- * * [roomsPanelRendered](/docs/html/events.html#roomsPanelRendered)
- *
- * The various plugins might also provide promises, and they do this by using the
- * `promises.add` api method.
- *
- * @namespace _converse.api.promises
- * @memberOf _converse.api
- */
- 'promises': {
- /**
- * By calling `promises.add`, a new [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
- * is made available for other code or plugins to depend on via the
- * {@link _converse.api.waitUntil} method.
- *
- * Generally, it's the responsibility of the plugin which adds the promise to
- * also resolve it.
- *
- * This is done by calling {@link _converse.api.emit}, which not only resolves the
- * promise, but also emits an event with the same name (which can be listened to
- * via {@link _converse.api.listen}).
- *
- * @method _converse.api.promises.add
- * @param {string|array} [name|names] The name or an array of names for the promise(s) to be added
- * @example _converse.api.promises.add('foo-completed');
- */
- 'add' (promises) {
- promises = _.isArray(promises) ? promises : [promises]
- _.each(promises, addPromise);
- }
- },
- /**
- * This namespace lets you access the BOSH tokens
- *
- * @namespace _converse.api.tokens
- * @memberOf _converse.api
- */
- 'tokens': {
- /**
- * @method _converse.api.tokens.get
- * @param {string} [id] The type of token to return ('rid' or 'sid').
- * @returns 'string' A token, either the RID or SID token depending on what's asked for.
- * @example _converse.api.tokens.get('rid');
- */
- 'get' (id) {
- if (!_converse.expose_rid_and_sid || _.isUndefined(_converse.connection)) {
- return null;
- }
- if (id.toLowerCase() === 'rid') {
- return _converse.connection.rid || _converse.connection._proto.rid;
- } else if (id.toLowerCase() === 'sid') {
- return _converse.connection.sid || _converse.connection._proto.sid;
- }
- }
- },
- /**
- * Converse emits events to which you can subscribe to.
- *
- * The `listen` namespace exposes methods for creating event listeners
- * (aka handlers) for these events.
- *
- * @namespace _converse.api.listen
- * @memberOf _converse
- */
- 'listen': {
- /**
- * Lets you listen to an event exactly once.
- *
- * @method _converse.api.listen.once
- * @param {string} name The event's name
- * @param {function} callback The callback method to be called when the event is emitted.
- * @param {object} [context] The value of the `this` parameter for the callback.
- * @example _converse.api.listen.once('message', function (messageXML) { ... });
- */
- 'once': _converse.once.bind(_converse),
- /**
- * Lets you subscribe to an event.
- *
- * Every time the event fires, the callback method specified by `callback` will be called.
- *
- * @method _converse.api.listen.on
- * @param {string} name The event's name
- * @param {function} callback The callback method to be called when the event is emitted.
- * @param {object} [context] The value of the `this` parameter for the callback.
- * @example _converse.api.listen.on('message', function (messageXML) { ... });
- */
- 'on': _converse.on.bind(_converse),
- /**
- * To stop listening to an event, you can use the `not` method.
- *
- * Every time the event fires, the callback method specified by `callback` will be called.
- *
- * @method _converse.api.listen.not
- * @param {string} name The event's name
- * @param {function} callback The callback method that is to no longer be called when the event fires
- * @example _converse.api.listen.not('message', function (messageXML);
- */
- 'not': _converse.off.bind(_converse),
- /**
- * Subscribe to an incoming stanza
- *
- * Every a matched stanza is received, the callback method specified by `callback` will be called.
- *
- * @method _converse.api.listen.stanza
- * @param {string} name The stanza's name
- * @param {object} options Matching options
- * (e.g. 'ns' for namespace, 'type' for stanza type, also 'id' and 'from');
- * @param {function} handler The callback method to be called when the stanza appears
- */
- 'stanza' (name, options, handler) {
- if (_.isFunction(options)) {
- handler = options;
- options = {};
- } else {
- options = options || {};
- }
- _converse.connection.addHandler(
- handler,
- options.ns,
- name,
- options.type,
- options.id,
- options.from,
- options
- );
- },
- },
- /**
- * Wait until a promise is resolved
- *
- * @method _converse.api.waitUntil
- * @param {string} name The name of the promise
- * @returns {Promise}
- */
- 'waitUntil' (name) {
- const promise = _converse.promises[name];
- if (_.isUndefined(promise)) {
- return null;
- }
- return promise;
- },
- /**
- * Allows you to send XML stanzas.
- *
- * @method _converse.api.send
- * @example
- * const msg = converse.env.$msg({
- * 'from': 'juliet@example.com/balcony',
- * 'to': 'romeo@example.net',
- * 'type':'chat'
- * });
- * _converse.api.send(msg);
- */
- 'send' (stanza) {
- _converse.connection.send(stanza);
- },
- /**
- * Send an IQ stanza and receive a promise
- *
- * @method _converse.api.sendIQ
- * @returns {Promise} A promise which resolves when we receive a `result` stanza
- * or is rejected when we receive an `error` stanza.
- */
- 'sendIQ' (stanza) {
- return new Promise((resolve, reject) => {
- _converse.connection.sendIQ(stanza, resolve, reject, _converse.IQ_TIMEOUT);
- });
- }
- };
- /**
- * ### The Public API
- *
- * This namespace contains public API methods which are are
- * accessible on the global `converse` object.
- * They are public, because any JavaScript in the
- * page can call them. Public methods therefore don’t expose any sensitive
- * or closured data. To do that, you’ll need to create a plugin, which has
- * access to the private API method.
- *
- * @namespace converse
- */
- const converse = {
- /**
- * Public API method which initializes Converse.
- * This method must always be called when using Converse.
- *
- * @memberOf converse
- * @method initialize
- * @param {object} config A map of [configuration-settings](https://conversejs.org/docs/html/configuration.html#configuration-settings).
- *
- * @example
- * converse.initialize({
- * allow_otr: true,
- * auto_list_rooms: false,
- * auto_subscribe: false,
- * bosh_service_url: 'https://bind.example.com',
- * hide_muc_server: false,
- * i18n: locales['en'],
- * keepalive: true,
- * play_sounds: true,
- * prebind: false,
- * show_controlbox_by_default: true,
- * debug: false,
- * roster_groups: true
- * });
- */
- 'initialize' (settings, callback) {
- return _converse.initialize(settings, callback);
- },
- /**
- * Exposes methods for adding and removing plugins. You'll need to write a plugin
- * if you want to have access to the private API methods defined further down below.
- *
- * For more information on plugins, read the documentation on [writing a plugin](/docs/html/plugin_development.html).
- *
- * @namespace plugins
- * @memberOf converse
- */
- 'plugins': {
- /** Registers a new plugin.
- *
- * @method converse.plugins.add
- * @param {string} name The name of the plugin
- * @param {object} plugin The plugin object
- *
- * @example
- *
- * const plugin = {
- * initialize: function () {
- * // Gets called as soon as the plugin has been loaded.
- *
- * // Inside this method, you have access to the private
- * // API via `_covnerse.api`.
- *
- * // The private _converse object contains the core logic
- * // and data-structures of Converse.
- * }
- * }
- * converse.plugins.add('myplugin', plugin);
- */
- 'add' (name, plugin) {
- plugin.__name__ = name;
- if (!_.isUndefined(_converse.pluggable.plugins[name])) {
- throw new TypeError(
- `Error: plugin with name "${name}" has already been `+
- 'registered!');
- } else {
- _converse.pluggable.plugins[name] = plugin;
- }
- }
- },
- /**
- * Utility methods and globals from bundled 3rd party libraries.
- * @memberOf converse
- *
- * @property {function} converse.env.$build - Creates a Strophe.Builder, for creating stanza objects.
- * @property {function} converse.env.$iq - Creates a Strophe.Builder with an <iq/> element as the root.
- * @property {function} converse.env.$msg - Creates a Strophe.Builder with an <message/> element as the root.
- * @property {function} converse.env.$pres - Creates a Strophe.Builder with an <presence/> element as the root.
- * @property {object} converse.env.Backbone - The [Backbone](http://backbonejs.org) object used by Converse to create models and views.
- * @property {function} converse.env.Promise - The Promise implementation used by Converse.
- * @property {function} converse.env.Strophe - The [Strophe](http://strophe.im/strophejs) XMPP library used by Converse.
- * @property {object} converse.env._ - The instance of [lodash](http://lodash.com) used by Converse.
- * @property {function} converse.env.f - And instance of Lodash with its methods wrapped to produce immutable auto-curried iteratee-first data-last methods.
- * @property {function} converse.env.b64_sha1 - Utility method from Strophe for creating base64 encoded sha1 hashes.
- * @property {object} converse.env.moment - [Moment](https://momentjs.com) date manipulation library.
- * @property {function} converse.env.sizzle - [Sizzle](https://sizzlejs.com) CSS selector engine.
- * @property {object} converse.env.utils - Module containing common utility methods used by Converse.
- */
- 'env': {
- '$build': $build,
- '$iq': $iq,
- '$msg': $msg,
- '$pres': $pres,
- 'Backbone': Backbone,
- 'Promise': Promise,
- 'Strophe': Strophe,
- '_': _,
- 'f': f,
- 'b64_sha1': b64_sha1,
- 'moment': moment,
- 'sizzle': sizzle,
- 'utils': u
- }
- };
- window.converse = converse;
- window.dispatchEvent(new CustomEvent('converse-loaded'));
- return converse;
- }));
|