pax_global_header00006660000000000000000000000064132001757330014513gustar00rootroot0000000000000052 comment=ad6541fe7b2514569a3b34795def2a738631d516 strophejs-1.2.14+dfsg/000077500000000000000000000000001320017573300145405ustar00rootroot00000000000000strophejs-1.2.14+dfsg/.gitattributes000066400000000000000000000000171320017573300174310ustar00rootroot00000000000000*.min.js binarystrophejs-1.2.14+dfsg/.travis.yml000066400000000000000000000000701320017573300166460ustar00rootroot00000000000000language: node_js node_js: - "0.10" script: make check strophejs-1.2.14+dfsg/CHANGELOG.md000066400000000000000000000176161320017573300163640ustar00rootroot00000000000000# Strophe.js Change Log ## Version 1.2.14 - 2017-06-15 * #231 SASL OAuth Bearer authentication should not require a JID node, when a user identifer can be retreived from the bearer token. * #250 Show XHR error message * #254 Set connection status to CONNFAIL after max retries * #255 Set CONNFAIL error status when connection fails on Safari 10 ## Version 1.2.13 - 2017-02-25 * Use almond to create the build. This means that the build itself is an AMD module and can be loaded via `require`. * Remove Grunt as a build tool. ## Version 1.2.12 - 2017-01-15 * Reduce the priority of the SASL-EXTERNAL auth mechanism. OpenFire 4.1.1 advertises support for SASL-EXTERNAL and the vast majority of Strophe.js installs are not set up to support SASL-EXTERNAl, causing them to fail logging users in. ## Version 1.2.11 - 2016-12-13 * 189 Strophe never reaches DISCONNECTED status after .connect(..) and .disconnect(..) calls while offline. * Add `sendPresence` method, similar to `sendIQ`, i.e. for cases where you expect a responding presence (e.g. when leaving a MUC room). ## Version 1.2.10 - 2016-11-30 * #172 and #215: Strophe shouldn't require `from` attribute in iq response * #216 Get inactivity attribute from session creation response * Enable session restoration for anonymous logins ## Version 1.2.9 - 2016-10-24 * Allow SASL mechanisms to be supported to be passed in as option to `Strophe.Connection` constructor. * Add new matching option to `Strophe.Handler`, namely `ignoreNamespaceFragment`. * The `matchBare` matching option for `Strophe.Handler` has been renamed to `matchBareFromJid`. The old name will still work in this release but is deprecated and will be removed in a future release. * #114 Add an error handler for HTTP calls * #213 "XHR open failed." in BOSH in IE9 * #214 Add function to move Strophe.Builder pointer back to the root node * #172, #215 Don't compare `to` and `to` values of sent and received IQ stanzas to determine correctness (we rely on UUIDs for that). ## Version 1.2.8 - 2016-09-16 * #200 Fix for webpack * #203 Allow custom Content-Type header for requests * #206 XML stanza attributes: there is no 'quot' escape inside 'serialize' method * The files in `./src` are now also included in the NPM distribution. * Add support for SASL-EXTERNAL ## Version 1.2.7 - 2016-06-17 * #193 Move phantomjs dependencies to devDependencies ## Version 1.2.6 - 2016-06-06 * #178 Added new value (CONNTIMEOUT) to Strophe.Status * #180 bosh: check sessionStorage support before using it * #182 Adding SASL OAuth Bearer authentication * #190 Fix .c() to accept both text and numbers as text for the child element * #192 User requirejs instead of require for node compat ## Version 1.2.5 - 2016-02-09 * Add a new Strophe.Connection option to add cookies * Add new Strophe.Connection option "withCredentials" ## Version 1.2.4 - 2016-01-28 * #147 Support for UTF-16 encoded usernames (e.g. Chinese) * #162 allow empty expectedFrom according to W3C DOM 3 Specification * #171 Improve invalid BOSH URL handling ## Version 1.2.3 - 2015-09-01 * Bugfix. Check if JID is null when restoring a session. * #127 IE-Fix: error on setting null value with setAttributes * #138 New stub method nextValidRid * #144 Change ID generator to generate UUIDs ## Version 1.2.2 - 2015-06-20 * #109 Explicitly define AMD modules to prevent errors with AlmondJS and AngularJS. * #111 Fixed IE9 compatibility. * #113 Permit connecting with an alternative authcid. * #116 tree.attrs() now removes Elements when they are set to undefined * #119 Provide the 'keepalive' option to keep a BOSH session alive across page loads. * #121 Ensure that the node names of HTML elements copied into XHTML are lower case. * #124 Strophe's Builder will swallow elements if given a blank string as a 'text' parameter. ## Version 1.2.1 - 2015-02-22 * Rerelease of 1.2.0 but with a semver tag and proper formatting of bower.json for usage with Bower.io. ## Version 1.2.0 - 2015-02-21 * Add bower package manager support. * Add commandline testing support via qunit-phantomjs-runner * Add integrated testing via TravisCI. * Fix Websocket connections now use the current XMPP-over-WebSockets RFC * #25 Item-not-found-error caused by long term request. * #29 Add support for the Asynchronous Module Definition (AMD) and require.js * #30 Base64 encoding problem in some older browsers. * #45 Move xhlr plugin to strophejs-plugins repo. * #60 Fixed deletion of handlers in websocket connections * #62 Add `xmlunescape` method. * #67 Use correct Content-Type in BOSH * #70 `_onDisconnectTimeout` never tiggers because maxRetries is undefined. * #71 switched to case sensitive handling of XML elements * #73 `getElementsByTagName` problem with namespaced elements. * #76 respect "Invalid SID" message * #79 connect.pause work correctly again * #90 The queue data was not reset in .reset() method. * #104 Websocket connections with MongooseIM work now ## Version 1.1.3 - 2014-01-20 * Fix SCRAM-SHA1 auth now works for multiple connections at the same time * Fix Connecting to a different server with the same connection after disconnect * Add Gruntfile so StropheJS can now also be built using grunt * Fix change in sha1.js that broke the caps plugin * Fix all warnings from jshint. ## Version 1.1.2 - 2014-01-04 * Add option for synchronous BOSH connections * moved bower.json to other repository * Remove unused code in sha1 and md5 modules ## Version 1.1.1 - 2013-12-16 * Fix BOSH attach is working again ## Version 1.1.0 - 2013-12-11 * Add Support for XMPP-over-WebSocket * Authentication mechanisms are now modular and can be toggled * Transport protocols are now modular and will be chosen based on the connection URL. Currently supported protocols are BOSH and WebSocket * Add Strings to some disconnects that indicate the reason * Add option to strip tags before passing to xmlInput/xmlOutput * Fix Connection status staying at CONNFAIL or CONNECTING in certain error scenarios * Add package.json for use with npm * Add bower.json for use with bower * Fix handlers not being removed after disconnect * Fix legacy non-sasl authentication * Add better tests for BOSH bind * Fix use of deprecated functions in tests * Remove some dead code * Remove deprecated documentation * Fix Memory leak in IE9 * Add An options object can be passed to a Connection constructor now * Add "Route" Parameter for BOSH Connections * Add Maximum number of connection attempts before disconnecting * Add conflict condition for AUTHFAIL * Add XHTML message support * Fix parsing chat messages in IE * Add SCRAM-SHA-1 SASL mechanism * Fix escaping of messages ## Version 1.0.2 - 2011-06-19 * Fix security bug where DIGEST-MD5 client nonce was not properly randomized. * Fix double escaping in copyElement. * Fix IE errors related to importNode. * Add ability to pass text into Builder.c(). * Improve performance by skipping debugging callbacks when not overridden. * Wrap handler runs in try/catch so they don't affect or remove later handlers. * Add ' and " to escaped characters and other escaping fixes. * Fix _throttledRequestHandler to use proper window size. * Fix timed handler management. * Fix flXHR plugin to better deal with errors. * Fix bind() to be ECMAScript 5 compatible. * Use bosh.metajack.im in examples so they work out of the box. * Add simple XHR tests. * Update basic example to HTML5. * Move community plugins to their own repository. * Fix bug causing infinite retries. * Fix 5xx error handling. * Store stream:features for later use by plugins. * Fix to prevent passing stanzas during disconnect. * Fix handling of disconnect responses. * Fix getBareJidFromJid to return null on error. * Fix equality testing in matchers so that string literals and string objects both match. * Fix bare matching on missing from attributes. * Remove use of reserved word self. * Fix various documentation errors. ## Version 1.0.1 - 2010-01-27 * Fix handling of window, hold, and wait attributes. Bug #75. ## Version 1.0 - 2010-01-01 * First release. strophejs-1.2.14+dfsg/LICENSE.txt000066400000000000000000000020471320017573300163660ustar00rootroot00000000000000Copyright (c) 2006-2009 Collecta, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. strophejs-1.2.14+dfsg/Makefile000066400000000000000000000050771320017573300162110ustar00rootroot00000000000000BOWER ?= node_modules/.bin/bower HTTPSERVE ?= ./node_modules/.bin/http-server JSHINT ?= ./node_modules/.bin/jshint PHANTOMJS ?= ./node_modules/.bin/phantomjs RJS ?= ./node_modules/.bin/r.js SHELL ?= /usr/env/bin/bash SRC_DIR = src DOC_DIR = doc DOC_TEMP = doc-temp NDPROJ_DIR = ndproj SED ?= sed STROPHE = strophe.js STROPHE_MIN = strophe.min.js STROPHE_LIGHT = strophe-no-polyfill.js all: doc $(STROPHE) $(STROPHE_MIN) .PHONY: help help: @echo "Please use \`make ' where is one of the following:" @echo "" @echo " release Prepare a new release of strophe.js. E.g. `make release VERSION=1.2.14`" @echo " serve Serve this directory via a webserver on port 8000." @echo " stamp-npm Install NPM dependencies and create the guard file stamp-npm which will prevent those dependencies from being installed again." stamp-npm: package.json npm install touch stamp-npm .PHONY: doc doc: @@echo "Building Strophe documentation..." @@if [ ! -d $(NDPROJ_DIR) ]; then mkdir $(NDPROJ_DIR); fi @@cp docs.css $(NDPROJ_DIR); @@if [ ! -d $(DOC_DIR) ]; then mkdir $(DOC_DIR); fi @@if [ ! -d $(DOC_TEMP) ]; then mkdir $(DOC_TEMP); fi @@cp $(STROPHE) $(DOC_TEMP) @@naturaldocs -r -ro -q -i $(DOC_TEMP) -o html $(DOC_DIR) -p $(NDPROJ_DIR) -s docs @@rm -r $(DOC_TEMP) @@echo "Documentation built." @@echo .PHONY: release release: $(SED) -i 's/\"version\":\ \"[0-9]\+\.[0-9]\+\.[0-9]\+\"/\"version\":\ \"$(VERSION)\"/' package.json $(SED) -i "s/Unreleased/`date +%Y-%m-%d`/" CHANGELOG.md make dist make doc .PHONE: dist dist: $(STROPHE) $(STROPHE_MIN) $(STROPHE_LIGHT) $(STROPHE_MIN): src node_modules Makefile $(RJS) -o build.js insertRequire=strophe-polyfill include=strophe-polyfill out=$(STROPHE_MIN) $(SED) -i s/@VERSION@/$(VERSION)/ $(STROPHE_MIN) $(STROPHE): src node_modules Makefile $(RJS) -o build.js optimize=none insertRequire=strophe-polyfill include=strophe-polyfill out=$(STROPHE) $(SED) -i s/@VERSION@/$(VERSION)/ $(STROPHE) $(STROPHE_LIGHT): src node_modules Makefile $(RJS) -o build.js optimize=none out=$(STROPHE_LIGHT) $(SED) -i s/@VERSION@/$(VERSION)/ $(STROPHE_LIGHT) .PHONY: jshint jshint: stamp-npm $(JSHINT) --config jshintrc src/*.js .PHONY: check check:: stamp-npm jshint $(PHANTOMJS) node_modules/qunit-phantomjs-runner/runner-list.js tests/index.html .PHONY: serve serve: $(HTTPSERVE) -p 8080 .PHONY: clean clean: @@rm -f stamp-npm @@rm -rf node_modules @@rm -f $(STROPHE) @@rm -f $(STROPHE_MIN) @@rm -f $(STROPHE_LIGHT) @@rm -f $(PLUGIN_FILES_MIN) @@rm -rf $(NDPROJ_DIR) $(DOC_DIR) $(DOC_TEMP) @@echo "Done." @@echo strophejs-1.2.14+dfsg/README.md000066400000000000000000000032661320017573300160260ustar00rootroot00000000000000# Strophe.js [![Build Status](https://travis-ci.org/strophe/strophejs.png?branch=master)](https://travis-ci.org/strophe/strophejs) Strophe.js is a JavaScript library for speaking XMPP via BOSH ([XEP 124](http://xmpp.org/extensions/xep-0124.html) and [XEP 206](http://xmpp.org/extensions/xep-0206.html)) and WebSockets ([RFC 7395](http://tools.ietf.org/html/rfc7395)). Its primary purpose is to enable web-based, real-time XMPP applications that run in any browser. The book [Professional XMPP Programming with JavaScript and jQuery](http://professionalxmpp.com) covers Strophe in detail in the context of web applications. ## Quick Links * [Homepage](http://strophe.im/strophejs) * [Documentation](http://strophe.im/strophejs/doc/1.2.9/files/strophe-js.html) * [Mailing list](http://groups.google.com/group/strophe) * [Community Plugins](http://github.com/strophe/strophejs-plugins) ## Browser support It has been tested on Firefox, Firefox for Android, IE, Safari, Mobile Safari, Chrome, Chrome for Android, Opera and the mobile Opera browser. ## Running tests You'll need to have [GNU Make](https://www.gnu.org/software/make/) available. Then, simply run `make check` to run the tests. ## License It is licensed under the [MIT license](https://github.com/strophe/strophejs/raw/master/LICENSE.txt), except for the files sha1.js, base64.js and md5.js, which are licensed as public domain and BSD (see these files for details). ## Author & History Strophe.js was originally created by Jack Moffitt. It was originally developed for Chesspark, an online chess community based on XMPP technology. It has been cared for and improved over the years and is currently maintained by many people in the community. strophejs-1.2.14+dfsg/RELEASE_CHECKLIST.md000066400000000000000000000010061320017573300175300ustar00rootroot00000000000000# Release Checklist 1. Make sure all tests pass (run 'make check') 2. Update CHANGELOG.md 3. Run "make release VERSION=1.2.14" (on Mac, prefix with "SED=gsed" so that GNU-sed is used). 4. Commit the newly generated files (mention it's a new release) 5. Tag code with version (git tag -s vVERSION ) 6. Push repo and tags (git push && git push --tags) 7. Publish on NPM: "npm publish" 8. Add documentation to strophe.im repo 9. Update links in index.markdown in Strophe.im 10. Update link to documentation in README strophejs-1.2.14+dfsg/build.js000066400000000000000000000003231320017573300161730ustar00rootroot00000000000000({ baseUrl: ".", name: "node_modules/almond/almond.js", mainConfigFile: "main.js", include: ["strophe"], wrap: { startFile: "src/start.frag", endFile: "src/end.frag" } }) strophejs-1.2.14+dfsg/contrib/000077500000000000000000000000001320017573300162005ustar00rootroot00000000000000strophejs-1.2.14+dfsg/contrib/discojs/000077500000000000000000000000001320017573300176365ustar00rootroot00000000000000strophejs-1.2.14+dfsg/contrib/discojs/README.txt000066400000000000000000000024421320017573300213360ustar00rootroot00000000000000Disco Dancing with XMPP There are many things one can do via XMPP. The list is endlist. But one thing that some forget about is discovering services a XMPP entity or server provides. In most cases a human or user does not care about this information and should not care. But you may have a website or web application that needs this information in order to decide what options to show to your users. You can do this very easily with JQuery, Strophe, and Punjab. We start with Punjab or a BOSH connection manager. This is needed so we can connect to a XMPP server. First, lets download punjab. svn co https://code.stanziq.com/svn/punjab/trunk punjab After we have punjab go into the directory and install punjab. cd punjab python setup.py install Then create a .tac file to configure Punjab. See punjab.tac Next, we will need Strophe. Lets download thelatest version from svn too. cd /directory/where/you/configured/punjab/html svn co https://code.stanziq.com/svn/strophe/trunk/strophejs In your html directory you will then begin to create your disco browser. Version 1 we take the basic example and modify it to do disco. Version 2 we add anonymous login Version 3 we make it pretty Version 4 we add handlers for different services strophejs-1.2.14+dfsg/contrib/discojs/css/000077500000000000000000000000001320017573300204265ustar00rootroot00000000000000strophejs-1.2.14+dfsg/contrib/discojs/css/disco.css000066400000000000000000000002301320017573300222340ustar00rootroot00000000000000#login .leftinput { margin-bottom: .5em; } #login input { margin-bottom: 10px; } #log { width: 100%; height: 200px; overflow: auto; } strophejs-1.2.14+dfsg/contrib/discojs/index.html000066400000000000000000000027201320017573300216340ustar00rootroot00000000000000 XMPP Disco Dancing



strophejs-1.2.14+dfsg/contrib/discojs/punjab.tac000066400000000000000000000010441320017573300216050ustar00rootroot00000000000000# punjab tac file from twisted.web import server, resource, static from twisted.application import service, internet from punjab.httpb import Httpb, HttpbService root = static.File("./") # This needs to be the directory # where you will have your html # and javascript. b = resource.IResource(HttpbService(1)) # turn on debug with 1 root.putChild('bosh', b) site = server.Site(root) application = service.Application("punjab") internet.TCPServer(5280, site).setServiceParent(application) strophejs-1.2.14+dfsg/contrib/discojs/scripts/000077500000000000000000000000001320017573300213255ustar00rootroot00000000000000strophejs-1.2.14+dfsg/contrib/discojs/scripts/basic.js000066400000000000000000000040331320017573300227440ustar00rootroot00000000000000var BOSH_SERVICE = 'http://localhost:5280/bosh'; var connection = null; var browser = null; var show_log = true; function log(msg) { $('#log').append('
').append(document.createTextNode(msg)); } function rawInput(data) { log('RECV: ' + data); } function rawOutput(data) { log('SENT: ' + data); } function onConnect(status) { if (status == Strophe.Status.CONNECTING) { log('Strophe is connecting.'); } else if (status == Strophe.Status.CONNFAIL) { log('Strophe failed to connect.'); showConnect(); } else if (status == Strophe.Status.DISCONNECTING) { log('Strophe is disconnecting.'); } else if (status == Strophe.Status.DISCONNECTED) { log('Strophe is disconnected.'); showConnect(); } else if (status == Strophe.Status.CONNECTED) { log('Strophe is connected.'); // Start up disco browser browser.showBrowser(); } } function showConnect() { var jid = $('#jid'); var pass = $('#pass'); var button = $('#connect').get(0); browser.closeBrowser(); $('label').show(); jid.show(); pass.show(); $('#anon').show(); button.value = 'connect'; return false; } function showDisconnect() { var jid = $('#jid'); var pass = $('#pass'); var button = $('#connect').get(0); button.value = 'disconnect'; pass.hide(); jid.hide(); $('label').hide(); $('#anon').hide(); return false; } $(document).ready(function () { connection = new Strophe.Connection(BOSH_SERVICE); connection.rawInput = rawInput; connection.rawOutput = rawOutput; browser = new Disco(); $("#log_container").bind('click', function () { $("#log").toggle(); } ); $('#cred').bind('submit', function () { var button = $('#connect').get(0); var jid = $('#jid'); var pass = $('#pass'); if (button.value == 'connect') { showDisconnect(); connection.connect(jid.get(0).value, pass.get(0).value, onConnect); } else { connection.disconnect(); showConnect(); } return false; }); });strophejs-1.2.14+dfsg/contrib/discojs/scripts/disco.js000066400000000000000000000026421320017573300227700ustar00rootroot00000000000000 var NS_DISCO_INFO = 'http://jabber.org/protocol/disco#info'; var NS_DISCO_ITEM = 'http://jabber.org/protocol/disco#items'; // Disco stuff Disco = function () { // Class that does nothing }; Disco.prototype = { showBrowser: function() { // Browser Display var disco = $('#disco'); var jid = $('#jid'); var server = connection.jid.split('@')[1]; // display input box disco.append("
Server :
"); // add handler for search form $("#browse").bind('submit', function () { this.startBrowse($("#server").get(0).value); return false; }); this.startBrowse(server); }, closeBrowser: function() { var disco = $('#disco'); disco.empty(); }, startBrowse: function(server) { // build iq request var id = 'startBrowse'; var discoiq = $iq({'from':connection.jid+"/"+connection.resource, 'to':server, 'id':id, 'type':'get'} ) .c('query', {'xmlns': NS_DISCO_INFO}); connection.addHandler(this._cbBrowse, null, 'iq', 'result', id); connection.send(discoiq.tree()); }, _cbBrowse: function(e) { var elem = $(e); // make this Element a JQuery Element alert(e); return false; // return false to remove the handler }, }; strophejs-1.2.14+dfsg/docs.css000066400000000000000000000475701320017573300162170ustar00rootroot00000000000000/* IMPORTANT: If you're editing this file in the output directory of one of your projects, your changes will be overwritten the next time you run Natural Docs. Instead, copy this file to your project directory, make your changes, and you can use it with -s. Even better would be to make a CSS file in your project directory with only your changes, which you can then use with -s [original style] [your changes]. On the other hand, if you're editing this file in the Natural Docs styles directory, the changes will automatically be applied to all your projects that use this style the next time Natural Docs is run on them. This file is part of Natural Docs, which is Copyright © 2003-2010 Greg Valure. Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL). Refer to License.txt for the complete details. This file may be distributed with documentation files generated by Natural Docs. Such documentation is not covered by Natural Docs' copyright and licensing, and may have its own copyright and distribution terms as decided by its author. */ body { font: 10pt Verdana, Arial, sans-serif; color: #0C2528; margin: 0; padding: 0; } .ContentPage, .IndexPage, .FramedMenuPage { background-color: #FBE5D5; } .FramedContentPage, .FramedIndexPage, .FramedSearchResultsPage, .PopupSearchResultsPage { background-color: #F1F1EF; } a:link, a:visited { color: #E37222; text-decoration: none } a:hover { color: #E37222; text-decoration: underline } a:active { color: #FF0000; text-decoration: underline } td { vertical-align: top } img { border: 0; } /* Comment out this line to use web-style paragraphs (blank line between paragraphs, no indent) instead of print-style paragraphs (no blank line, indented.) */ p { margin: 1em 0; } /* Opera doesn't break with just wbr, but will if you add this. */ .Opera wbr:after { content: "\00200B"; } /* Blockquotes are used as containers for things that may need to scroll. */ blockquote { padding: 0; margin: 0; overflow: auto; } .Firefox1 blockquote { padding-bottom: .5em; } /* Turn off scrolling when printing. */ @media print { blockquote { overflow: visible; } .IE blockquote { width: auto; } } #Menu { font-size: 9pt; padding: 1em; } .ContentPage #Menu, .IndexPage #Menu { position: absolute; top: 0; left: 0; width: 30ex; overflow: hidden; } .MTitle { font-size: 16pt; font-weight: bold; font-variant: small-caps; text-align: center; padding: 5px 10px 15px 10px; border-bottom: 1px dotted #0C2528; margin-bottom: 15px } .MSubTitle { font-size: 9pt; font-weight: normal; font-variant: normal; margin-top: 1ex; margin-bottom: 5px } .MEntry a:link, .MEntry a:hover, .MEntry a:visited { color: #606060; margin-right: 0 } .MEntry a:active { color: #A00000; margin-right: 0 } .MGroup { font-variant: small-caps; font-weight: bold; margin: 1em 0 1em 10px; } .MGroupContent { font-variant: normal; font-weight: normal } .MGroup a:link, .MGroup a:hover, .MGroup a:visited { color: #545454; margin-right: 10px } .MGroup a:active { color: #A00000; margin-right: 10px } .MFile, .MText, .MLink, .MIndex { padding: 1px 17px 2px 10px; margin: .25em 0 .25em 0; } .MText { font-size: 8pt; font-style: italic } .MLink { font-style: italic } #MSelected { color: #000; background-color: #F1F1EF; /* Replace padding with border. */ padding: 0 10px 0 10px; border-width: 1px 2px 2px 0; border-style: solid; border-color: lightgrey; margin-right: 5px; border-radius: 10px; } /* Close off the left side when its in a group. */ .MGroup #MSelected { padding-left: 9px; border-left-width: 1px; border-top-left-radius: 10px; border-bottom-left-radius: 10px; border-top-right-radius: 10px; border-bottom-right-radius: 10px; } #MSearchPanel { padding: 0px 6px; margin: .25em 0; } #MSearchField { font: italic 9pt Verdana, sans-serif; color: #606060; background-color: #FBE5D5; border: none; padding: 2px 4px; width: 100%; } /* Only Opera gets it right. */ .Firefox #MSearchField, .IE #MSearchField, .Safari #MSearchField { width: 94%; } .Opera9 #MSearchField, .Konqueror #MSearchField { width: 97%; } .FramedMenuPage .Firefox #MSearchField, .FramedMenuPage .Safari #MSearchField, .FramedMenuPage .Konqueror #MSearchField { width: 98%; } /* Firefox doesn't do this right in frames without #MSearchPanel added on. It's presence doesn't hurt anything other browsers. */ #MSearchPanel.MSearchPanelInactive:hover #MSearchField { background-color: #F1F1EF; border: 1px solid #C0C0C0; padding: 1px 3px; } .MSearchPanelActive #MSearchField { background-color: #F1F1EF; border: 1px solid #C0C0C0; font-style: normal; padding: 1px 3px; } #MSearchType { visibility: visible; font: 8pt Verdana, sans-serif; width: 98%; padding: 0; border: 1px solid #C0C0C0; } #MSearchType option#MSearchEverything { font-weight: bold; } .Opera8 .MSearchPanelInactive:hover, .Opera8 .MSearchPanelActive { margin-left: -1px; } iframe#MSearchResults { width: 60ex; height: 15em; } #MSearchResultsWindow { display: none; position: absolute; left: 0; top: 0; border: 1px solid #0C2528; background-color: #FBE5D5; } #MSearchResultsWindowClose { font-weight: bold; font-size: 8pt; display: block; padding: 2px 5px; } #MSearchResultsWindowClose:link, #MSearchResultsWindowClose:visited { color: #0C2528; text-decoration: none; } #MSearchResultsWindowClose:active, #MSearchResultsWindowClose:hover { color: #E37222; font-weight: bold; text-decoration: none; background-color: #F4F4F4; } #Content { padding-bottom: 15px; border-bottom-left-radius: 20px; } .ContentPage #Content { background-color: #F1F1EF; font-size: 10pt; margin-left: 30ex; } .CTopic { font-size: 10pt; margin-bottom: 3em; } .CTitle { font-size: 12pt; font-weight: bold; margin: 0 15px .5em 15px } .CGroup .CTitle { font-size: 16pt; font-variant: small-caps; padding-left: 15px; padding-right: 15px; margin-left: 0; margin-right: 0 } .CClass .CTitle, .CInterface .CTitle, .CDatabase .CTitle, .CDatabaseTable .CTitle, .CSection .CTitle { font-size: 18pt; color: #F1F1EF; background-color: #66B9BF; padding: 10px 15px 10px 15px; margin-left: 0; margin-right: 0 } #MainTopic .CTitle { font-size: 20pt; color: #F1F1EF; background-color: #07889B; padding: 10px 15px 10px 15px; margin-left: 0; margin-right: 0 } .CBody { margin: 1em; padding: 1em; } .CToolTip { position: absolute; visibility: hidden; left: 0; top: 0; background-color: #FFFFE0; padding: 5px; border-width: 1px 2px 2px 1px; border-style: solid; border-color: #0C2528; font-size: 8pt; } .Opera .CToolTip { max-width: 98%; } /* Scrollbars would be useless. */ .CToolTip blockquote { overflow: hidden; } .IE6 .CToolTip blockquote { overflow: visible; } .CHeading { font-weight: bold; font-size: 10pt; margin: 1.5em 0 .5em 0; } .CBody pre { font: 10pt "Courier New", Courier, monospace; background-color: #FCFCFC; margin: 1em 35px; padding: 10px 15px 10px 10px; border-color: #E0E0E0 #E0E0E0 #E0E0E0 #E4E4E4; border-width: 1px 1px 1px 6px; border-style: dashed dashed dashed solid; } .CBody ul { /* I don't know why CBody's margin doesn't apply, but it's consistent across browsers so whatever. Reapply it here as padding. */ padding-left: 15px; padding-right: 15px; margin: .5em 5ex .5em 5ex; } .CDescriptionList { margin: .5em 5ex 0 5ex } .CDLEntry { font: 10pt "Courier New", Courier, monospace; color: #808080; padding-bottom: .25em; white-space: nowrap } .CDLDescription { font-size: 10pt; /* For browsers that don't inherit correctly, like Opera 5. */ padding-bottom: .5em; padding-left: 5ex } .CTopic img { text-align: center; display: block; margin: 1em auto; } .CImageCaption { font-variant: small-caps; font-size: 8pt; color: #808080; text-align: center; position: relative; top: 1em; } .CImageLink { color: #808080; font-style: italic; } a.CImageLink:link, a.CImageLink:visited, a.CImageLink:hover { color: #808080 } .Prototype { font: 10pt "Courier New", Courier, monospace; padding: 5px 3ex; border-width: 1px; border-style: solid; margin: 0 5ex 1.5em 5ex; } .Prototype td { font-size: 10pt; } .PDefaultValue, .PDefaultValuePrefix, .PTypePrefix { color: #8F8F8F; } .PTypePrefix { text-align: right; } .PAfterParameters { vertical-align: bottom; } .IE .Prototype table { padding: 0; } .CFunction .Prototype { background-color: #F4F4F4; border-color: #D0D0D0 } .CProperty .Prototype { background-color: #F4F4FF; border-color: #C0C0E8 } .CVariable .Prototype { background-color: #FDF1E8; border-color: #E0E0A0 } .CClass .Prototype { border-width: 1px 2px 2px 1px; border-style: solid; border-color: #66B9BF; background-color: #F4F4F4; } .CInterface .Prototype { border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0D0; background-color: #F4F4FF; } .CDatabaseIndex .Prototype, .CConstant .Prototype { background-color: #D0D0D0; border-color: #0C2528 } .CType .Prototype, .CEnumeration .Prototype { background-color: #FAF0F0; border-color: #E0B0B0; } .CDatabaseTrigger .Prototype, .CEvent .Prototype, .CDelegate .Prototype { background-color: #F0FCF0; border-color: #B8E4B8 } .CToolTip .Prototype { margin: 0 0 .5em 0; white-space: nowrap; } .Summary { margin: 1.5em 5ex 0 5ex } .STitle { font-size: 12pt; font-weight: bold; margin-bottom: .5em } .SBorder { background-color: #FDF1E8; padding: 15px; border: 1px solid #E37222; border-radius: 20px; } /* In a frame IE 6 will make them too long unless you set the width to 100%. Without frames it will be correct without a width or slightly too long (but not enough to scroll) with a width. This arbitrary weirdness simply astounds me. IE 7 has the same problem with frames, haven't tested it without. */ .FramedContentPage .IE .SBorder { width: 100% } .STable { font-size: 9pt; width: 100% } .SEntry { width: 30% } .SDescription { width: 70% } .SMarked { background-color: #FBE5D5 } .SDescription { padding-left: 2ex } .SIndent1 .SEntry { padding-left: 1.5ex } .SIndent1 .SDescription { padding-left: 3.5ex } .SIndent2 .SEntry { padding-left: 3.0ex } .SIndent2 .SDescription { padding-left: 5.0ex } .SIndent3 .SEntry { padding-left: 4.5ex } .SIndent3 .SDescription { padding-left: 6.5ex } .SIndent4 .SEntry { padding-left: 6.0ex } .SIndent4 .SDescription { padding-left: 8.0ex } .SIndent5 .SEntry { padding-left: 7.5ex } .SIndent5 .SDescription { padding-left: 9.5ex } .SDescription a { color: #E37222; font-size: 115%; } .SDescription a:active { color: #A00000 } .SGroup td { padding-top: .5em; padding-bottom: .25em } .SGroup .SEntry { font-weight: bold; font-variant: small-caps } .SGroup .SEntry a { color: #E37222; font-size: 115%; font-weight: bold; } .SGroup .SEntry a:active { color: #F00000 } .SMain td, .SClass td, .SDatabase td, .SDatabaseTable td, .SSection td { font-size: 10pt; padding-bottom: .25em } .SClass td, .SDatabase td, .SDatabaseTable td, .SSection td { padding-top: 1em } .SMain .SEntry, .SClass .SEntry, .SDatabase .SEntry, .SDatabaseTable .SEntry, .SSection .SEntry { font-weight: bold; } .SMain .SEntry a, .SClass .SEntry a, .SDatabase .SEntry a, .SDatabaseTable .SEntry a, .SSection .SEntry a { color: #0C2528 } .SMain .SEntry a:active, .SClass .SEntry a:active, .SDatabase .SEntry a:active, .SDatabaseTable .SEntry a:active, .SSection .SEntry a:active { color: #A00000 } .ClassHierarchy { margin: 0 15px 1em 15px } .CHEntry { border-width: 1px 2px 2px 1px; border-style: solid; border-color: #66B9BF; margin-bottom: 3px; padding: 2px 2ex; font-size: 10pt; background-color: #F4F4F4; color: #606060; } .Firefox .CHEntry { -moz-border-radius: 4px; } .CHCurrent .CHEntry { font-weight: bold; border-color: #0C2528; color: #0C2528; } .CHChildNote .CHEntry { font-style: italic; font-size: 8pt; } .CHIndent { margin-left: 3ex; } .CHEntry a:link, .CHEntry a:visited, .CHEntry a:hover { color: #606060; } .CHEntry a:active { color: #E37222; } #Index { background-color: #F1F1EF; border-bottom-left-radius: 20px; } /* As opposed to .PopupSearchResultsPage #Index */ .IndexPage #Index, .FramedIndexPage #Index, .FramedSearchResultsPage #Index { padding: 15px; } .IndexPage #Index { font-size: 10pt; margin-left: 30ex; } .IPageTitle { font-size: 20pt; font-weight: bold; color: #F1F1EF; background-color: #07889B; padding: 10px 15px 10px 15px; border-width: 0 0 3px 0; border-style: solid; margin: -15px -15px 0 -15px; } .FramedSearchResultsPage .IPageTitle { margin-bottom: 15px; } .INavigationBar { font-size: 10pt; text-align: center; background-color: #FDF1E8; padding: 5px; margin: 0 -15px 15px -15px; } .INavigationBar a { font-weight: bold } .IHeading { font-size: 16pt; font-weight: bold; padding: 2.5em 0 .5em 0; text-align: center; width: 3.5ex; } #IFirstHeading { padding-top: 0; } .IEntry { font-size: 10pt; padding-left: 1ex; } .PopupSearchResultsPage .IEntry { font-size: 8pt; padding: 1px 5px; } .PopupSearchResultsPage .Opera9 .IEntry, .FramedSearchResultsPage .Opera9 .IEntry { text-align: left; } .FramedSearchResultsPage .IEntry { padding: 0; } .ISubIndex { padding-left: 3ex; padding-bottom: .5em } .PopupSearchResultsPage .ISubIndex { display: none; } /* While it may cause some entries to look like links when they aren't, I found it's much easier to read the index if everything's the same color. */ .ISymbol { font-weight: bold; color: #E37222 } .IndexPage .ISymbolPrefix, .FramedIndexPage .ISymbolPrefix { font-size: 10pt; text-align: right; color: #C47C7C; background-color: #F8F8F8; border-right: 3px solid #E0E0E0; border-left: 1px solid #E0E0E0; padding: 0 1px 0 2px; } .PopupSearchResultsPage .ISymbolPrefix, .FramedSearchResultsPage .ISymbolPrefix { color: #E37222; } .PopupSearchResultsPage .ISymbolPrefix { font-size: 8pt; } .IndexPage #IFirstSymbolPrefix, .FramedIndexPage #IFirstSymbolPrefix { border-top: 1px solid #E0E0E0; } .IndexPage #ILastSymbolPrefix, .FramedIndexPage #ILastSymbolPrefix { border-bottom: 1px solid #E0E0E0; } .IndexPage #IOnlySymbolPrefix, .FramedIndexPage #IOnlySymbolPrefix { border-top: 1px solid #E0E0E0; border-bottom: 1px solid #E0E0E0; } a.IParent, a.IFile { display: block; } .PopupSearchResultsPage .SRStatus { padding: 2px 5px; font-size: 8pt; font-style: italic; } .FramedSearchResultsPage .SRStatus { font-size: 10pt; font-style: italic; } .SRResult { display: none; } #Footer { font-size: 8pt; color: #989898; text-align: right; } #Footer p { text-indent: 0; margin-bottom: .5em; } .ContentPage #Footer, .IndexPage #Footer { text-align: right; margin: 2px; } .FramedMenuPage #Footer { text-align: center; margin: 5em 10px 10px 10px; padding-top: 1em; border-top: 1px solid #C8C8C8; } #Footer a:link, #Footer a:hover, #Footer a:visited { color: #989898 } #Footer a:active { color: #A00000 } .prettyprint .kwd { color: #E37222; } /* keywords */ .prettyprint.PDefaultValue .kwd, .prettyprint.PDefaultValuePrefix .kwd, .prettyprint.PTypePrefix .kwd { color: #C88F8F; } .prettyprint .com { color: #008000; } /* comments */ .prettyprint.PDefaultValue .com, .prettyprint.PDefaultValuePrefix .com, .prettyprint.PTypePrefix .com { color: #8FC88F; } .prettyprint .str { color: #0000B0; } /* strings */ .prettyprint .lit { color: #0000B0; } /* literals */ .prettyprint.PDefaultValue .str, .prettyprint.PDefaultValuePrefix .str, .prettyprint.PTypePrefix .str, .prettyprint.PDefaultValue .lit, .prettyprint.PDefaultValuePrefix .lit, .prettyprint.PTypePrefix .lit { color: #8F8FC0; } .prettyprint .typ { color: #0C2528; } /* types */ .prettyprint .pun { color: #0C2528; } /* punctuation */ .prettyprint .pln { color: #0C2528; } /* punctuation */ .prettyprint.PDefaultValue .typ, .prettyprint.PDefaultValuePrefix .typ, .prettyprint.PTypePrefix .typ, .prettyprint.PDefaultValue .pun, .prettyprint.PDefaultValuePrefix .pun, .prettyprint.PTypePrefix .pun, .prettyprint.PDefaultValue .pln, .prettyprint.PDefaultValuePrefix .pln, .prettyprint.PTypePrefix .pln { color: #8F8F8F; } .prettyprint .tag { color: #008; } .prettyprint .atn { color: #606; } .prettyprint .atv { color: #080; } .prettyprint .dec { color: #606; } strophejs-1.2.14+dfsg/examples/000077500000000000000000000000001320017573300163565ustar00rootroot00000000000000strophejs-1.2.14+dfsg/examples/amd.html000066400000000000000000000011101320017573300177760ustar00rootroot00000000000000 Strophe.js AMD Example

strophejs-1.2.14+dfsg/examples/attach/000077500000000000000000000000001320017573300176225ustar00rootroot00000000000000strophejs-1.2.14+dfsg/examples/attach/README000066400000000000000000000023171320017573300205050ustar00rootroot00000000000000This is an example of Strophe attaching to a pre-existing BOSH session that is created externally. This example requires a bit more than HTML and JavaScript. Specifically it contains a very simple Web application written in Django which creates a BOSH session before rendering the page. Requirements: * Django 1.0 (http://www.djangoproject.com) * Twisted 8.1.x (http://twistedmatrix.com) * Punjab 0.3 (http://code.stanziq.com/punjab) Note that Twisted and Punjab are only used for small functions related to JID and BOSH parsing. How It Works: The Django app contains one view which is tied to the root URL. This view uses the BOSHClient class to start a BOSH session using the settings from settings.py. Once the connection is established, Django passes the JID, SID, and RID for the BOSH session into the template engine and renders the page. The template assigns the JID, SID, and RID to global vars like so: var BOSH_JID = {{ jid }}; var BOSH_SID = {{ sid }}; var BOSH_RID = {{ rid }}; The connection is attached to Strophe by calling Strophe.Connection.attach() with this data and a connection callback handler. To show that the session is attached and works, a disco info ping is done to jabber.org. strophejs-1.2.14+dfsg/examples/attach/__init__.py000066400000000000000000000000001320017573300217210ustar00rootroot00000000000000strophejs-1.2.14+dfsg/examples/attach/attacher/000077500000000000000000000000001320017573300214155ustar00rootroot00000000000000strophejs-1.2.14+dfsg/examples/attach/attacher/__init__.py000066400000000000000000000000001320017573300235140ustar00rootroot00000000000000strophejs-1.2.14+dfsg/examples/attach/attacher/views.py000066400000000000000000000007331320017573300231270ustar00rootroot00000000000000from django.http import HttpResponse from django.template import Context, loader from attach.settings import BOSH_SERVICE, JABBERID, PASSWORD from attach.boshclient import BOSHClient def index(request): bc = BOSHClient(JABBERID, PASSWORD, BOSH_SERVICE) bc.startSessionAndAuth() t = loader.get_template("attacher/index.html") c = Context({ 'jid': bc.jabberid.full(), 'sid': bc.sid, 'rid': bc.rid, }) return HttpResponse(t.render(c)) strophejs-1.2.14+dfsg/examples/attach/boshclient.py000066400000000000000000000122661320017573300223350ustar00rootroot00000000000000import sys, os import httplib, urllib import random, binascii from urlparse import urlparse from punjab.httpb import HttpbParse from twisted.words.xish import domish from twisted.words.protocols.jabber import jid TLS_XMLNS = 'urn:ietf:params:xml:ns:xmpp-tls' SASL_XMLNS = 'urn:ietf:params:xml:ns:xmpp-sasl' BIND_XMLNS = 'urn:ietf:params:xml:ns:xmpp-bind' SESSION_XMLNS = 'urn:ietf:params:xml:ns:xmpp-session' class BOSHClient: def __init__(self, jabberid, password, bosh_service): self.rid = random.randint(0, 10000000) self.jabberid = jid.internJID(jabberid) self.password = password self.authid = None self.sid = None self.logged_in = False self.headers = {"Content-type": "text/xml", "Accept": "text/xml"} self.bosh_service = urlparse(bosh_service) def buildBody(self, child=None): """Build a BOSH body. """ body = domish.Element(("http://jabber.org/protocol/httpbind", "body")) body['content'] = 'text/xml; charset=utf-8' self.rid = self.rid + 1 body['rid'] = str(self.rid) body['sid'] = str(self.sid) body['xml:lang'] = 'en' if child is not None: body.addChild(child) return body def sendBody(self, body): """Send the body. """ parser = HttpbParse(True) # start new session conn = httplib.HTTPConnection(self.bosh_service.netloc) conn.request("POST", self.bosh_service.path, body.toXml(), self.headers) response = conn.getresponse() data = '' if response.status == 200: data = response.read() conn.close() return parser.parse(data) def startSessionAndAuth(self, hold='1', wait='70'): # Create a session # create body body = domish.Element(("http://jabber.org/protocol/httpbind", "body")) body['content'] = 'text/xml; charset=utf-8' body['hold'] = hold body['rid'] = str(self.rid) body['to'] = self.jabberid.host body['wait'] = wait body['window'] = '5' body['xml:lang'] = 'en' retb, elems = self.sendBody(body) if type(retb) != str and retb.hasAttribute('authid') and \ retb.hasAttribute('sid'): self.authid = retb['authid'] self.sid = retb['sid'] # go ahead and auth auth = domish.Element((SASL_XMLNS, 'auth')) auth['mechanism'] = 'PLAIN' # TODO: add authzid if auth['mechanism'] == 'PLAIN': auth_str = "" auth_str += "\000" auth_str += self.jabberid.user.encode('utf-8') auth_str += "\000" try: auth_str += self.password.encode('utf-8').strip() except UnicodeDecodeError: auth_str += self.password.decode('latin1') \ .encode('utf-8').strip() auth.addContent(binascii.b2a_base64(auth_str)) retb, elems = self.sendBody(self.buildBody(auth)) if len(elems) == 0: # poll for data retb, elems = self.sendBody(self.buildBody()) if len(elems) > 0: if elems[0].name == 'success': retb, elems = self.sendBody(self.buildBody()) has_bind = False for child in elems[0].children: if child.name == 'bind': has_bind = True break if has_bind: iq = domish.Element(('jabber:client', 'iq')) iq['type'] = 'set' iq.addUniqueId() iq.addElement('bind') iq.bind['xmlns'] = BIND_XMLNS if self.jabberid.resource: iq.bind.addElement('resource') iq.bind.resource.addContent( self.jabberid.resource) retb, elems = self.sendBody(self.buildBody(iq)) if type(retb) != str and retb.name == 'body': # send session iq = domish.Element(('jabber:client', 'iq')) iq['type'] = 'set' iq.addUniqueId() iq.addElement('session') iq.session['xmlns'] = SESSION_XMLNS retb, elems = self.sendBody(self.buildBody(iq)) # did not bind, TODO - add a retry? if type(retb) != str and retb.name == 'body': self.logged_in = True # bump up the rid, punjab already # received self.rid self.rid += 1 if __name__ == '__main__': USERNAME = sys.argv[1] PASSWORD = sys.argv[2] SERVICE = sys.argv[3] c = BOSHClient(USERNAME, PASSWORD, SERVICE) c.startSessionAndAuth() print c.logged_in strophejs-1.2.14+dfsg/examples/attach/manage.py000077500000000000000000000010421320017573300214240ustar00rootroot00000000000000#!/usr/bin/env python from django.core.management import execute_manager try: import settings # Assumed to be in the same directory. except ImportError: import sys sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) sys.exit(1) if __name__ == "__main__": execute_manager(settings) strophejs-1.2.14+dfsg/examples/attach/settings.py000066400000000000000000000055441320017573300220440ustar00rootroot00000000000000# Django settings for attach project. DEBUG = True TEMPLATE_DEBUG = DEBUG ADMINS = ( ('Some Body', 'romeo@example.com'), ) MANAGERS = ADMINS DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. DATABASE_NAME = '/path/to/attach.db' # Or path to database file if using sqlite3. DATABASE_USER = '' # Not used with sqlite3. DATABASE_PASSWORD = '' # Not used with sqlite3. DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # although not all choices may be available on all operating systems. # If running in a Windows environment this must be set to the same as your # system time zone. TIME_ZONE = 'America/Denver' # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html LANGUAGE_CODE = 'en-us' SITE_ID = 1 # If you set this to False, Django will make some optimizations so as not # to load the internationalization machinery. USE_I18N = True # Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). # Examples: "http://media.lawrence.com", "http://example.com/media/" MEDIA_URL = '' # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a # trailing slash. # Examples: "http://foo.com/media/", "/media/". ADMIN_MEDIA_PREFIX = '/media/' # Make this unique, and don't share it with anybody. SECRET_KEY = 'asdf' # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.load_template_source', 'django.template.loaders.app_directories.load_template_source', # 'django.template.loaders.eggs.load_template_source', ) MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', ) ROOT_URLCONF = 'attach.urls' TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. '/path/to/attach/templates', ) INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'attach.attacher', ) BOSH_SERVICE = 'http://example.com/xmpp-httpbind' JABBERID = 'romeo@example.com/bosh' PASSWORD = 'juliet.is.hawt' strophejs-1.2.14+dfsg/examples/attach/templates/000077500000000000000000000000001320017573300216205ustar00rootroot00000000000000strophejs-1.2.14+dfsg/examples/attach/templates/attacher/000077500000000000000000000000001320017573300234135ustar00rootroot00000000000000strophejs-1.2.14+dfsg/examples/attach/templates/attacher/index.html000066400000000000000000000054421320017573300254150ustar00rootroot00000000000000 Strophe Attach Example

Strophe Attach Example

This example shows how to attach to an existing BOSH session with Strophe.

Log

strophejs-1.2.14+dfsg/examples/attach/urls.py000066400000000000000000000011061320017573300211570ustar00rootroot00000000000000from django.conf.urls.defaults import * # Uncomment the next two lines to enable the admin: # from django.contrib import admin # admin.autodiscover() urlpatterns = patterns('', # Example: # (r'^attach/', include('attach.foo.urls')), # Uncomment the admin/doc line below and add 'django.contrib.admindocs' # to INSTALLED_APPS to enable admin documentation: # (r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: # (r'^admin/(.*)', admin.site.root), (r'^$', 'attach.attacher.views.index'), ) strophejs-1.2.14+dfsg/examples/basic.html000066400000000000000000000014221320017573300203240ustar00rootroot00000000000000 Strophe.js Basic Example

strophejs-1.2.14+dfsg/examples/basic.js000066400000000000000000000036301320017573300177770ustar00rootroot00000000000000var BOSH_SERVICE = 'http://bosh.metajack.im:5280/xmpp-httpbind'; var connection = null; function log(msg, data) { var tr = document.createElement('tr'); var th = document.createElement('th'); th.setAttribute( "style", "text-align: left; vertical-align: top;" ); var td; th.appendChild( document.createTextNode(msg) ); tr.appendChild( th ); if (data) { td = document.createElement('td'); pre = document.createElement('code'); pre.setAttribute("style", "white-space: pre-wrap;"); td.appendChild(pre); pre.appendChild( document.createTextNode( vkbeautify.xml(data) ) ); tr.appendChild(td); } else { th.setAttribute('colspan', '2'); } $('#log').append(tr); } function rawInput(data) { log('RECV', data); } function rawOutput(data) { log('SENT', data); } function onConnect(status) { if (status == Strophe.Status.CONNECTING) { log('Strophe is connecting.'); } else if (status == Strophe.Status.CONNFAIL) { log('Strophe failed to connect.'); $('#connect').get(0).value = 'connect'; } else if (status == Strophe.Status.DISCONNECTING) { log('Strophe is disconnecting.'); } else if (status == Strophe.Status.DISCONNECTED) { log('Strophe is disconnected.'); $('#connect').get(0).value = 'connect'; } else if (status == Strophe.Status.CONNECTED) { log('Strophe is connected.'); connection.disconnect(); } } $(document).ready(function () { connection = new Strophe.Connection(BOSH_SERVICE); connection.rawInput = rawInput; connection.rawOutput = rawOutput; $('#connect').bind('click', function () { var button = $('#connect').get(0); if (button.value == 'connect') { button.value = 'disconnect'; connection.connect($('#jid').get(0).value, $('#pass').get(0).value, onConnect); } else { button.value = 'connect'; connection.disconnect(); } }); }); strophejs-1.2.14+dfsg/examples/echobot.html000066400000000000000000000014631320017573300206730ustar00rootroot00000000000000 Strophe.js Echobot Example

strophejs-1.2.14+dfsg/examples/echobot.js000066400000000000000000000044751320017573300203510ustar00rootroot00000000000000var BOSH_SERVICE = '/xmpp-httpbind'; var connection = null; function log(msg) { $('#log').append('
').append(document.createTextNode(msg)); } function onConnect(status) { if (status == Strophe.Status.CONNECTING) { log('Strophe is connecting.'); } else if (status == Strophe.Status.CONNFAIL) { log('Strophe failed to connect.'); $('#connect').get(0).value = 'connect'; } else if (status == Strophe.Status.DISCONNECTING) { log('Strophe is disconnecting.'); } else if (status == Strophe.Status.DISCONNECTED) { log('Strophe is disconnected.'); $('#connect').get(0).value = 'connect'; } else if (status == Strophe.Status.CONNECTED) { log('Strophe is connected.'); log('ECHOBOT: Send a message to ' + connection.jid + ' to talk to me.'); connection.addHandler(onMessage, null, 'message', null, null, null); connection.send($pres().tree()); } } function onMessage(msg) { var to = msg.getAttribute('to'); var from = msg.getAttribute('from'); var type = msg.getAttribute('type'); var elems = msg.getElementsByTagName('body'); if (type == "chat" && elems.length > 0) { var body = elems[0]; log('ECHOBOT: I got a message from ' + from + ': ' + Strophe.getText(body)); var reply = $msg({to: from, from: to, type: 'chat'}) .cnode(Strophe.copyElement(body)); connection.send(reply.tree()); log('ECHOBOT: I sent ' + from + ': ' + Strophe.getText(body)); } // we must return true to keep the handler alive. // returning false would remove it after it finishes. return true; } $(document).ready(function () { connection = new Strophe.Connection(BOSH_SERVICE); // Uncomment the following lines to spy on the wire traffic. //connection.rawInput = function (data) { log('RECV: ' + data); }; //connection.rawOutput = function (data) { log('SEND: ' + data); }; // Uncomment the following line to see all the debug output. //Strophe.log = function (level, msg) { log('LOG: ' + msg); }; $('#connect').bind('click', function () { var button = $('#connect').get(0); if (button.value == 'connect') { button.value = 'disconnect'; connection.connect($('#jid').get(0).value, $('#pass').get(0).value, onConnect); } else { button.value = 'connect'; connection.disconnect(); } }); }); strophejs-1.2.14+dfsg/examples/main.js000066400000000000000000000040541320017573300176430ustar00rootroot00000000000000config.baseUrl = '../'; require.config(config); if (typeof(require) === 'function') { require(["jquery", "strophe", ], function($, wrapper) { Strophe = wrapper.Strophe; var BOSH_SERVICE = 'http://bosh.metajack.im:5280/xmpp-httpbind'; var connection = null; function log(msg) { $('#log').append('
').append(document.createTextNode(msg)); } function rawInput(data) { log('RECV: ' + data); } function rawOutput(data) { log('SENT: ' + data); } function onConnect(status) { if (status == Strophe.Status.CONNECTING) { log('Strophe is connecting.'); } else if (status == Strophe.Status.CONNFAIL) { log('Strophe failed to connect.'); $('#connect').get(0).value = 'connect'; } else if (status == Strophe.Status.DISCONNECTING) { log('Strophe is disconnecting.'); } else if (status == Strophe.Status.DISCONNECTED) { log('Strophe is disconnected.'); $('#connect').get(0).value = 'connect'; } else if (status == Strophe.Status.CONNECTED) { log('Strophe is connected.'); connection.disconnect(); } } $(document).ready(function () { connection = new Strophe.Connection(BOSH_SERVICE); connection.rawInput = rawInput; connection.rawOutput = rawOutput; $('#connect').bind('click', function () { var button = $('#connect').get(0); if (button.value == 'connect') { button.value = 'disconnect'; connection.connect( $('#jid').get(0).value, $('#pass').get(0).value, onConnect ); } else { button.value = 'connect'; connection.disconnect(); } }); }); }); } strophejs-1.2.14+dfsg/examples/prebind.html000066400000000000000000000021121320017573300206630ustar00rootroot00000000000000 Strophe.js Pre-Bind Example

strophejs-1.2.14+dfsg/examples/prebind.js000066400000000000000000000057151320017573300203470ustar00rootroot00000000000000// http-pre-bind example // This example works with mod_http_pre_bind found here: // http://github.com/thepug/Mod-Http-Pre-Bind // // It expects both /xmpp-httpbind to be proxied and /http-pre-bind // // If you want to test this out without setting it up, you can use Collecta's // at http://www.collecta.com/xmpp-httpbind and // http://www.collecta.com/http-pre-bind // Use a JID of 'guest.collecta.com' to test. var BOSH_SERVICE = '/xmpp-httpbind'; var PREBIND_SERVICE = '/http-pre-bind'; var connection = null; function log(msg) { $('#log').append('
').append(document.createTextNode(msg)); } function rawInput(data) { log('RECV: ' + data); } function rawOutput(data) { log('SENT: ' + data); } function onConnect(status) { if (status === Strophe.Status.CONNECTING) { log('Strophe is connecting.'); } else if (status === Strophe.Status.CONNFAIL) { log('Strophe failed to connect.'); $('#connect').get(0).value = 'connect'; } else if (status === Strophe.Status.DISCONNECTING) { log('Strophe is disconnecting.'); } else if (status === Strophe.Status.DISCONNECTED) { log('Strophe is disconnected.'); $('#connect').get(0).value = 'connect'; } else if (status === Strophe.Status.CONNECTED) { log('Strophe is connected.'); connection.disconnect(); } else if (status === Strophe.Status.ATTACHED) { log('Strophe is attached.'); connection.disconnect(); } } function normal_connect() { log('Prebind failed. Connecting normally...'); connection = new Strophe.Connection(BOSH_SERVICE); connection.rawInput = rawInput; connection.rawOutput = rawOutput; connection.connect($('#jid').val(), $('#pass').val(), onConnect); } function attach(data) { log('Prebind succeeded. Attaching...'); connection = new Strophe.Connection(BOSH_SERVICE); connection.rawInput = rawInput; connection.rawOutput = rawOutput; var $body = $(data.documentElement); connection.attach($body.find('jid').text(), $body.attr('sid'), parseInt($body.attr('rid'), 10) + 1, onConnect); } $(document).ready(function () { $('#connect').bind('click', function () { var button = $('#connect').get(0); if (button.value == 'connect') { button.value = 'disconnect'; // attempt prebind $.ajax({ type: 'POST', url: PREBIND_SERVICE, contentType: 'text/xml', processData: false, data: $build('body', { to: Strophe.getDomainFromJid($('#jid').val()), rid: '' + Math.floor(Math.random() * 4294967295), wait: '60', hold: '1'}).toString(), dataType: 'xml', error: normal_connect, success: attach}); } else { button.value = 'connect'; if (connection) { connection.disconnect(); } } }); });strophejs-1.2.14+dfsg/examples/restore.html000066400000000000000000000012161320017573300207270ustar00rootroot00000000000000 Strophe.js Persistent Session Example

strophejs-1.2.14+dfsg/examples/restore.js000066400000000000000000000047001320017573300204000ustar00rootroot00000000000000config.baseUrl = '../'; require.config(config); if (typeof(require) === 'function') { require(["jquery", "strophe", ], function($, wrapper) { Strophe = wrapper.Strophe; var button = document.getElementById("connect"); button.addEventListener('click', function () { if (button.value == 'connect') { button.value = 'disconnect'; connection.connect( $('#jid').get(0).value, $('#pass').get(0).value, onConnect ); } else { button.value = 'connect'; connection.disconnect(); } }); $(button).hide(); var BOSH_SERVICE = 'https://conversejs.org/http-bind/'; var connection = null; function log(msg) { $('#log').append('
').append(document.createTextNode(msg)); } function rawInput(data) { log('RECV: ' + data); } function rawOutput(data) { log('SENT: ' + data); } function onConnect(status) { if (status == Strophe.Status.CONNECTING) { log('Strophe is connecting.'); } else if (status == Strophe.Status.CONNFAIL) { log('Strophe failed to connect.'); $('#connect').get(0).value = 'connect'; } else if (status == Strophe.Status.DISCONNECTING) { log('Strophe is disconnecting.'); } else if (status == Strophe.Status.DISCONNECTED) { log('Strophe is disconnected.'); $('#connect').get(0).value = 'connect'; } else if (status == Strophe.Status.CONNECTED) { log('Strophe is connected.'); } else if (status == Strophe.Status.ATTACHED) { log('Strophe is attached.'); var button = $('#connect').get(0); button.value = 'disconnect'; $(button).show(); } } $(document).ready(function () { connection = new Strophe.Connection(BOSH_SERVICE, {'keepalive': true}); connection.rawInput = rawInput; connection.rawOutput = rawOutput; try { connection.restore(null, onConnect); } catch(e) { if (e.name !== "StropheSessionError") { throw(e); } $(button).show(); } }); }); } strophejs-1.2.14+dfsg/jshintrc000066400000000000000000000005461320017573300163140ustar00rootroot00000000000000{ "browser": true, "devel": true, "eqeqeq": true, "indent": 4, "jquery": false, "smarttabs": true, "trailing": true, "undef": true, "unused": "vars", "white": false, "predef": [ "define", "escape", "expect", "global", "module", "require", "unescape" ] } strophejs-1.2.14+dfsg/main.js000066400000000000000000000022231320017573300160210ustar00rootroot00000000000000var config; if (typeof(require) === 'undefined') { /* XXX: Hack to work around r.js's stupid parsing. * We want to save the configuration in a variable so that we can reuse it in * tests/main.js. */ require = { config: function (c) { config = c; } }; } require.config({ baseUrl: '.', paths: { // Strophe.js src files "strophe-base64": "src/base64", "strophe-bosh": "src/bosh", "strophe-core": "src/core", "strophe-utils": "src/utils", "strophe": "src/wrapper", "strophe-md5": "src/md5", "strophe-sha1": "src/sha1", "strophe-websocket": "src/websocket", "strophe-polyfill": "src/polyfills", // Examples "basic": "examples/basic", // Tests "jquery": "node_modules/jquery/dist/jquery", "sinon": "node_modules/sinon/lib/sinon", "sinon-qunit": "node_modules/sinon-qunit/lib/sinon-qunit", "tests": "tests/tests" } }); if (typeof(require) === 'function') { require(["strophe"], function(Strophe) { window.Strophe = Strophe; }); } strophejs-1.2.14+dfsg/package.json000066400000000000000000000026201320017573300170260ustar00rootroot00000000000000{ "name": "strophe.js", "description": "Strophe.js is an XMPP library for JavaScript", "version": "1.2.14", "homepage": "http://strophe.im/strophejs", "repository": { "type": "git", "url": "git://github.com/strophe/strophejs.git" }, "keywords": [ "xmpp", "message", "bosh", "websocket", "browser" ], "files": [ "strophe.js", "CHANGELOG.txt", "LICENSE.txt", "README.txt", "src/" ], "author": "Jack Moffit (metajack)", "contributors": [ "Nathan Zorn (thepug)", "Andreas Guth (Gordin)", "Anton Stroganov (Aeon)", "Florian Zeitz (Florob)", "Christopher Zorn (twonds)", "dodo", "Lee Boynton (lboynton)", "Theo Cushion (theozaurus)", "Brendon Crawford (brendoncrawford)", "JC Brand (jcbrand)" ], "license": "MIT", "main": "strophe.js", "browser": "strophe.js", "scripts": { "lint": "make jshint", "clean": "make clean", "doc": "make doc" }, "engines": { "browser": "*" }, "volo": { "url": "https://raw.githubusercontent.com/strophe/strophejs/release-{version}/strophe.js" }, "devDependencies": { "almond": "~0.3.0", "http-server": "^0.10.0", "jquery": "3.2.1", "jshint": "^2.9.4", "phantomjs": "~1.9.7-1", "qunitjs": "~1.16.0", "qunit-phantomjs-runner": "^2.1.0", "requirejs": "~2.3.2", "sinon": "1.17.7", "sinon-qunit": "~2.0.0" } } strophejs-1.2.14+dfsg/src/000077500000000000000000000000001320017573300153275ustar00rootroot00000000000000strophejs-1.2.14+dfsg/src/bosh.js000066400000000000000000001034731320017573300166300ustar00rootroot00000000000000/* This program is distributed under the terms of the MIT license. Please see the LICENSE file for details. Copyright 2006-2008, OGG, LLC */ /* jshint undef: true, unused: true:, noarg: true, latedef: true */ /* global define, window, setTimeout, clearTimeout, XMLHttpRequest, ActiveXObject, Strophe, $build */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define(['strophe-core'], function (core) { return factory( core.Strophe, core.$build ); }); } else { // Browser globals return factory(Strophe, $build); } }(this, function (Strophe, $build) { /** PrivateClass: Strophe.Request * _Private_ helper class that provides a cross implementation abstraction * for a BOSH related XMLHttpRequest. * * The Strophe.Request class is used internally to encapsulate BOSH request * information. It is not meant to be used from user's code. */ /** PrivateConstructor: Strophe.Request * Create and initialize a new Strophe.Request object. * * Parameters: * (XMLElement) elem - The XML data to be sent in the request. * (Function) func - The function that will be called when the * XMLHttpRequest readyState changes. * (Integer) rid - The BOSH rid attribute associated with this request. * (Integer) sends - The number of times this same request has been sent. */ Strophe.Request = function (elem, func, rid, sends) { this.id = ++Strophe._requestId; this.xmlData = elem; this.data = Strophe.serialize(elem); // save original function in case we need to make a new request // from this one. this.origFunc = func; this.func = func; this.rid = rid; this.date = NaN; this.sends = sends || 0; this.abort = false; this.dead = null; this.age = function () { if (!this.date) { return 0; } var now = new Date(); return (now - this.date) / 1000; }; this.timeDead = function () { if (!this.dead) { return 0; } var now = new Date(); return (now - this.dead) / 1000; }; this.xhr = this._newXHR(); }; Strophe.Request.prototype = { /** PrivateFunction: getResponse * Get a response from the underlying XMLHttpRequest. * * This function attempts to get a response from the request and checks * for errors. * * Throws: * "parsererror" - A parser error occured. * "badformat" - The entity has sent XML that cannot be processed. * * Returns: * The DOM element tree of the response. */ getResponse: function () { var node = null; if (this.xhr.responseXML && this.xhr.responseXML.documentElement) { node = this.xhr.responseXML.documentElement; if (node.tagName === "parsererror") { Strophe.error("invalid response received"); Strophe.error("responseText: " + this.xhr.responseText); Strophe.error("responseXML: " + Strophe.serialize(this.xhr.responseXML)); throw "parsererror"; } } else if (this.xhr.responseText) { Strophe.error("invalid response received"); Strophe.error("responseText: " + this.xhr.responseText); throw "badformat"; } return node; }, /** PrivateFunction: _newXHR * _Private_ helper function to create XMLHttpRequests. * * This function creates XMLHttpRequests across all implementations. * * Returns: * A new XMLHttpRequest. */ _newXHR: function () { var xhr = null; if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); if (xhr.overrideMimeType) { xhr.overrideMimeType("text/xml; charset=utf-8"); } } else if (window.ActiveXObject) { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } // use Function.bind() to prepend ourselves as an argument xhr.onreadystatechange = this.func.bind(null, this); return xhr; } }; /** Class: Strophe.Bosh * _Private_ helper class that handles BOSH Connections * * The Strophe.Bosh class is used internally by Strophe.Connection * to encapsulate BOSH sessions. It is not meant to be used from user's code. */ /** File: bosh.js * A JavaScript library to enable BOSH in Strophejs. * * this library uses Bidirectional-streams Over Synchronous HTTP (BOSH) * to emulate a persistent, stateful, two-way connection to an XMPP server. * More information on BOSH can be found in XEP 124. */ /** PrivateConstructor: Strophe.Bosh * Create and initialize a Strophe.Bosh object. * * Parameters: * (Strophe.Connection) connection - The Strophe.Connection that will use BOSH. * * Returns: * A new Strophe.Bosh object. */ Strophe.Bosh = function(connection) { this._conn = connection; /* request id for body tags */ this.rid = Math.floor(Math.random() * 4294967295); /* The current session ID. */ this.sid = null; // default BOSH values this.hold = 1; this.wait = 60; this.window = 5; this.errors = 0; this.inactivity = null; this._requests = []; }; Strophe.Bosh.prototype = { /** Variable: strip * * BOSH-Connections will have all stanzas wrapped in a tag when * passed to or . * To strip this tag, User code can set to "body": * * > Strophe.Bosh.prototype.strip = "body"; * * This will enable stripping of the body tag in both * and . */ strip: null, /** PrivateFunction: _buildBody * _Private_ helper function to generate the wrapper for BOSH. * * Returns: * A Strophe.Builder with a element. */ _buildBody: function () { var bodyWrap = $build('body', { rid: this.rid++, xmlns: Strophe.NS.HTTPBIND }); if (this.sid !== null) { bodyWrap.attrs({sid: this.sid}); } if (this._conn.options.keepalive && this._conn._sessionCachingSupported()) { this._cacheSession(); } return bodyWrap; }, /** PrivateFunction: _reset * Reset the connection. * * This function is called by the reset function of the Strophe Connection */ _reset: function () { this.rid = Math.floor(Math.random() * 4294967295); this.sid = null; this.errors = 0; if (this._conn._sessionCachingSupported()) { window.sessionStorage.removeItem('strophe-bosh-session'); } this._conn.nextValidRid(this.rid); }, /** PrivateFunction: _connect * _Private_ function that initializes the BOSH connection. * * Creates and sends the Request that initializes the BOSH connection. */ _connect: function (wait, hold, route) { this.wait = wait || this.wait; this.hold = hold || this.hold; this.errors = 0; // build the body tag var body = this._buildBody().attrs({ to: this._conn.domain, "xml:lang": "en", wait: this.wait, hold: this.hold, content: "text/xml; charset=utf-8", ver: "1.6", "xmpp:version": "1.0", "xmlns:xmpp": Strophe.NS.BOSH }); if(route){ body.attrs({ route: route }); } var _connect_cb = this._conn._connect_cb; this._requests.push( new Strophe.Request(body.tree(), this._onRequestStateChange.bind( this, _connect_cb.bind(this._conn)), body.tree().getAttribute("rid"))); this._throttledRequestHandler(); }, /** PrivateFunction: _attach * Attach to an already created and authenticated BOSH session. * * This function is provided to allow Strophe to attach to BOSH * sessions which have been created externally, perhaps by a Web * application. This is often used to support auto-login type features * without putting user credentials into the page. * * Parameters: * (String) jid - The full JID that is bound by the session. * (String) sid - The SID of the BOSH session. * (String) rid - The current RID of the BOSH session. This RID * will be used by the next request. * (Function) callback The connect callback function. * (Integer) wait - The optional HTTPBIND wait value. This is the * time the server will wait before returning an empty result for * a request. The default setting of 60 seconds is recommended. * Other settings will require tweaks to the Strophe.TIMEOUT value. * (Integer) hold - The optional HTTPBIND hold value. This is the * number of connections the server will hold at one time. This * should almost always be set to 1 (the default). * (Integer) wind - The optional HTTBIND window value. This is the * allowed range of request ids that are valid. The default is 5. */ _attach: function (jid, sid, rid, callback, wait, hold, wind) { this._conn.jid = jid; this.sid = sid; this.rid = rid; this._conn.connect_callback = callback; this._conn.domain = Strophe.getDomainFromJid(this._conn.jid); this._conn.authenticated = true; this._conn.connected = true; this.wait = wait || this.wait; this.hold = hold || this.hold; this.window = wind || this.window; this._conn._changeConnectStatus(Strophe.Status.ATTACHED, null); }, /** PrivateFunction: _restore * Attempt to restore a cached BOSH session * * Parameters: * (String) jid - The full JID that is bound by the session. * This parameter is optional but recommended, specifically in cases * where prebinded BOSH sessions are used where it's important to know * that the right session is being restored. * (Function) callback The connect callback function. * (Integer) wait - The optional HTTPBIND wait value. This is the * time the server will wait before returning an empty result for * a request. The default setting of 60 seconds is recommended. * Other settings will require tweaks to the Strophe.TIMEOUT value. * (Integer) hold - The optional HTTPBIND hold value. This is the * number of connections the server will hold at one time. This * should almost always be set to 1 (the default). * (Integer) wind - The optional HTTBIND window value. This is the * allowed range of request ids that are valid. The default is 5. */ _restore: function (jid, callback, wait, hold, wind) { var session = JSON.parse(window.sessionStorage.getItem('strophe-bosh-session')); if (typeof session !== "undefined" && session !== null && session.rid && session.sid && session.jid && ( typeof jid === "undefined" || jid === null || Strophe.getBareJidFromJid(session.jid) === Strophe.getBareJidFromJid(jid) || // If authcid is null, then it's an anonymous login, so // we compare only the domains: ((Strophe.getNodeFromJid(jid) === null) && (Strophe.getDomainFromJid(session.jid) === jid)) ) ) { this._conn.restored = true; this._attach(session.jid, session.sid, session.rid, callback, wait, hold, wind); } else { throw { name: "StropheSessionError", message: "_restore: no restoreable session." }; } }, /** PrivateFunction: _cacheSession * _Private_ handler for the beforeunload event. * * This handler is used to process the Bosh-part of the initial request. * Parameters: * (Strophe.Request) bodyWrap - The received stanza. */ _cacheSession: function () { if (this._conn.authenticated) { if (this._conn.jid && this.rid && this.sid) { window.sessionStorage.setItem('strophe-bosh-session', JSON.stringify({ 'jid': this._conn.jid, 'rid': this.rid, 'sid': this.sid })); } } else { window.sessionStorage.removeItem('strophe-bosh-session'); } }, /** PrivateFunction: _connect_cb * _Private_ handler for initial connection request. * * This handler is used to process the Bosh-part of the initial request. * Parameters: * (Strophe.Request) bodyWrap - The received stanza. */ _connect_cb: function (bodyWrap) { var typ = bodyWrap.getAttribute("type"); var cond, conflict; if (typ !== null && typ === "terminate") { // an error occurred cond = bodyWrap.getAttribute("condition"); Strophe.error("BOSH-Connection failed: " + cond); conflict = bodyWrap.getElementsByTagName("conflict"); if (cond !== null) { if (cond === "remote-stream-error" && conflict.length > 0) { cond = "conflict"; } this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, cond); } else { this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown"); } this._conn._doDisconnect(cond); return Strophe.Status.CONNFAIL; } // check to make sure we don't overwrite these if _connect_cb is // called multiple times in the case of missing stream:features if (!this.sid) { this.sid = bodyWrap.getAttribute("sid"); } var wind = bodyWrap.getAttribute('requests'); if (wind) { this.window = parseInt(wind, 10); } var hold = bodyWrap.getAttribute('hold'); if (hold) { this.hold = parseInt(hold, 10); } var wait = bodyWrap.getAttribute('wait'); if (wait) { this.wait = parseInt(wait, 10); } var inactivity = bodyWrap.getAttribute('inactivity'); if (inactivity) { this.inactivity = parseInt(inactivity, 10); } }, /** PrivateFunction: _disconnect * _Private_ part of Connection.disconnect for Bosh * * Parameters: * (Request) pres - This stanza will be sent before disconnecting. */ _disconnect: function (pres) { this._sendTerminate(pres); }, /** PrivateFunction: _doDisconnect * _Private_ function to disconnect. * * Resets the SID and RID. */ _doDisconnect: function () { this.sid = null; this.rid = Math.floor(Math.random() * 4294967295); if (this._conn._sessionCachingSupported()) { window.sessionStorage.removeItem('strophe-bosh-session'); } this._conn.nextValidRid(this.rid); }, /** PrivateFunction: _emptyQueue * _Private_ function to check if the Request queue is empty. * * Returns: * True, if there are no Requests queued, False otherwise. */ _emptyQueue: function () { return this._requests.length === 0; }, /** PrivateFunction: _callProtocolErrorHandlers * _Private_ function to call error handlers registered for HTTP errors. * * Parameters: * (Strophe.Request) req - The request that is changing readyState. */ _callProtocolErrorHandlers: function (req) { var reqStatus = this._getRequestStatus(req), err_callback; err_callback = this._conn.protocolErrorHandlers.HTTP[reqStatus]; if (err_callback) { err_callback.call(this, reqStatus); } }, /** PrivateFunction: _hitError * _Private_ function to handle the error count. * * Requests are resent automatically until their error count reaches * 5. Each time an error is encountered, this function is called to * increment the count and disconnect if the count is too high. * * Parameters: * (Integer) reqStatus - The request status. */ _hitError: function (reqStatus) { this.errors++; Strophe.warn("request errored, status: " + reqStatus + ", number of errors: " + this.errors); if (this.errors > 4) { this._conn._onDisconnectTimeout(); } }, /** PrivateFunction: _no_auth_received * * Called on stream start/restart when no stream:features * has been received and sends a blank poll request. */ _no_auth_received: function (_callback) { if (_callback) { _callback = _callback.bind(this._conn); } else { _callback = this._conn._connect_cb.bind(this._conn); } var body = this._buildBody(); this._requests.push( new Strophe.Request(body.tree(), this._onRequestStateChange.bind( this, _callback.bind(this._conn)), body.tree().getAttribute("rid"))); this._throttledRequestHandler(); }, /** PrivateFunction: _onDisconnectTimeout * _Private_ timeout handler for handling non-graceful disconnection. * * Cancels all remaining Requests and clears the queue. */ _onDisconnectTimeout: function () { this._abortAllRequests(); }, /** PrivateFunction: _abortAllRequests * _Private_ helper function that makes sure all pending requests are aborted. */ _abortAllRequests: function _abortAllRequests() { var req; while (this._requests.length > 0) { req = this._requests.pop(); req.abort = true; req.xhr.abort(); // jslint complains, but this is fine. setting to empty func // is necessary for IE6 req.xhr.onreadystatechange = function () {}; // jshint ignore:line } }, /** PrivateFunction: _onIdle * _Private_ handler called by Strophe.Connection._onIdle * * Sends all queued Requests or polls with empty Request if there are none. */ _onIdle: function () { var data = this._conn._data; // if no requests are in progress, poll if (this._conn.authenticated && this._requests.length === 0 && data.length === 0 && !this._conn.disconnecting) { Strophe.info("no requests during idle cycle, sending " + "blank request"); data.push(null); } if (this._conn.paused) { return; } if (this._requests.length < 2 && data.length > 0) { var body = this._buildBody(); for (var i = 0; i < data.length; i++) { if (data[i] !== null) { if (data[i] === "restart") { body.attrs({ to: this._conn.domain, "xml:lang": "en", "xmpp:restart": "true", "xmlns:xmpp": Strophe.NS.BOSH }); } else { body.cnode(data[i]).up(); } } } delete this._conn._data; this._conn._data = []; this._requests.push( new Strophe.Request(body.tree(), this._onRequestStateChange.bind( this, this._conn._dataRecv.bind(this._conn)), body.tree().getAttribute("rid"))); this._throttledRequestHandler(); } if (this._requests.length > 0) { var time_elapsed = this._requests[0].age(); if (this._requests[0].dead !== null) { if (this._requests[0].timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) { this._throttledRequestHandler(); } } if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) { Strophe.warn("Request " + this._requests[0].id + " timed out, over " + Math.floor(Strophe.TIMEOUT * this.wait) + " seconds since last activity"); this._throttledRequestHandler(); } } }, /** PrivateFunction: _getRequestStatus * * Returns the HTTP status code from a Strophe.Request * * Parameters: * (Strophe.Request) req - The Strophe.Request instance. * (Integer) def - The default value that should be returned if no * status value was found. */ _getRequestStatus: function (req, def) { var reqStatus; if (req.xhr.readyState === 4) { try { reqStatus = req.xhr.status; } catch (e) { // ignore errors from undefined status attribute. Works // around a browser bug Strophe.error( "Caught an error while retrieving a request's status, " + "reqStatus: " + reqStatus); } } if (typeof(reqStatus) === "undefined") { reqStatus = typeof def === 'number' ? def : 0; } return reqStatus; }, /** PrivateFunction: _onRequestStateChange * _Private_ handler for Strophe.Request state changes. * * This function is called when the XMLHttpRequest readyState changes. * It contains a lot of error handling logic for the many ways that * requests can fail, and calls the request callback when requests * succeed. * * Parameters: * (Function) func - The handler for the request. * (Strophe.Request) req - The request that is changing readyState. */ _onRequestStateChange: function (func, req) { Strophe.debug("request id "+req.id+"."+req.sends+ " state changed to "+req.xhr.readyState); if (req.abort) { req.abort = false; return; } if (req.xhr.readyState !== 4) { // The request is not yet complete return; } var reqStatus = this._getRequestStatus(req); if (this.disconnecting && reqStatus >= 400) { this._hitError(reqStatus); this._callProtocolErrorHandlers(req); return; } var valid_request = reqStatus > 0 && reqStatus < 500; var too_many_retries = req.sends > this._conn.maxRetries; if (valid_request || too_many_retries) { // remove from internal queue this._removeRequest(req); Strophe.debug("request id "+req.id+" should now be removed"); } if (reqStatus === 200) { // request succeeded var reqIs0 = (this._requests[0] === req); var reqIs1 = (this._requests[1] === req); // if request 1 finished, or request 0 finished and request // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to // restart the other - both will be in the first spot, as the // completed request has been removed from the queue already if (reqIs1 || (reqIs0 && this._requests.length > 0 && this._requests[0].age() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait))) { this._restartRequest(0); } this._conn.nextValidRid(Number(req.rid) + 1); Strophe.debug("request id "+req.id+"."+req.sends+" got 200"); func(req); // call handler this.errors = 0; } else if (reqStatus === 0 || (reqStatus >= 400 && reqStatus < 600) || reqStatus >= 12000) { // request failed Strophe.error("request id "+req.id+"."+req.sends+" error "+reqStatus+" happened"); this._hitError(reqStatus); this._callProtocolErrorHandlers(req); if (reqStatus >= 400 && reqStatus < 500) { this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING, null); this._conn._doDisconnect(); } } else { Strophe.error("request id "+req.id+"."+req.sends+" error "+reqStatus+" happened"); } if (!valid_request && !too_many_retries) { this._throttledRequestHandler(); } else if (too_many_retries && !this._conn.connected) { this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "giving-up"); } }, /** PrivateFunction: _processRequest * _Private_ function to process a request in the queue. * * This function takes requests off the queue and sends them and * restarts dead requests. * * Parameters: * (Integer) i - The index of the request in the queue. */ _processRequest: function (i) { var self = this; var req = this._requests[i]; var reqStatus = this._getRequestStatus(req, -1); // make sure we limit the number of retries if (req.sends > this._conn.maxRetries) { this._conn._onDisconnectTimeout(); return; } var time_elapsed = req.age(); var primaryTimeout = (!isNaN(time_elapsed) && time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)); var secondaryTimeout = (req.dead !== null && req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)); var requestCompletedWithServerError = (req.xhr.readyState === 4 && (reqStatus < 1 || reqStatus >= 500)); if (primaryTimeout || secondaryTimeout || requestCompletedWithServerError) { if (secondaryTimeout) { Strophe.error("Request " + this._requests[i].id + " timed out (secondary), restarting"); } req.abort = true; req.xhr.abort(); // setting to null fails on IE6, so set to empty function req.xhr.onreadystatechange = function () {}; this._requests[i] = new Strophe.Request(req.xmlData, req.origFunc, req.rid, req.sends); req = this._requests[i]; } if (req.xhr.readyState === 0) { Strophe.debug("request id "+req.id+"."+req.sends+" posting"); try { var contentType = this._conn.options.contentType || "text/xml; charset=utf-8"; req.xhr.open("POST", this._conn.service, this._conn.options.sync ? false : true); if (typeof req.xhr.setRequestHeader !== 'undefined') { // IE9 doesn't have setRequestHeader req.xhr.setRequestHeader("Content-Type", contentType); } if (this._conn.options.withCredentials) { req.xhr.withCredentials = true; } } catch (e2) { Strophe.error("XHR open failed: " + e2.toString()); if (!this._conn.connected) { this._conn._changeConnectStatus( Strophe.Status.CONNFAIL, "bad-service"); } this._conn.disconnect(); return; } // Fires the XHR request -- may be invoked immediately // or on a gradually expanding retry window for reconnects var sendFunc = function () { req.date = new Date(); if (self._conn.options.customHeaders){ var headers = self._conn.options.customHeaders; for (var header in headers) { if (headers.hasOwnProperty(header)) { req.xhr.setRequestHeader(header, headers[header]); } } } req.xhr.send(req.data); }; // Implement progressive backoff for reconnects -- // First retry (send === 1) should also be instantaneous if (req.sends > 1) { // Using a cube of the retry number creates a nicely // expanding retry window var backoff = Math.min(Math.floor(Strophe.TIMEOUT * this.wait), Math.pow(req.sends, 3)) * 1000; setTimeout(function() { // XXX: setTimeout should be called only with function expressions (23974bc1) sendFunc(); }, backoff); } else { sendFunc(); } req.sends++; if (this._conn.xmlOutput !== Strophe.Connection.prototype.xmlOutput) { if (req.xmlData.nodeName === this.strip && req.xmlData.childNodes.length) { this._conn.xmlOutput(req.xmlData.childNodes[0]); } else { this._conn.xmlOutput(req.xmlData); } } if (this._conn.rawOutput !== Strophe.Connection.prototype.rawOutput) { this._conn.rawOutput(req.data); } } else { Strophe.debug("_processRequest: " + (i === 0 ? "first" : "second") + " request has readyState of " + req.xhr.readyState); } }, /** PrivateFunction: _removeRequest * _Private_ function to remove a request from the queue. * * Parameters: * (Strophe.Request) req - The request to remove. */ _removeRequest: function (req) { Strophe.debug("removing request"); var i; for (i = this._requests.length - 1; i >= 0; i--) { if (req === this._requests[i]) { this._requests.splice(i, 1); } } // IE6 fails on setting to null, so set to empty function req.xhr.onreadystatechange = function () {}; this._throttledRequestHandler(); }, /** PrivateFunction: _restartRequest * _Private_ function to restart a request that is presumed dead. * * Parameters: * (Integer) i - The index of the request in the queue. */ _restartRequest: function (i) { var req = this._requests[i]; if (req.dead === null) { req.dead = new Date(); } this._processRequest(i); }, /** PrivateFunction: _reqToData * _Private_ function to get a stanza out of a request. * * Tries to extract a stanza out of a Request Object. * When this fails the current connection will be disconnected. * * Parameters: * (Object) req - The Request. * * Returns: * The stanza that was passed. */ _reqToData: function (req) { try { return req.getResponse(); } catch (e) { if (e !== "parsererror") { throw e; } this._conn.disconnect("strophe-parsererror"); } }, /** PrivateFunction: _sendTerminate * _Private_ function to send initial disconnect sequence. * * This is the first step in a graceful disconnect. It sends * the BOSH server a terminate body and includes an unavailable * presence if authentication has completed. */ _sendTerminate: function (pres) { Strophe.info("_sendTerminate was called"); var body = this._buildBody().attrs({type: "terminate"}); if (pres) { body.cnode(pres.tree()); } var req = new Strophe.Request( body.tree(), this._onRequestStateChange.bind( this, this._conn._dataRecv.bind(this._conn)), body.tree().getAttribute("rid") ); this._requests.push(req); this._throttledRequestHandler(); }, /** PrivateFunction: _send * _Private_ part of the Connection.send function for BOSH * * Just triggers the RequestHandler to send the messages that are in the queue */ _send: function () { clearTimeout(this._conn._idleTimeout); this._throttledRequestHandler(); // XXX: setTimeout should be called only with function expressions (23974bc1) this._conn._idleTimeout = setTimeout(function() { this._onIdle(); }.bind(this._conn), 100); }, /** PrivateFunction: _sendRestart * * Send an xmpp:restart stanza. */ _sendRestart: function () { this._throttledRequestHandler(); clearTimeout(this._conn._idleTimeout); }, /** PrivateFunction: _throttledRequestHandler * _Private_ function to throttle requests to the connection window. * * This function makes sure we don't send requests so fast that the * request ids overflow the connection window in the case that one * request died. */ _throttledRequestHandler: function () { if (!this._requests) { Strophe.debug("_throttledRequestHandler called with " + "undefined requests"); } else { Strophe.debug("_throttledRequestHandler called with " + this._requests.length + " requests"); } if (!this._requests || this._requests.length === 0) { return; } if (this._requests.length > 0) { this._processRequest(0); } if (this._requests.length > 1 && Math.abs(this._requests[0].rid - this._requests[1].rid) < this.window) { this._processRequest(1); } } }; return Strophe; })); strophejs-1.2.14+dfsg/src/core.js000066400000000000000000003647121320017573300166320ustar00rootroot00000000000000/* This program is distributed under the terms of the MIT license. Please see the LICENSE file for details. Copyright 2006-2008, OGG, LLC */ /* jshint undef: true, unused: true:, noarg: true, latedef: true */ /*global define, document, sessionStorage, setTimeout, clearTimeout, ActiveXObject, DOMParser, btoa, atob */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define([ 'strophe-sha1', 'strophe-md5', 'strophe-utils' ], function () { return factory.apply(this, arguments); }); } else { // Browser globals var o = factory(root.SHA1, root.MD5, root.stropheUtils); root.Strophe = o.Strophe; root.$build = o.$build; root.$iq = o.$iq; root.$msg = o.$msg; root.$pres = o.$pres; root.SHA1 = o.SHA1; root.MD5 = o.MD5; root.b64_hmac_sha1 = o.SHA1.b64_hmac_sha1; root.b64_sha1 = o.SHA1.b64_sha1; root.str_hmac_sha1 = o.SHA1.str_hmac_sha1; root.str_sha1 = o.SHA1.str_sha1; } }(this, function (SHA1, MD5, utils) { var Strophe; /** Function: $build * Create a Strophe.Builder. * This is an alias for 'new Strophe.Builder(name, attrs)'. * * Parameters: * (String) name - The root element name. * (Object) attrs - The attributes for the root element in object notation. * * Returns: * A new Strophe.Builder object. */ function $build(name, attrs) { return new Strophe.Builder(name, attrs); } /** Function: $msg * Create a Strophe.Builder with a element as the root. * * Parameters: * (Object) attrs - The element attributes in object notation. * * Returns: * A new Strophe.Builder object. */ function $msg(attrs) { return new Strophe.Builder("message", attrs); } /** Function: $iq * Create a Strophe.Builder with an element as the root. * * Parameters: * (Object) attrs - The element attributes in object notation. * * Returns: * A new Strophe.Builder object. */ function $iq(attrs) { return new Strophe.Builder("iq", attrs); } /** Function: $pres * Create a Strophe.Builder with a element as the root. * * Parameters: * (Object) attrs - The element attributes in object notation. * * Returns: * A new Strophe.Builder object. */ function $pres(attrs) { return new Strophe.Builder("presence", attrs); } /** Class: Strophe * An object container for all Strophe library functions. * * This class is just a container for all the objects and constants * used in the library. It is not meant to be instantiated, but to * provide a namespace for library objects, constants, and functions. */ Strophe = { /** Constant: VERSION */ VERSION: "@VERSION@", /** Constants: XMPP Namespace Constants * Common namespace constants from the XMPP RFCs and XEPs. * * NS.HTTPBIND - HTTP BIND namespace from XEP 124. * NS.BOSH - BOSH namespace from XEP 206. * NS.CLIENT - Main XMPP client namespace. * NS.AUTH - Legacy authentication namespace. * NS.ROSTER - Roster operations namespace. * NS.PROFILE - Profile namespace. * NS.DISCO_INFO - Service discovery info namespace from XEP 30. * NS.DISCO_ITEMS - Service discovery items namespace from XEP 30. * NS.MUC - Multi-User Chat namespace from XEP 45. * NS.SASL - XMPP SASL namespace from RFC 3920. * NS.STREAM - XMPP Streams namespace from RFC 3920. * NS.BIND - XMPP Binding namespace from RFC 3920. * NS.SESSION - XMPP Session namespace from RFC 3920. * NS.XHTML_IM - XHTML-IM namespace from XEP 71. * NS.XHTML - XHTML body namespace from XEP 71. */ NS: { HTTPBIND: "http://jabber.org/protocol/httpbind", BOSH: "urn:xmpp:xbosh", CLIENT: "jabber:client", AUTH: "jabber:iq:auth", ROSTER: "jabber:iq:roster", PROFILE: "jabber:iq:profile", DISCO_INFO: "http://jabber.org/protocol/disco#info", DISCO_ITEMS: "http://jabber.org/protocol/disco#items", MUC: "http://jabber.org/protocol/muc", SASL: "urn:ietf:params:xml:ns:xmpp-sasl", STREAM: "http://etherx.jabber.org/streams", FRAMING: "urn:ietf:params:xml:ns:xmpp-framing", BIND: "urn:ietf:params:xml:ns:xmpp-bind", SESSION: "urn:ietf:params:xml:ns:xmpp-session", VERSION: "jabber:iq:version", STANZAS: "urn:ietf:params:xml:ns:xmpp-stanzas", XHTML_IM: "http://jabber.org/protocol/xhtml-im", XHTML: "http://www.w3.org/1999/xhtml" }, /** Constants: XHTML_IM Namespace * contains allowed tags, tag attributes, and css properties. * Used in the createHtml function to filter incoming html into the allowed XHTML-IM subset. * See http://xmpp.org/extensions/xep-0071.html#profile-summary for the list of recommended * allowed tags and their attributes. */ XHTML: { tags: ['a','blockquote','br','cite','em','img','li','ol','p','span','strong','ul','body'], attributes: { 'a': ['href'], 'blockquote': ['style'], 'br': [], 'cite': ['style'], 'em': [], 'img': ['src', 'alt', 'style', 'height', 'width'], 'li': ['style'], 'ol': ['style'], 'p': ['style'], 'span': ['style'], 'strong': [], 'ul': ['style'], 'body': [] }, css: ['background-color','color','font-family','font-size','font-style','font-weight','margin-left','margin-right','text-align','text-decoration'], /** Function: XHTML.validTag * * Utility method to determine whether a tag is allowed * in the XHTML_IM namespace. * * XHTML tag names are case sensitive and must be lower case. */ validTag: function(tag) { for (var i = 0; i < Strophe.XHTML.tags.length; i++) { if (tag === Strophe.XHTML.tags[i]) { return true; } } return false; }, /** Function: XHTML.validAttribute * * Utility method to determine whether an attribute is allowed * as recommended per XEP-0071 * * XHTML attribute names are case sensitive and must be lower case. */ validAttribute: function(tag, attribute) { if (typeof Strophe.XHTML.attributes[tag] !== 'undefined' && Strophe.XHTML.attributes[tag].length > 0) { for (var i = 0; i < Strophe.XHTML.attributes[tag].length; i++) { if (attribute === Strophe.XHTML.attributes[tag][i]) { return true; } } } return false; }, validCSS: function(style) { for (var i = 0; i < Strophe.XHTML.css.length; i++) { if (style === Strophe.XHTML.css[i]) { return true; } } return false; } }, /** Constants: Connection Status Constants * Connection status constants for use by the connection handler * callback. * * Status.ERROR - An error has occurred * Status.CONNECTING - The connection is currently being made * Status.CONNFAIL - The connection attempt failed * Status.AUTHENTICATING - The connection is authenticating * Status.AUTHFAIL - The authentication attempt failed * Status.CONNECTED - The connection has succeeded * Status.DISCONNECTED - The connection has been terminated * Status.DISCONNECTING - The connection is currently being terminated * Status.ATTACHED - The connection has been attached * Status.REDIRECT - The connection has been redirected * Status.CONNTIMEOUT - The connection has timed out */ Status: { ERROR: 0, CONNECTING: 1, CONNFAIL: 2, AUTHENTICATING: 3, AUTHFAIL: 4, CONNECTED: 5, DISCONNECTED: 6, DISCONNECTING: 7, ATTACHED: 8, REDIRECT: 9, CONNTIMEOUT: 10 }, /** Constants: Log Level Constants * Logging level indicators. * * LogLevel.DEBUG - Debug output * LogLevel.INFO - Informational output * LogLevel.WARN - Warnings * LogLevel.ERROR - Errors * LogLevel.FATAL - Fatal errors */ LogLevel: { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, FATAL: 4 }, /** PrivateConstants: DOM Element Type Constants * DOM element types. * * ElementType.NORMAL - Normal element. * ElementType.TEXT - Text data element. * ElementType.FRAGMENT - XHTML fragment element. */ ElementType: { NORMAL: 1, TEXT: 3, CDATA: 4, FRAGMENT: 11 }, /** PrivateConstants: Timeout Values * Timeout values for error states. These values are in seconds. * These should not be changed unless you know exactly what you are * doing. * * TIMEOUT - Timeout multiplier. A waiting request will be considered * failed after Math.floor(TIMEOUT * wait) seconds have elapsed. * This defaults to 1.1, and with default wait, 66 seconds. * SECONDARY_TIMEOUT - Secondary timeout multiplier. In cases where * Strophe can detect early failure, it will consider the request * failed if it doesn't return after * Math.floor(SECONDARY_TIMEOUT * wait) seconds have elapsed. * This defaults to 0.1, and with default wait, 6 seconds. */ TIMEOUT: 1.1, SECONDARY_TIMEOUT: 0.1, /** Function: addNamespace * This function is used to extend the current namespaces in * Strophe.NS. It takes a key and a value with the key being the * name of the new namespace, with its actual value. * For example: * Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub"); * * Parameters: * (String) name - The name under which the namespace will be * referenced under Strophe.NS * (String) value - The actual namespace. */ addNamespace: function (name, value) { Strophe.NS[name] = value; }, /** Function: forEachChild * Map a function over some or all child elements of a given element. * * This is a small convenience function for mapping a function over * some or all of the children of an element. If elemName is null, all * children will be passed to the function, otherwise only children * whose tag names match elemName will be passed. * * Parameters: * (XMLElement) elem - The element to operate on. * (String) elemName - The child element tag name filter. * (Function) func - The function to apply to each child. This * function should take a single argument, a DOM element. */ forEachChild: function (elem, elemName, func) { var i, childNode; for (i = 0; i < elem.childNodes.length; i++) { childNode = elem.childNodes[i]; if (childNode.nodeType === Strophe.ElementType.NORMAL && (!elemName || this.isTagEqual(childNode, elemName))) { func(childNode); } } }, /** Function: isTagEqual * Compare an element's tag name with a string. * * This function is case sensitive. * * Parameters: * (XMLElement) el - A DOM element. * (String) name - The element name. * * Returns: * true if the element's tag name matches _el_, and false * otherwise. */ isTagEqual: function (el, name) { return el.tagName === name; }, /** PrivateVariable: _xmlGenerator * _Private_ variable that caches a DOM document to * generate elements. */ _xmlGenerator: null, /** PrivateFunction: _makeGenerator * _Private_ function that creates a dummy XML DOM document to serve as * an element and text node generator. */ _makeGenerator: function () { var doc; // IE9 does implement createDocument(); however, using it will cause the browser to leak memory on page unload. // Here, we test for presence of createDocument() plus IE's proprietary documentMode attribute, which would be // less than 10 in the case of IE9 and below. if (document.implementation.createDocument === undefined || document.implementation.createDocument && document.documentMode && document.documentMode < 10) { doc = this._getIEXmlDom(); doc.appendChild(doc.createElement('strophe')); } else { doc = document.implementation .createDocument('jabber:client', 'strophe', null); } return doc; }, /** Function: xmlGenerator * Get the DOM document to generate elements. * * Returns: * The currently used DOM document. */ xmlGenerator: function () { if (!Strophe._xmlGenerator) { Strophe._xmlGenerator = Strophe._makeGenerator(); } return Strophe._xmlGenerator; }, /** PrivateFunction: _getIEXmlDom * Gets IE xml doc object * * Returns: * A Microsoft XML DOM Object * See Also: * http://msdn.microsoft.com/en-us/library/ms757837%28VS.85%29.aspx */ _getIEXmlDom : function() { var doc = null; var docStrings = [ "Msxml2.DOMDocument.6.0", "Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "MSXML2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM" ]; for (var d = 0; d < docStrings.length; d++) { if (doc === null) { try { doc = new ActiveXObject(docStrings[d]); } catch (e) { doc = null; } } else { break; } } return doc; }, /** Function: xmlElement * Create an XML DOM element. * * This function creates an XML DOM element correctly across all * implementations. Note that these are not HTML DOM elements, which * aren't appropriate for XMPP stanzas. * * Parameters: * (String) name - The name for the element. * (Array|Object) attrs - An optional array or object containing * key/value pairs to use as element attributes. The object should * be in the format {'key': 'value'} or {key: 'value'}. The array * should have the format [['key1', 'value1'], ['key2', 'value2']]. * (String) text - The text child data for the element. * * Returns: * A new XML DOM element. */ xmlElement: function (name) { if (!name) { return null; } var node = Strophe.xmlGenerator().createElement(name); // FIXME: this should throw errors if args are the wrong type or // there are more than two optional args var a, i, k; for (a = 1; a < arguments.length; a++) { var arg = arguments[a]; if (!arg) { continue; } if (typeof(arg) === "string" || typeof(arg) === "number") { node.appendChild(Strophe.xmlTextNode(arg)); } else if (typeof(arg) === "object" && typeof(arg.sort) === "function") { for (i = 0; i < arg.length; i++) { var attr = arg[i]; if (typeof(attr) === "object" && typeof(attr.sort) === "function" && attr[1] !== undefined && attr[1] !== null) { node.setAttribute(attr[0], attr[1]); } } } else if (typeof(arg) === "object") { for (k in arg) { if (arg.hasOwnProperty(k)) { if (arg[k] !== undefined && arg[k] !== null) { node.setAttribute(k, arg[k]); } } } } } return node; }, /* Function: xmlescape * Excapes invalid xml characters. * * Parameters: * (String) text - text to escape. * * Returns: * Escaped text. */ xmlescape: function(text) { text = text.replace(/\&/g, "&"); text = text.replace(//g, ">"); text = text.replace(/'/g, "'"); text = text.replace(/"/g, """); return text; }, /* Function: xmlunescape * Unexcapes invalid xml characters. * * Parameters: * (String) text - text to unescape. * * Returns: * Unescaped text. */ xmlunescape: function(text) { text = text.replace(/\&/g, "&"); text = text.replace(/</g, "<"); text = text.replace(/>/g, ">"); text = text.replace(/'/g, "'"); text = text.replace(/"/g, "\""); return text; }, /** Function: xmlTextNode * Creates an XML DOM text node. * * Provides a cross implementation version of document.createTextNode. * * Parameters: * (String) text - The content of the text node. * * Returns: * A new XML DOM text node. */ xmlTextNode: function (text) { return Strophe.xmlGenerator().createTextNode(text); }, /** Function: xmlHtmlNode * Creates an XML DOM html node. * * Parameters: * (String) html - The content of the html node. * * Returns: * A new XML DOM text node. */ xmlHtmlNode: function (html) { var node; //ensure text is escaped if (DOMParser) { var parser = new DOMParser(); node = parser.parseFromString(html, "text/xml"); } else { node = new ActiveXObject("Microsoft.XMLDOM"); node.async="false"; node.loadXML(html); } return node; }, /** Function: getText * Get the concatenation of all text children of an element. * * Parameters: * (XMLElement) elem - A DOM element. * * Returns: * A String with the concatenated text of all text element children. */ getText: function (elem) { if (!elem) { return null; } var str = ""; if (elem.childNodes.length === 0 && elem.nodeType === Strophe.ElementType.TEXT) { str += elem.nodeValue; } for (var i = 0; i < elem.childNodes.length; i++) { if (elem.childNodes[i].nodeType === Strophe.ElementType.TEXT) { str += elem.childNodes[i].nodeValue; } } return Strophe.xmlescape(str); }, /** Function: copyElement * Copy an XML DOM element. * * This function copies a DOM element and all its descendants and returns * the new copy. * * Parameters: * (XMLElement) elem - A DOM element. * * Returns: * A new, copied DOM element tree. */ copyElement: function (elem) { var i, el; if (elem.nodeType === Strophe.ElementType.NORMAL) { el = Strophe.xmlElement(elem.tagName); for (i = 0; i < elem.attributes.length; i++) { el.setAttribute(elem.attributes[i].nodeName, elem.attributes[i].value); } for (i = 0; i < elem.childNodes.length; i++) { el.appendChild(Strophe.copyElement(elem.childNodes[i])); } } else if (elem.nodeType === Strophe.ElementType.TEXT) { el = Strophe.xmlGenerator().createTextNode(elem.nodeValue); } return el; }, /** Function: createHtml * Copy an HTML DOM element into an XML DOM. * * This function copies a DOM element and all its descendants and returns * the new copy. * * Parameters: * (HTMLElement) elem - A DOM element. * * Returns: * A new, copied DOM element tree. */ createHtml: function (elem) { var i, el, j, tag, attribute, value, css, cssAttrs, attr, cssName, cssValue; if (elem.nodeType === Strophe.ElementType.NORMAL) { tag = elem.nodeName.toLowerCase(); // XHTML tags must be lower case. if(Strophe.XHTML.validTag(tag)) { try { el = Strophe.xmlElement(tag); for(i = 0; i < Strophe.XHTML.attributes[tag].length; i++) { attribute = Strophe.XHTML.attributes[tag][i]; value = elem.getAttribute(attribute); if(typeof value === 'undefined' || value === null || value === '' || value === false || value === 0) { continue; } if(attribute === 'style' && typeof value === 'object') { if(typeof value.cssText !== 'undefined') { value = value.cssText; // we're dealing with IE, need to get CSS out } } // filter out invalid css styles if(attribute === 'style') { css = []; cssAttrs = value.split(';'); for(j = 0; j < cssAttrs.length; j++) { attr = cssAttrs[j].split(':'); cssName = attr[0].replace(/^\s*/, "").replace(/\s*$/, "").toLowerCase(); if(Strophe.XHTML.validCSS(cssName)) { cssValue = attr[1].replace(/^\s*/, "").replace(/\s*$/, ""); css.push(cssName + ': ' + cssValue); } } if(css.length > 0) { value = css.join('; '); el.setAttribute(attribute, value); } } else { el.setAttribute(attribute, value); } } for (i = 0; i < elem.childNodes.length; i++) { el.appendChild(Strophe.createHtml(elem.childNodes[i])); } } catch(e) { // invalid elements el = Strophe.xmlTextNode(''); } } else { el = Strophe.xmlGenerator().createDocumentFragment(); for (i = 0; i < elem.childNodes.length; i++) { el.appendChild(Strophe.createHtml(elem.childNodes[i])); } } } else if (elem.nodeType === Strophe.ElementType.FRAGMENT) { el = Strophe.xmlGenerator().createDocumentFragment(); for (i = 0; i < elem.childNodes.length; i++) { el.appendChild(Strophe.createHtml(elem.childNodes[i])); } } else if (elem.nodeType === Strophe.ElementType.TEXT) { el = Strophe.xmlTextNode(elem.nodeValue); } return el; }, /** Function: escapeNode * Escape the node part (also called local part) of a JID. * * Parameters: * (String) node - A node (or local part). * * Returns: * An escaped node (or local part). */ escapeNode: function (node) { if (typeof node !== "string") { return node; } return node.replace(/^\s+|\s+$/g, '') .replace(/\\/g, "\\5c") .replace(/ /g, "\\20") .replace(/\"/g, "\\22") .replace(/\&/g, "\\26") .replace(/\'/g, "\\27") .replace(/\//g, "\\2f") .replace(/:/g, "\\3a") .replace(//g, "\\3e") .replace(/@/g, "\\40"); }, /** Function: unescapeNode * Unescape a node part (also called local part) of a JID. * * Parameters: * (String) node - A node (or local part). * * Returns: * An unescaped node (or local part). */ unescapeNode: function (node) { if (typeof node !== "string") { return node; } return node.replace(/\\20/g, " ") .replace(/\\22/g, '"') .replace(/\\26/g, "&") .replace(/\\27/g, "'") .replace(/\\2f/g, "/") .replace(/\\3a/g, ":") .replace(/\\3c/g, "<") .replace(/\\3e/g, ">") .replace(/\\40/g, "@") .replace(/\\5c/g, "\\"); }, /** Function: getNodeFromJid * Get the node portion of a JID String. * * Parameters: * (String) jid - A JID. * * Returns: * A String containing the node. */ getNodeFromJid: function (jid) { if (jid.indexOf("@") < 0) { return null; } return jid.split("@")[0]; }, /** Function: getDomainFromJid * Get the domain portion of a JID String. * * Parameters: * (String) jid - A JID. * * Returns: * A String containing the domain. */ getDomainFromJid: function (jid) { var bare = Strophe.getBareJidFromJid(jid); if (bare.indexOf("@") < 0) { return bare; } else { var parts = bare.split("@"); parts.splice(0, 1); return parts.join('@'); } }, /** Function: getResourceFromJid * Get the resource portion of a JID String. * * Parameters: * (String) jid - A JID. * * Returns: * A String containing the resource. */ getResourceFromJid: function (jid) { var s = jid.split("/"); if (s.length < 2) { return null; } s.splice(0, 1); return s.join('/'); }, /** Function: getBareJidFromJid * Get the bare JID from a JID String. * * Parameters: * (String) jid - A JID. * * Returns: * A String containing the bare JID. */ getBareJidFromJid: function (jid) { return jid ? jid.split("/")[0] : null; }, /** PrivateFunction: _handleError * _Private_ function that properly logs an error to the console */ _handleError: function (e) { if (typeof e.stack !== "undefined") { Strophe.fatal(e.stack); } if (e.sourceURL) { Strophe.fatal("error: " + this.handler + " " + e.sourceURL + ":" + e.line + " - " + e.name + ": " + e.message); } else if (e.fileName) { Strophe.fatal("error: " + this.handler + " " + e.fileName + ":" + e.lineNumber + " - " + e.name + ": " + e.message); } else { Strophe.fatal("error: " + e.message); } }, /** Function: log * User overrideable logging function. * * This function is called whenever the Strophe library calls any * of the logging functions. The default implementation of this * function does nothing. If client code wishes to handle the logging * messages, it should override this with * > Strophe.log = function (level, msg) { * > (user code here) * > }; * * Please note that data sent and received over the wire is logged * via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput(). * * The different levels and their meanings are * * DEBUG - Messages useful for debugging purposes. * INFO - Informational messages. This is mostly information like * 'disconnect was called' or 'SASL auth succeeded'. * WARN - Warnings about potential problems. This is mostly used * to report transient connection errors like request timeouts. * ERROR - Some error occurred. * FATAL - A non-recoverable fatal error occurred. * * Parameters: * (Integer) level - The log level of the log message. This will * be one of the values in Strophe.LogLevel. * (String) msg - The log message. */ /* jshint ignore:start */ log: function (level, msg) { return; }, /* jshint ignore:end */ /** Function: debug * Log a message at the Strophe.LogLevel.DEBUG level. * * Parameters: * (String) msg - The log message. */ debug: function(msg) { this.log(this.LogLevel.DEBUG, msg); }, /** Function: info * Log a message at the Strophe.LogLevel.INFO level. * * Parameters: * (String) msg - The log message. */ info: function (msg) { this.log(this.LogLevel.INFO, msg); }, /** Function: warn * Log a message at the Strophe.LogLevel.WARN level. * * Parameters: * (String) msg - The log message. */ warn: function (msg) { this.log(this.LogLevel.WARN, msg); }, /** Function: error * Log a message at the Strophe.LogLevel.ERROR level. * * Parameters: * (String) msg - The log message. */ error: function (msg) { this.log(this.LogLevel.ERROR, msg); }, /** Function: fatal * Log a message at the Strophe.LogLevel.FATAL level. * * Parameters: * (String) msg - The log message. */ fatal: function (msg) { this.log(this.LogLevel.FATAL, msg); }, /** Function: serialize * Render a DOM element and all descendants to a String. * * Parameters: * (XMLElement) elem - A DOM element. * * Returns: * The serialized element tree as a String. */ serialize: function (elem) { var result; if (!elem) { return null; } if (typeof(elem.tree) === "function") { elem = elem.tree(); } var nodeName = elem.nodeName; var i, child; if (elem.getAttribute("_realname")) { nodeName = elem.getAttribute("_realname"); } result = "<" + nodeName; for (i = 0; i < elem.attributes.length; i++) { if(elem.attributes[i].nodeName !== "_realname") { result += " " + elem.attributes[i].nodeName + "='" + Strophe.xmlescape(elem.attributes[i].value) + "'"; } } if (elem.childNodes.length > 0) { result += ">"; for (i = 0; i < elem.childNodes.length; i++) { child = elem.childNodes[i]; switch( child.nodeType ){ case Strophe.ElementType.NORMAL: // normal element, so recurse result += Strophe.serialize(child); break; case Strophe.ElementType.TEXT: // text element to escape values result += Strophe.xmlescape(child.nodeValue); break; case Strophe.ElementType.CDATA: // cdata section so don't escape values result += ""; } } result += ""; } else { result += "/>"; } return result; }, /** PrivateVariable: _requestId * _Private_ variable that keeps track of the request ids for * connections. */ _requestId: 0, /** PrivateVariable: Strophe.connectionPlugins * _Private_ variable Used to store plugin names that need * initialization on Strophe.Connection construction. */ _connectionPlugins: {}, /** Function: addConnectionPlugin * Extends the Strophe.Connection object with the given plugin. * * Parameters: * (String) name - The name of the extension. * (Object) ptype - The plugin's prototype. */ addConnectionPlugin: function (name, ptype) { Strophe._connectionPlugins[name] = ptype; } }; /** Class: Strophe.Builder * XML DOM builder. * * This object provides an interface similar to JQuery but for building * DOM elements easily and rapidly. All the functions except for toString() * and tree() return the object, so calls can be chained. Here's an * example using the $iq() builder helper. * > $iq({to: 'you', from: 'me', type: 'get', id: '1'}) * > .c('query', {xmlns: 'strophe:example'}) * > .c('example') * > .toString() * * The above generates this XML fragment * > * > * > * > * > * The corresponding DOM manipulations to get a similar fragment would be * a lot more tedious and probably involve several helper variables. * * Since adding children makes new operations operate on the child, up() * is provided to traverse up the tree. To add two children, do * > builder.c('child1', ...).up().c('child2', ...) * The next operation on the Builder will be relative to the second child. */ /** Constructor: Strophe.Builder * Create a Strophe.Builder object. * * The attributes should be passed in object notation. For example * > var b = new Builder('message', {to: 'you', from: 'me'}); * or * > var b = new Builder('messsage', {'xml:lang': 'en'}); * * Parameters: * (String) name - The name of the root element. * (Object) attrs - The attributes for the root element in object notation. * * Returns: * A new Strophe.Builder. */ Strophe.Builder = function (name, attrs) { // Set correct namespace for jabber:client elements if (name === "presence" || name === "message" || name === "iq") { if (attrs && !attrs.xmlns) { attrs.xmlns = Strophe.NS.CLIENT; } else if (!attrs) { attrs = {xmlns: Strophe.NS.CLIENT}; } } // Holds the tree being built. this.nodeTree = Strophe.xmlElement(name, attrs); // Points to the current operation node. this.node = this.nodeTree; }; Strophe.Builder.prototype = { /** Function: tree * Return the DOM tree. * * This function returns the current DOM tree as an element object. This * is suitable for passing to functions like Strophe.Connection.send(). * * Returns: * The DOM tree as a element object. */ tree: function () { return this.nodeTree; }, /** Function: toString * Serialize the DOM tree to a String. * * This function returns a string serialization of the current DOM * tree. It is often used internally to pass data to a * Strophe.Request object. * * Returns: * The serialized DOM tree in a String. */ toString: function () { return Strophe.serialize(this.nodeTree); }, /** Function: up * Make the current parent element the new current element. * * This function is often used after c() to traverse back up the tree. * For example, to add two children to the same element * > builder.c('child1', {}).up().c('child2', {}); * * Returns: * The Stophe.Builder object. */ up: function () { this.node = this.node.parentNode; return this; }, /** Function: root * Make the root element the new current element. * * When at a deeply nested element in the tree, this function can be used * to jump back to the root of the tree, instead of having to repeatedly * call up(). * * Returns: * The Stophe.Builder object. */ root: function () { this.node = this.nodeTree; return this; }, /** Function: attrs * Add or modify attributes of the current element. * * The attributes should be passed in object notation. This function * does not move the current element pointer. * * Parameters: * (Object) moreattrs - The attributes to add/modify in object notation. * * Returns: * The Strophe.Builder object. */ attrs: function (moreattrs) { for (var k in moreattrs) { if (moreattrs.hasOwnProperty(k)) { if (moreattrs[k] === undefined) { this.node.removeAttribute(k); } else { this.node.setAttribute(k, moreattrs[k]); } } } return this; }, /** Function: c * Add a child to the current element and make it the new current * element. * * This function moves the current element pointer to the child, * unless text is provided. If you need to add another child, it * is necessary to use up() to go back to the parent in the tree. * * Parameters: * (String) name - The name of the child. * (Object) attrs - The attributes of the child in object notation. * (String) text - The text to add to the child. * * Returns: * The Strophe.Builder object. */ c: function (name, attrs, text) { var child = Strophe.xmlElement(name, attrs, text); this.node.appendChild(child); if (typeof text !== "string" && typeof text !=="number") { this.node = child; } return this; }, /** Function: cnode * Add a child to the current element and make it the new current * element. * * This function is the same as c() except that instead of using a * name and an attributes object to create the child it uses an * existing DOM element object. * * Parameters: * (XMLElement) elem - A DOM element. * * Returns: * The Strophe.Builder object. */ cnode: function (elem) { var impNode; var xmlGen = Strophe.xmlGenerator(); try { impNode = (xmlGen.importNode !== undefined); } catch (e) { impNode = false; } var newElem = impNode ? xmlGen.importNode(elem, true) : Strophe.copyElement(elem); this.node.appendChild(newElem); this.node = newElem; return this; }, /** Function: t * Add a child text element. * * This *does not* make the child the new current element since there * are no children of text elements. * * Parameters: * (String) text - The text data to append to the current element. * * Returns: * The Strophe.Builder object. */ t: function (text) { var child = Strophe.xmlTextNode(text); this.node.appendChild(child); return this; }, /** Function: h * Replace current element contents with the HTML passed in. * * This *does not* make the child the new current element * * Parameters: * (String) html - The html to insert as contents of current element. * * Returns: * The Strophe.Builder object. */ h: function (html) { var fragment = document.createElement('body'); // force the browser to try and fix any invalid HTML tags fragment.innerHTML = html; // copy cleaned html into an xml dom var xhtml = Strophe.createHtml(fragment); while(xhtml.childNodes.length > 0) { this.node.appendChild(xhtml.childNodes[0]); } return this; } }; /** PrivateClass: Strophe.Handler * _Private_ helper class for managing stanza handlers. * * A Strophe.Handler encapsulates a user provided callback function to be * executed when matching stanzas are received by the connection. * Handlers can be either one-off or persistant depending on their * return value. Returning true will cause a Handler to remain active, and * returning false will remove the Handler. * * Users will not use Strophe.Handler objects directly, but instead they * will use Strophe.Connection.addHandler() and * Strophe.Connection.deleteHandler(). */ /** PrivateConstructor: Strophe.Handler * Create and initialize a new Strophe.Handler. * * Parameters: * (Function) handler - A function to be executed when the handler is run. * (String) ns - The namespace to match. * (String) name - The element name to match. * (String) type - The element type to match. * (String) id - The element id attribute to match. * (String) from - The element from attribute to match. * (Object) options - Handler options * * Returns: * A new Strophe.Handler object. */ Strophe.Handler = function (handler, ns, name, type, id, from, options) { this.handler = handler; this.ns = ns; this.name = name; this.type = type; this.id = id; this.options = options || {'matchBareFromJid': false, 'ignoreNamespaceFragment': false}; // BBB: Maintain backward compatibility with old `matchBare` option if (this.options.matchBare) { Strophe.warn('The "matchBare" option is deprecated, use "matchBareFromJid" instead.'); this.options.matchBareFromJid = this.options.matchBare; delete this.options.matchBare; } if (this.options.matchBareFromJid) { this.from = from ? Strophe.getBareJidFromJid(from) : null; } else { this.from = from; } // whether the handler is a user handler or a system handler this.user = true; }; Strophe.Handler.prototype = { /** PrivateFunction: getNamespace * Returns the XML namespace attribute on an element. * If `ignoreNamespaceFragment` was passed in for this handler, then the * URL fragment will be stripped. * * Parameters: * (XMLElement) elem - The XML element with the namespace. * * Returns: * The namespace, with optionally the fragment stripped. */ getNamespace: function (elem) { var elNamespace = elem.getAttribute("xmlns"); if (elNamespace && this.options.ignoreNamespaceFragment) { elNamespace = elNamespace.split('#')[0]; } return elNamespace; }, /** PrivateFunction: namespaceMatch * Tests if a stanza matches the namespace set for this Strophe.Handler. * * Parameters: * (XMLElement) elem - The XML element to test. * * Returns: * true if the stanza matches and false otherwise. */ namespaceMatch: function (elem) { var nsMatch = false; if (!this.ns) { return true; } else { var that = this; Strophe.forEachChild(elem, null, function (elem) { if (that.getNamespace(elem) === that.ns) { nsMatch = true; } }); nsMatch = nsMatch || this.getNamespace(elem) === this.ns; } return nsMatch; }, /** PrivateFunction: isMatch * Tests if a stanza matches the Strophe.Handler. * * Parameters: * (XMLElement) elem - The XML element to test. * * Returns: * true if the stanza matches and false otherwise. */ isMatch: function (elem) { var from = elem.getAttribute('from'); if (this.options.matchBareFromJid) { from = Strophe.getBareJidFromJid(from); } var elem_type = elem.getAttribute("type"); if (this.namespaceMatch(elem) && (!this.name || Strophe.isTagEqual(elem, this.name)) && (!this.type || (Array.isArray(this.type) ? this.type.indexOf(elem_type) !== -1 : elem_type === this.type)) && (!this.id || elem.getAttribute("id") === this.id) && (!this.from || from === this.from)) { return true; } return false; }, /** PrivateFunction: run * Run the callback on a matching stanza. * * Parameters: * (XMLElement) elem - The DOM element that triggered the * Strophe.Handler. * * Returns: * A boolean indicating if the handler should remain active. */ run: function (elem) { var result = null; try { result = this.handler(elem); } catch (e) { Strophe._handleError(e); throw e; } return result; }, /** PrivateFunction: toString * Get a String representation of the Strophe.Handler object. * * Returns: * A String. */ toString: function () { return "{Handler: " + this.handler + "(" + this.name + "," + this.id + "," + this.ns + ")}"; } }; /** PrivateClass: Strophe.TimedHandler * _Private_ helper class for managing timed handlers. * * A Strophe.TimedHandler encapsulates a user provided callback that * should be called after a certain period of time or at regular * intervals. The return value of the callback determines whether the * Strophe.TimedHandler will continue to fire. * * Users will not use Strophe.TimedHandler objects directly, but instead * they will use Strophe.Connection.addTimedHandler() and * Strophe.Connection.deleteTimedHandler(). */ /** PrivateConstructor: Strophe.TimedHandler * Create and initialize a new Strophe.TimedHandler object. * * Parameters: * (Integer) period - The number of milliseconds to wait before the * handler is called. * (Function) handler - The callback to run when the handler fires. This * function should take no arguments. * * Returns: * A new Strophe.TimedHandler object. */ Strophe.TimedHandler = function (period, handler) { this.period = period; this.handler = handler; this.lastCalled = new Date().getTime(); this.user = true; }; Strophe.TimedHandler.prototype = { /** PrivateFunction: run * Run the callback for the Strophe.TimedHandler. * * Returns: * true if the Strophe.TimedHandler should be called again, and false * otherwise. */ run: function () { this.lastCalled = new Date().getTime(); return this.handler(); }, /** PrivateFunction: reset * Reset the last called time for the Strophe.TimedHandler. */ reset: function () { this.lastCalled = new Date().getTime(); }, /** PrivateFunction: toString * Get a string representation of the Strophe.TimedHandler object. * * Returns: * The string representation. */ toString: function () { return "{TimedHandler: " + this.handler + "(" + this.period +")}"; } }; /** Class: Strophe.Connection * XMPP Connection manager. * * This class is the main part of Strophe. It manages a BOSH or websocket * connection to an XMPP server and dispatches events to the user callbacks * as data arrives. It supports SASL PLAIN, SASL DIGEST-MD5, SASL SCRAM-SHA1 * and legacy authentication. * * After creating a Strophe.Connection object, the user will typically * call connect() with a user supplied callback to handle connection level * events like authentication failure, disconnection, or connection * complete. * * The user will also have several event handlers defined by using * addHandler() and addTimedHandler(). These will allow the user code to * respond to interesting stanzas or do something periodically with the * connection. These handlers will be active once authentication is * finished. * * To send data to the connection, use send(). */ /** Constructor: Strophe.Connection * Create and initialize a Strophe.Connection object. * * The transport-protocol for this connection will be chosen automatically * based on the given service parameter. URLs starting with "ws://" or * "wss://" will use WebSockets, URLs starting with "http://", "https://" * or without a protocol will use BOSH. * * To make Strophe connect to the current host you can leave out the protocol * and host part and just pass the path, e.g. * * > var conn = new Strophe.Connection("/http-bind/"); * * Options common to both Websocket and BOSH: * ------------------------------------------ * * cookies: * * The *cookies* option allows you to pass in cookies to be added to the * document. These cookies will then be included in the BOSH XMLHttpRequest * or in the websocket connection. * * The passed in value must be a map of cookie names and string values. * * > { "myCookie": { * > "value": "1234", * > "domain": ".example.org", * > "path": "/", * > "expires": expirationDate * > } * > } * * Note that cookies can't be set in this way for other domains (i.e. cross-domain). * Those cookies need to be set under those domains, for example they can be * set server-side by making a XHR call to that domain to ask it to set any * necessary cookies. * * mechanisms: * * The *mechanisms* option allows you to specify the SASL mechanisms that this * instance of Strophe.Connection (and therefore your XMPP client) will * support. * * The value must be an array of objects with Strophe.SASLMechanism * prototypes. * * If nothing is specified, then the following mechanisms (and their * priorities) are registered: * * OAUTHBEARER - 60 * SCRAM-SHA1 - 50 * DIGEST-MD5 - 40 * PLAIN - 30 * ANONYMOUS - 20 * EXTERNAL - 10 * * WebSocket options: * ------------------ * * If you want to connect to the current host with a WebSocket connection you * can tell Strophe to use WebSockets through a "protocol" attribute in the * optional options parameter. Valid values are "ws" for WebSocket and "wss" * for Secure WebSocket. * So to connect to "wss://CURRENT_HOSTNAME/xmpp-websocket" you would call * * > var conn = new Strophe.Connection("/xmpp-websocket/", {protocol: "wss"}); * * Note that relative URLs _NOT_ starting with a "/" will also include the path * of the current site. * * Also because downgrading security is not permitted by browsers, when using * relative URLs both BOSH and WebSocket connections will use their secure * variants if the current connection to the site is also secure (https). * * BOSH options: * ------------- * * By adding "sync" to the options, you can control if requests will * be made synchronously or not. The default behaviour is asynchronous. * If you want to make requests synchronous, make "sync" evaluate to true. * > var conn = new Strophe.Connection("/http-bind/", {sync: true}); * * You can also toggle this on an already established connection. * > conn.options.sync = true; * * The *customHeaders* option can be used to provide custom HTTP headers to be * included in the XMLHttpRequests made. * * The *keepalive* option can be used to instruct Strophe to maintain the * current BOSH session across interruptions such as webpage reloads. * * It will do this by caching the sessions tokens in sessionStorage, and when * "restore" is called it will check whether there are cached tokens with * which it can resume an existing session. * * The *withCredentials* option should receive a Boolean value and is used to * indicate wether cookies should be included in ajax requests (by default * they're not). * Set this value to true if you are connecting to a BOSH service * and for some reason need to send cookies to it. * In order for this to work cross-domain, the server must also enable * credentials by setting the Access-Control-Allow-Credentials response header * to "true". For most usecases however this setting should be false (which * is the default). * Additionally, when using Access-Control-Allow-Credentials, the * Access-Control-Allow-Origin header can't be set to the wildcard "*", but * instead must be restricted to actual domains. * * The *contentType* option can be set to change the default Content-Type * of "text/xml; charset=utf-8", which can be useful to reduce the amount of * CORS preflight requests that are sent to the server. * * Parameters: * (String) service - The BOSH or WebSocket service URL. * (Object) options - A hash of configuration options * * Returns: * A new Strophe.Connection object. */ Strophe.Connection = function (service, options) { // The service URL this.service = service; // Configuration options this.options = options || {}; var proto = this.options.protocol || ""; // Select protocal based on service or options if (service.indexOf("ws:") === 0 || service.indexOf("wss:") === 0 || proto.indexOf("ws") === 0) { this._proto = new Strophe.Websocket(this); } else { this._proto = new Strophe.Bosh(this); } /* The connected JID. */ this.jid = ""; /* the JIDs domain */ this.domain = null; /* stream:features */ this.features = null; // SASL this._sasl_data = {}; this.do_session = false; this.do_bind = false; // handler lists this.timedHandlers = []; this.handlers = []; this.removeTimeds = []; this.removeHandlers = []; this.addTimeds = []; this.addHandlers = []; this.protocolErrorHandlers = { 'HTTP': {}, 'websocket': {} }; this._idleTimeout = null; this._disconnectTimeout = null; this.authenticated = false; this.connected = false; this.disconnecting = false; this.do_authentication = true; this.paused = false; this.restored = false; this._data = []; this._uniqueId = 0; this._sasl_success_handler = null; this._sasl_failure_handler = null; this._sasl_challenge_handler = null; // Max retries before disconnecting this.maxRetries = 5; // Call onIdle callback every 1/10th of a second // XXX: setTimeout should be called only with function expressions (23974bc1) this._idleTimeout = setTimeout(function() { this._onIdle(); }.bind(this), 100); utils.addCookies(this.options.cookies); this.registerSASLMechanisms(this.options.mechanisms); // initialize plugins for (var k in Strophe._connectionPlugins) { if (Strophe._connectionPlugins.hasOwnProperty(k)) { var ptype = Strophe._connectionPlugins[k]; // jslint complaints about the below line, but this is fine var F = function () {}; // jshint ignore:line F.prototype = ptype; this[k] = new F(); this[k].init(this); } } }; Strophe.Connection.prototype = { /** Function: reset * Reset the connection. * * This function should be called after a connection is disconnected * before that connection is reused. */ reset: function () { this._proto._reset(); // SASL this.do_session = false; this.do_bind = false; // handler lists this.timedHandlers = []; this.handlers = []; this.removeTimeds = []; this.removeHandlers = []; this.addTimeds = []; this.addHandlers = []; this.authenticated = false; this.connected = false; this.disconnecting = false; this.restored = false; this._data = []; this._requests = []; this._uniqueId = 0; }, /** Function: pause * Pause the request manager. * * This will prevent Strophe from sending any more requests to the * server. This is very useful for temporarily pausing * BOSH-Connections while a lot of send() calls are happening quickly. * This causes Strophe to send the data in a single request, saving * many request trips. */ pause: function () { this.paused = true; }, /** Function: resume * Resume the request manager. * * This resumes after pause() has been called. */ resume: function () { this.paused = false; }, /** Function: getUniqueId * Generate a unique ID for use in elements. * * All stanzas are required to have unique id attributes. This * function makes creating these easy. Each connection instance has * a counter which starts from zero, and the value of this counter * plus a colon followed by the suffix becomes the unique id. If no * suffix is supplied, the counter is used as the unique id. * * Suffixes are used to make debugging easier when reading the stream * data, and their use is recommended. The counter resets to 0 for * every new connection for the same reason. For connections to the * same server that authenticate the same way, all the ids should be * the same, which makes it easy to see changes. This is useful for * automated testing as well. * * Parameters: * (String) suffix - A optional suffix to append to the id. * * Returns: * A unique string to be used for the id attribute. */ getUniqueId: function(suffix) { var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c === 'x' ? r : r & 0x3 | 0x8; return v.toString(16); }); if (typeof(suffix) === "string" || typeof(suffix) === "number") { return uuid + ":" + suffix; } else { return uuid + ""; } }, /** Function: addProtocolErrorHandler * Register a handler function for when a protocol (websocker or HTTP) * error occurs. * * NOTE: Currently only HTTP errors for BOSH requests are handled. * Patches that handle websocket errors would be very welcome. * * Parameters: * (String) protocol - 'HTTP' or 'websocket' * (Integer) status_code - Error status code (e.g 500, 400 or 404) * (Function) callback - Function that will fire on Http error * * Example: * function onError(err_code){ * //do stuff * } * * var conn = Strophe.connect('http://example.com/http-bind'); * conn.addProtocolErrorHandler('HTTP', 500, onError); * // Triggers HTTP 500 error and onError handler will be called * conn.connect('user_jid@incorrect_jabber_host', 'secret', onConnect); */ addProtocolErrorHandler: function(protocol, status_code, callback){ this.protocolErrorHandlers[protocol][status_code] = callback; }, /** Function: connect * Starts the connection process. * * As the connection process proceeds, the user supplied callback will * be triggered multiple times with status updates. The callback * should take two arguments - the status code and the error condition. * * The status code will be one of the values in the Strophe.Status * constants. The error condition will be one of the conditions * defined in RFC 3920 or the condition 'strophe-parsererror'. * * The Parameters _wait_, _hold_ and _route_ are optional and only relevant * for BOSH connections. Please see XEP 124 for a more detailed explanation * of the optional parameters. * * Parameters: * (String) jid - The user's JID. This may be a bare JID, * or a full JID. If a node is not supplied, SASL OAUTHBEARER or * SASL ANONYMOUS authentication will be attempted (OAUTHBEARER will * process the provided password value as an access token). * (String) pass - The user's password. * (Function) callback - The connect callback function. * (Integer) wait - The optional HTTPBIND wait value. This is the * time the server will wait before returning an empty result for * a request. The default setting of 60 seconds is recommended. * (Integer) hold - The optional HTTPBIND hold value. This is the * number of connections the server will hold at one time. This * should almost always be set to 1 (the default). * (String) route - The optional route value. * (String) authcid - The optional alternative authentication identity * (username) if intending to impersonate another user. * When using the SASL-EXTERNAL authentication mechanism, for example * with client certificates, then the authcid value is used to * determine whether an authorization JID (authzid) should be sent to * the server. The authzid should not be sent to the server if the * authzid and authcid are the same. So to prevent it from being sent * (for example when the JID is already contained in the client * certificate), set authcid to that same JID. See XEP-178 for more * details. */ connect: function (jid, pass, callback, wait, hold, route, authcid) { this.jid = jid; /** Variable: authzid * Authorization identity. */ this.authzid = Strophe.getBareJidFromJid(this.jid); /** Variable: authcid * Authentication identity (User name). */ this.authcid = authcid || Strophe.getNodeFromJid(this.jid); /** Variable: pass * Authentication identity (User password). */ this.pass = pass; /** Variable: servtype * Digest MD5 compatibility. */ this.servtype = "xmpp"; this.connect_callback = callback; this.disconnecting = false; this.connected = false; this.authenticated = false; this.restored = false; // parse jid for domain this.domain = Strophe.getDomainFromJid(this.jid); this._changeConnectStatus(Strophe.Status.CONNECTING, null); this._proto._connect(wait, hold, route); }, /** Function: attach * Attach to an already created and authenticated BOSH session. * * This function is provided to allow Strophe to attach to BOSH * sessions which have been created externally, perhaps by a Web * application. This is often used to support auto-login type features * without putting user credentials into the page. * * Parameters: * (String) jid - The full JID that is bound by the session. * (String) sid - The SID of the BOSH session. * (String) rid - The current RID of the BOSH session. This RID * will be used by the next request. * (Function) callback The connect callback function. * (Integer) wait - The optional HTTPBIND wait value. This is the * time the server will wait before returning an empty result for * a request. The default setting of 60 seconds is recommended. * Other settings will require tweaks to the Strophe.TIMEOUT value. * (Integer) hold - The optional HTTPBIND hold value. This is the * number of connections the server will hold at one time. This * should almost always be set to 1 (the default). * (Integer) wind - The optional HTTBIND window value. This is the * allowed range of request ids that are valid. The default is 5. */ attach: function (jid, sid, rid, callback, wait, hold, wind) { if (this._proto instanceof Strophe.Bosh) { this._proto._attach(jid, sid, rid, callback, wait, hold, wind); } else { throw { name: 'StropheSessionError', message: 'The "attach" method can only be used with a BOSH connection.' }; } }, /** Function: restore * Attempt to restore a cached BOSH session. * * This function is only useful in conjunction with providing the * "keepalive":true option when instantiating a new Strophe.Connection. * * When "keepalive" is set to true, Strophe will cache the BOSH tokens * RID (Request ID) and SID (Session ID) and then when this function is * called, it will attempt to restore the session from those cached * tokens. * * This function must therefore be called instead of connect or attach. * * For an example on how to use it, please see examples/restore.js * * Parameters: * (String) jid - The user's JID. This may be a bare JID or a full JID. * (Function) callback - The connect callback function. * (Integer) wait - The optional HTTPBIND wait value. This is the * time the server will wait before returning an empty result for * a request. The default setting of 60 seconds is recommended. * (Integer) hold - The optional HTTPBIND hold value. This is the * number of connections the server will hold at one time. This * should almost always be set to 1 (the default). * (Integer) wind - The optional HTTBIND window value. This is the * allowed range of request ids that are valid. The default is 5. */ restore: function (jid, callback, wait, hold, wind) { if (this._sessionCachingSupported()) { this._proto._restore(jid, callback, wait, hold, wind); } else { throw { name: 'StropheSessionError', message: 'The "restore" method can only be used with a BOSH connection.' }; } }, /** PrivateFunction: _sessionCachingSupported * Checks whether sessionStorage and JSON are supported and whether we're * using BOSH. */ _sessionCachingSupported: function () { if (this._proto instanceof Strophe.Bosh) { if (!JSON) { return false; } try { sessionStorage.setItem('_strophe_', '_strophe_'); sessionStorage.removeItem('_strophe_'); } catch (e) { return false; } return true; } return false; }, /** Function: xmlInput * User overrideable function that receives XML data coming into the * connection. * * The default function does nothing. User code can override this with * > Strophe.Connection.xmlInput = function (elem) { * > (user code) * > }; * * Due to limitations of current Browsers' XML-Parsers the opening and closing * tag for WebSocket-Connoctions will be passed as selfclosing here. * * BOSH-Connections will have all stanzas wrapped in a tag. See * if you want to strip this tag. * * Parameters: * (XMLElement) elem - The XML data received by the connection. */ /* jshint unused:false */ xmlInput: function (elem) { return; }, /* jshint unused:true */ /** Function: xmlOutput * User overrideable function that receives XML data sent to the * connection. * * The default function does nothing. User code can override this with * > Strophe.Connection.xmlOutput = function (elem) { * > (user code) * > }; * * Due to limitations of current Browsers' XML-Parsers the opening and closing * tag for WebSocket-Connoctions will be passed as selfclosing here. * * BOSH-Connections will have all stanzas wrapped in a tag. See * if you want to strip this tag. * * Parameters: * (XMLElement) elem - The XMLdata sent by the connection. */ /* jshint unused:false */ xmlOutput: function (elem) { return; }, /* jshint unused:true */ /** Function: rawInput * User overrideable function that receives raw data coming into the * connection. * * The default function does nothing. User code can override this with * > Strophe.Connection.rawInput = function (data) { * > (user code) * > }; * * Parameters: * (String) data - The data received by the connection. */ /* jshint unused:false */ rawInput: function (data) { return; }, /* jshint unused:true */ /** Function: rawOutput * User overrideable function that receives raw data sent to the * connection. * * The default function does nothing. User code can override this with * > Strophe.Connection.rawOutput = function (data) { * > (user code) * > }; * * Parameters: * (String) data - The data sent by the connection. */ /* jshint unused:false */ rawOutput: function (data) { return; }, /* jshint unused:true */ /** Function: nextValidRid * User overrideable function that receives the new valid rid. * * The default function does nothing. User code can override this with * > Strophe.Connection.nextValidRid = function (rid) { * > (user code) * > }; * * Parameters: * (Number) rid - The next valid rid */ /* jshint unused:false */ nextValidRid: function (rid) { return; }, /* jshint unused:true */ /** Function: send * Send a stanza. * * This function is called to push data onto the send queue to * go out over the wire. Whenever a request is sent to the BOSH * server, all pending data is sent and the queue is flushed. * * Parameters: * (XMLElement | * [XMLElement] | * Strophe.Builder) elem - The stanza to send. */ send: function (elem) { if (elem === null) { return ; } if (typeof(elem.sort) === "function") { for (var i = 0; i < elem.length; i++) { this._queueData(elem[i]); } } else if (typeof(elem.tree) === "function") { this._queueData(elem.tree()); } else { this._queueData(elem); } this._proto._send(); }, /** Function: flush * Immediately send any pending outgoing data. * * Normally send() queues outgoing data until the next idle period * (100ms), which optimizes network use in the common cases when * several send()s are called in succession. flush() can be used to * immediately send all pending data. */ flush: function () { // cancel the pending idle period and run the idle function // immediately clearTimeout(this._idleTimeout); this._onIdle(); }, /** Function: sendPresence * Helper function to send presence stanzas. The main benefit is for * sending presence stanzas for which you expect a responding presence * stanza with the same id (for example when leaving a chat room). * * Parameters: * (XMLElement) elem - The stanza to send. * (Function) callback - The callback function for a successful request. * (Function) errback - The callback function for a failed or timed * out request. On timeout, the stanza will be null. * (Integer) timeout - The time specified in milliseconds for a * timeout to occur. * * Returns: * The id used to send the presence. */ sendPresence: function(elem, callback, errback, timeout) { var timeoutHandler = null; var that = this; if (typeof(elem.tree) === "function") { elem = elem.tree(); } var id = elem.getAttribute('id'); if (!id) { // inject id if not found id = this.getUniqueId("sendPresence"); elem.setAttribute("id", id); } if (typeof callback === "function" || typeof errback === "function") { var handler = this.addHandler(function (stanza) { // remove timeout handler if there is one if (timeoutHandler) { that.deleteTimedHandler(timeoutHandler); } var type = stanza.getAttribute('type'); if (type === 'error') { if (errback) { errback(stanza); } } else if (callback) { callback(stanza); } }, null, 'presence', null, id); // if timeout specified, set up a timeout handler. if (timeout) { timeoutHandler = this.addTimedHandler(timeout, function () { // get rid of normal handler that.deleteHandler(handler); // call errback on timeout with null stanza if (errback) { errback(null); } return false; }); } } this.send(elem); return id; }, /** Function: sendIQ * Helper function to send IQ stanzas. * * Parameters: * (XMLElement) elem - The stanza to send. * (Function) callback - The callback function for a successful request. * (Function) errback - The callback function for a failed or timed * out request. On timeout, the stanza will be null. * (Integer) timeout - The time specified in milliseconds for a * timeout to occur. * * Returns: * The id used to send the IQ. */ sendIQ: function(elem, callback, errback, timeout) { var timeoutHandler = null; var that = this; if (typeof(elem.tree) === "function") { elem = elem.tree(); } var id = elem.getAttribute('id'); if (!id) { // inject id if not found id = this.getUniqueId("sendIQ"); elem.setAttribute("id", id); } if (typeof callback === "function" || typeof errback === "function") { var handler = this.addHandler(function (stanza) { // remove timeout handler if there is one if (timeoutHandler) { that.deleteTimedHandler(timeoutHandler); } var iqtype = stanza.getAttribute('type'); if (iqtype === 'result') { if (callback) { callback(stanza); } } else if (iqtype === 'error') { if (errback) { errback(stanza); } } else { throw { name: "StropheError", message: "Got bad IQ type of " + iqtype }; } }, null, 'iq', ['error', 'result'], id); // if timeout specified, set up a timeout handler. if (timeout) { timeoutHandler = this.addTimedHandler(timeout, function () { // get rid of normal handler that.deleteHandler(handler); // call errback on timeout with null stanza if (errback) { errback(null); } return false; }); } } this.send(elem); return id; }, /** PrivateFunction: _queueData * Queue outgoing data for later sending. Also ensures that the data * is a DOMElement. */ _queueData: function (element) { if (element === null || !element.tagName || !element.childNodes) { throw { name: "StropheError", message: "Cannot queue non-DOMElement." }; } this._data.push(element); }, /** PrivateFunction: _sendRestart * Send an xmpp:restart stanza. */ _sendRestart: function () { this._data.push("restart"); this._proto._sendRestart(); // XXX: setTimeout should be called only with function expressions (23974bc1) this._idleTimeout = setTimeout(function() { this._onIdle(); }.bind(this), 100); }, /** Function: addTimedHandler * Add a timed handler to the connection. * * This function adds a timed handler. The provided handler will * be called every period milliseconds until it returns false, * the connection is terminated, or the handler is removed. Handlers * that wish to continue being invoked should return true. * * Because of method binding it is necessary to save the result of * this function if you wish to remove a handler with * deleteTimedHandler(). * * Note that user handlers are not active until authentication is * successful. * * Parameters: * (Integer) period - The period of the handler. * (Function) handler - The callback function. * * Returns: * A reference to the handler that can be used to remove it. */ addTimedHandler: function (period, handler) { var thand = new Strophe.TimedHandler(period, handler); this.addTimeds.push(thand); return thand; }, /** Function: deleteTimedHandler * Delete a timed handler for a connection. * * This function removes a timed handler from the connection. The * handRef parameter is *not* the function passed to addTimedHandler(), * but is the reference returned from addTimedHandler(). * * Parameters: * (Strophe.TimedHandler) handRef - The handler reference. */ deleteTimedHandler: function (handRef) { // this must be done in the Idle loop so that we don't change // the handlers during iteration this.removeTimeds.push(handRef); }, /** Function: addHandler * Add a stanza handler for the connection. * * This function adds a stanza handler to the connection. The * handler callback will be called for any stanza that matches * the parameters. Note that if multiple parameters are supplied, * they must all match for the handler to be invoked. * * The handler will receive the stanza that triggered it as its argument. * *The handler should return true if it is to be invoked again; * returning false will remove the handler after it returns.* * * As a convenience, the ns parameters applies to the top level element * and also any of its immediate children. This is primarily to make * matching /iq/query elements easy. * * Options * ~~~~~~~ * With the options argument, you can specify boolean flags that affect how * matches are being done. * * Currently two flags exist: * * - matchBareFromJid: * When set to true, the from parameter and the * from attribute on the stanza will be matched as bare JIDs instead * of full JIDs. To use this, pass {matchBareFromJid: true} as the * value of options. The default value for matchBareFromJid is false. * * - ignoreNamespaceFragment: * When set to true, a fragment specified on the stanza's namespace * URL will be ignored when it's matched with the one configured for * the handler. * * This means that if you register like this: * > connection.addHandler( * > handler, * > 'http://jabber.org/protocol/muc', * > null, null, null, null, * > {'ignoreNamespaceFragment': true} * > ); * * Then a stanza with XML namespace of * 'http://jabber.org/protocol/muc#user' will also be matched. If * 'ignoreNamespaceFragment' is false, then only stanzas with * 'http://jabber.org/protocol/muc' will be matched. * * Deleting the handler * ~~~~~~~~~~~~~~~~~~~~ * The return value should be saved if you wish to remove the handler * with deleteHandler(). * * Parameters: * (Function) handler - The user callback. * (String) ns - The namespace to match. * (String) name - The stanza name to match. * (String|Array) type - The stanza type (or types if an array) to match. * (String) id - The stanza id attribute to match. * (String) from - The stanza from attribute to match. * (String) options - The handler options * * Returns: * A reference to the handler that can be used to remove it. */ addHandler: function (handler, ns, name, type, id, from, options) { var hand = new Strophe.Handler(handler, ns, name, type, id, from, options); this.addHandlers.push(hand); return hand; }, /** Function: deleteHandler * Delete a stanza handler for a connection. * * This function removes a stanza handler from the connection. The * handRef parameter is *not* the function passed to addHandler(), * but is the reference returned from addHandler(). * * Parameters: * (Strophe.Handler) handRef - The handler reference. */ deleteHandler: function (handRef) { // this must be done in the Idle loop so that we don't change // the handlers during iteration this.removeHandlers.push(handRef); // If a handler is being deleted while it is being added, // prevent it from getting added var i = this.addHandlers.indexOf(handRef); if (i >= 0) { this.addHandlers.splice(i, 1); } }, /** Function: registerSASLMechanisms * * Register the SASL mechanisms which will be supported by this instance of * Strophe.Connection (i.e. which this XMPP client will support). * * Parameters: * (Array) mechanisms - Array of objects with Strophe.SASLMechanism prototypes * */ registerSASLMechanisms: function (mechanisms) { this.mechanisms = {}; mechanisms = mechanisms || [ Strophe.SASLAnonymous, Strophe.SASLExternal, Strophe.SASLMD5, Strophe.SASLOAuthBearer, Strophe.SASLPlain, Strophe.SASLSHA1 ]; mechanisms.forEach(this.registerSASLMechanism.bind(this)); }, /** Function: registerSASLMechanism * * Register a single SASL mechanism, to be supported by this client. * * Parameters: * (Object) mechanism - Object with a Strophe.SASLMechanism prototype * */ registerSASLMechanism: function (mechanism) { this.mechanisms[mechanism.prototype.name] = mechanism; }, /** Function: disconnect * Start the graceful disconnection process. * * This function starts the disconnection process. This process starts * by sending unavailable presence and sending BOSH body of type * terminate. A timeout handler makes sure that disconnection happens * even if the BOSH server does not respond. * If the Connection object isn't connected, at least tries to abort all pending requests * so the connection object won't generate successful requests (which were already opened). * * The user supplied connection callback will be notified of the * progress as this process happens. * * Parameters: * (String) reason - The reason the disconnect is occuring. */ disconnect: function (reason) { this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason); Strophe.info("Disconnect was called because: " + reason); if (this.connected) { var pres = false; this.disconnecting = true; if (this.authenticated) { pres = $pres({ xmlns: Strophe.NS.CLIENT, type: 'unavailable' }); } // setup timeout handler this._disconnectTimeout = this._addSysTimedHandler( 3000, this._onDisconnectTimeout.bind(this)); this._proto._disconnect(pres); } else { Strophe.info("Disconnect was called before Strophe connected to the server"); this._proto._abortAllRequests(); this._doDisconnect(); } }, /** PrivateFunction: _changeConnectStatus * _Private_ helper function that makes sure plugins and the user's * callback are notified of connection status changes. * * Parameters: * (Integer) status - the new connection status, one of the values * in Strophe.Status * (String) condition - the error condition or null */ _changeConnectStatus: function (status, condition) { // notify all plugins listening for status changes for (var k in Strophe._connectionPlugins) { if (Strophe._connectionPlugins.hasOwnProperty(k)) { var plugin = this[k]; if (plugin.statusChanged) { try { plugin.statusChanged(status, condition); } catch (err) { Strophe.error("" + k + " plugin caused an exception " + "changing status: " + err); } } } } // notify the user's callback if (this.connect_callback) { try { this.connect_callback(status, condition); } catch (e) { Strophe._handleError(e); Strophe.error( "User connection callback caused an "+"exception: "+e); } } }, /** PrivateFunction: _doDisconnect * _Private_ function to disconnect. * * This is the last piece of the disconnection logic. This resets the * connection and alerts the user's connection callback. */ _doDisconnect: function (condition) { if (typeof this._idleTimeout === "number") { clearTimeout(this._idleTimeout); } // Cancel Disconnect Timeout if (this._disconnectTimeout !== null) { this.deleteTimedHandler(this._disconnectTimeout); this._disconnectTimeout = null; } Strophe.info("_doDisconnect was called"); this._proto._doDisconnect(); this.authenticated = false; this.disconnecting = false; this.restored = false; // delete handlers this.handlers = []; this.timedHandlers = []; this.removeTimeds = []; this.removeHandlers = []; this.addTimeds = []; this.addHandlers = []; // tell the parent we disconnected this._changeConnectStatus(Strophe.Status.DISCONNECTED, condition); this.connected = false; }, /** PrivateFunction: _dataRecv * _Private_ handler to processes incoming data from the the connection. * * Except for _connect_cb handling the initial connection request, * this function handles the incoming data for all requests. This * function also fires stanza handlers that match each incoming * stanza. * * Parameters: * (Strophe.Request) req - The request that has data ready. * (string) req - The stanza a raw string (optiona). */ _dataRecv: function (req, raw) { Strophe.info("_dataRecv called"); var elem = this._proto._reqToData(req); if (elem === null) { return; } if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) { if (elem.nodeName === this._proto.strip && elem.childNodes.length) { this.xmlInput(elem.childNodes[0]); } else { this.xmlInput(elem); } } if (this.rawInput !== Strophe.Connection.prototype.rawInput) { if (raw) { this.rawInput(raw); } else { this.rawInput(Strophe.serialize(elem)); } } // remove handlers scheduled for deletion var i, hand; while (this.removeHandlers.length > 0) { hand = this.removeHandlers.pop(); i = this.handlers.indexOf(hand); if (i >= 0) { this.handlers.splice(i, 1); } } // add handlers scheduled for addition while (this.addHandlers.length > 0) { this.handlers.push(this.addHandlers.pop()); } // handle graceful disconnect if (this.disconnecting && this._proto._emptyQueue()) { this._doDisconnect(); return; } var type = elem.getAttribute("type"); var cond, conflict; if (type !== null && type === "terminate") { // Don't process stanzas that come in after disconnect if (this.disconnecting) { return; } // an error occurred cond = elem.getAttribute("condition"); conflict = elem.getElementsByTagName("conflict"); if (cond !== null) { if (cond === "remote-stream-error" && conflict.length > 0) { cond = "conflict"; } this._changeConnectStatus(Strophe.Status.CONNFAIL, cond); } else { this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown"); } this._doDisconnect(cond); return; } // send each incoming stanza through the handler chain var that = this; Strophe.forEachChild(elem, null, function (child) { var i, newList; // process handlers newList = that.handlers; that.handlers = []; for (i = 0; i < newList.length; i++) { var hand = newList[i]; // encapsulate 'handler.run' not to lose the whole handler list if // one of the handlers throws an exception try { if (hand.isMatch(child) && (that.authenticated || !hand.user)) { if (hand.run(child)) { that.handlers.push(hand); } } else { that.handlers.push(hand); } } catch(e) { // if the handler throws an exception, we consider it as false Strophe.warn('Removing Strophe handlers due to uncaught exception: '+e.message); } } }); }, /** Attribute: mechanisms * SASL Mechanisms available for Connection. */ mechanisms: {}, /** PrivateFunction: _connect_cb * _Private_ handler for initial connection request. * * This handler is used to process the initial connection request * response from the BOSH server. It is used to set up authentication * handlers and start the authentication process. * * SASL authentication will be attempted if available, otherwise * the code will fall back to legacy authentication. * * Parameters: * (Strophe.Request) req - The current request. * (Function) _callback - low level (xmpp) connect callback function. * Useful for plugins with their own xmpp connect callback (when their) * want to do something special). */ _connect_cb: function (req, _callback, raw) { Strophe.info("_connect_cb was called"); this.connected = true; var bodyWrap; try { bodyWrap = this._proto._reqToData(req); } catch (e) { if (e !== "badformat") { throw e; } this._changeConnectStatus(Strophe.Status.CONNFAIL, 'bad-format'); this._doDisconnect('bad-format'); } if (!bodyWrap) { return; } if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) { if (bodyWrap.nodeName === this._proto.strip && bodyWrap.childNodes.length) { this.xmlInput(bodyWrap.childNodes[0]); } else { this.xmlInput(bodyWrap); } } if (this.rawInput !== Strophe.Connection.prototype.rawInput) { if (raw) { this.rawInput(raw); } else { this.rawInput(Strophe.serialize(bodyWrap)); } } var conncheck = this._proto._connect_cb(bodyWrap); if (conncheck === Strophe.Status.CONNFAIL) { return; } // Check for the stream:features tag var hasFeatures; if (bodyWrap.getElementsByTagNameNS) { hasFeatures = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, "features").length > 0; } else { hasFeatures = bodyWrap.getElementsByTagName("stream:features").length > 0 || bodyWrap.getElementsByTagName("features").length > 0; } if (!hasFeatures) { this._proto._no_auth_received(_callback); return; } var matched = [], i, mech; var mechanisms = bodyWrap.getElementsByTagName("mechanism"); if (mechanisms.length > 0) { for (i = 0; i < mechanisms.length; i++) { mech = Strophe.getText(mechanisms[i]); if (this.mechanisms[mech]) matched.push(this.mechanisms[mech]); } } if (matched.length === 0) { if (bodyWrap.getElementsByTagName("auth").length === 0) { // There are no matching SASL mechanisms and also no legacy // auth available. this._proto._no_auth_received(_callback); return; } } if (this.do_authentication !== false) { this.authenticate(matched); } }, /** Function: sortMechanismsByPriority * * Sorts an array of objects with prototype SASLMechanism according to * their priorities. * * Parameters: * (Array) mechanisms - Array of SASL mechanisms. * */ sortMechanismsByPriority: function (mechanisms) { // Sorting mechanisms according to priority. var i, j, higher, swap; for (i = 0; i < mechanisms.length - 1; ++i) { higher = i; for (j = i + 1; j < mechanisms.length; ++j) { if (mechanisms[j].prototype.priority > mechanisms[higher].prototype.priority) { higher = j; } } if (higher !== i) { swap = mechanisms[i]; mechanisms[i] = mechanisms[higher]; mechanisms[higher] = swap; } } return mechanisms; }, /** PrivateFunction: _attemptSASLAuth * * Iterate through an array of SASL mechanisms and attempt authentication * with the highest priority (enabled) mechanism. * * Parameters: * (Array) mechanisms - Array of SASL mechanisms. * * Returns: * (Boolean) mechanism_found - true or false, depending on whether a * valid SASL mechanism was found with which authentication could be * started. */ _attemptSASLAuth: function (mechanisms) { mechanisms = this.sortMechanismsByPriority(mechanisms || []); var i = 0, mechanism_found = false; for (i = 0; i < mechanisms.length; ++i) { if (!mechanisms[i].prototype.test(this)) { continue; } this._sasl_success_handler = this._addSysHandler( this._sasl_success_cb.bind(this), null, "success", null, null); this._sasl_failure_handler = this._addSysHandler( this._sasl_failure_cb.bind(this), null, "failure", null, null); this._sasl_challenge_handler = this._addSysHandler( this._sasl_challenge_cb.bind(this), null, "challenge", null, null); this._sasl_mechanism = new mechanisms[i](); this._sasl_mechanism.onStart(this); var request_auth_exchange = $build("auth", { xmlns: Strophe.NS.SASL, mechanism: this._sasl_mechanism.name }); if (this._sasl_mechanism.isClientFirst) { var response = this._sasl_mechanism.onChallenge(this, null); request_auth_exchange.t(btoa(response)); } this.send(request_auth_exchange.tree()); mechanism_found = true; break; } return mechanism_found; }, /** PrivateFunction: _attemptLegacyAuth * * Attempt legacy (i.e. non-SASL) authentication. * */ _attemptLegacyAuth: function () { if (Strophe.getNodeFromJid(this.jid) === null) { // we don't have a node, which is required for non-anonymous // client connections this._changeConnectStatus( Strophe.Status.CONNFAIL, 'x-strophe-bad-non-anon-jid' ); this.disconnect('x-strophe-bad-non-anon-jid'); } else { // Fall back to legacy authentication this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null); this._addSysHandler( this._auth1_cb.bind(this), null, null, null, "_auth_1" ); this.send($iq({ 'type': "get", 'to': this.domain, 'id': "_auth_1" }).c("query", {xmlns: Strophe.NS.AUTH}) .c("username", {}).t(Strophe.getNodeFromJid(this.jid)) .tree()); } }, /** Function: authenticate * Set up authentication * * Continues the initial connection request by setting up authentication * handlers and starting the authentication process. * * SASL authentication will be attempted if available, otherwise * the code will fall back to legacy authentication. * * Parameters: * (Array) matched - Array of SASL mechanisms supported. * */ authenticate: function (matched) { if (!this._attemptSASLAuth(matched)) { this._attemptLegacyAuth(); } }, /** PrivateFunction: _sasl_challenge_cb * _Private_ handler for the SASL challenge * */ _sasl_challenge_cb: function(elem) { var challenge = atob(Strophe.getText(elem)); var response = this._sasl_mechanism.onChallenge(this, challenge); var stanza = $build('response', { 'xmlns': Strophe.NS.SASL }); if (response !== "") { stanza.t(btoa(response)); } this.send(stanza.tree()); return true; }, /** PrivateFunction: _auth1_cb * _Private_ handler for legacy authentication. * * This handler is called in response to the initial * for legacy authentication. It builds an authentication and * sends it, creating a handler (calling back to _auth2_cb()) to * handle the result * * Parameters: * (XMLElement) elem - The stanza that triggered the callback. * * Returns: * false to remove the handler. */ /* jshint unused:false */ _auth1_cb: function (elem) { // build plaintext auth iq var iq = $iq({type: "set", id: "_auth_2"}) .c('query', {xmlns: Strophe.NS.AUTH}) .c('username', {}).t(Strophe.getNodeFromJid(this.jid)) .up() .c('password').t(this.pass); if (!Strophe.getResourceFromJid(this.jid)) { // since the user has not supplied a resource, we pick // a default one here. unlike other auth methods, the server // cannot do this for us. this.jid = Strophe.getBareJidFromJid(this.jid) + '/strophe'; } iq.up().c('resource', {}).t(Strophe.getResourceFromJid(this.jid)); this._addSysHandler(this._auth2_cb.bind(this), null, null, null, "_auth_2"); this.send(iq.tree()); return false; }, /* jshint unused:true */ /** PrivateFunction: _sasl_success_cb * _Private_ handler for succesful SASL authentication. * * Parameters: * (XMLElement) elem - The matching stanza. * * Returns: * false to remove the handler. */ _sasl_success_cb: function (elem) { if (this._sasl_data["server-signature"]) { var serverSignature; var success = atob(Strophe.getText(elem)); var attribMatch = /([a-z]+)=([^,]+)(,|$)/; var matches = success.match(attribMatch); if (matches[1] === "v") { serverSignature = matches[2]; } if (serverSignature !== this._sasl_data["server-signature"]) { // remove old handlers this.deleteHandler(this._sasl_failure_handler); this._sasl_failure_handler = null; if (this._sasl_challenge_handler) { this.deleteHandler(this._sasl_challenge_handler); this._sasl_challenge_handler = null; } this._sasl_data = {}; return this._sasl_failure_cb(null); } } Strophe.info("SASL authentication succeeded."); if (this._sasl_mechanism) { this._sasl_mechanism.onSuccess(); } // remove old handlers this.deleteHandler(this._sasl_failure_handler); this._sasl_failure_handler = null; if (this._sasl_challenge_handler) { this.deleteHandler(this._sasl_challenge_handler); this._sasl_challenge_handler = null; } var streamfeature_handlers = []; var wrapper = function(handlers, elem) { while (handlers.length) { this.deleteHandler(handlers.pop()); } this._sasl_auth1_cb.bind(this)(elem); return false; }; streamfeature_handlers.push(this._addSysHandler(function(elem) { wrapper.bind(this)(streamfeature_handlers, elem); }.bind(this), null, "stream:features", null, null)); streamfeature_handlers.push(this._addSysHandler(function(elem) { wrapper.bind(this)(streamfeature_handlers, elem); }.bind(this), Strophe.NS.STREAM, "features", null, null)); // we must send an xmpp:restart now this._sendRestart(); return false; }, /** PrivateFunction: _sasl_auth1_cb * _Private_ handler to start stream binding. * * Parameters: * (XMLElement) elem - The matching stanza. * * Returns: * false to remove the handler. */ _sasl_auth1_cb: function (elem) { // save stream:features for future usage this.features = elem; var i, child; for (i = 0; i < elem.childNodes.length; i++) { child = elem.childNodes[i]; if (child.nodeName === 'bind') { this.do_bind = true; } if (child.nodeName === 'session') { this.do_session = true; } } if (!this.do_bind) { this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); return false; } else { this._addSysHandler(this._sasl_bind_cb.bind(this), null, null, null, "_bind_auth_2"); var resource = Strophe.getResourceFromJid(this.jid); if (resource) { this.send($iq({type: "set", id: "_bind_auth_2"}) .c('bind', {xmlns: Strophe.NS.BIND}) .c('resource', {}).t(resource).tree()); } else { this.send($iq({type: "set", id: "_bind_auth_2"}) .c('bind', {xmlns: Strophe.NS.BIND}) .tree()); } } return false; }, /** PrivateFunction: _sasl_bind_cb * _Private_ handler for binding result and session start. * * Parameters: * (XMLElement) elem - The matching stanza. * * Returns: * false to remove the handler. */ _sasl_bind_cb: function (elem) { if (elem.getAttribute("type") === "error") { Strophe.info("SASL binding failed."); var conflict = elem.getElementsByTagName("conflict"), condition; if (conflict.length > 0) { condition = 'conflict'; } this._changeConnectStatus(Strophe.Status.AUTHFAIL, condition); return false; } // TODO - need to grab errors var bind = elem.getElementsByTagName("bind"); var jidNode; if (bind.length > 0) { // Grab jid jidNode = bind[0].getElementsByTagName("jid"); if (jidNode.length > 0) { this.jid = Strophe.getText(jidNode[0]); if (this.do_session) { this._addSysHandler(this._sasl_session_cb.bind(this), null, null, null, "_session_auth_2"); this.send($iq({type: "set", id: "_session_auth_2"}) .c('session', {xmlns: Strophe.NS.SESSION}) .tree()); } else { this.authenticated = true; this._changeConnectStatus(Strophe.Status.CONNECTED, null); } } } else { Strophe.info("SASL binding failed."); this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); return false; } }, /** PrivateFunction: _sasl_session_cb * _Private_ handler to finish successful SASL connection. * * This sets Connection.authenticated to true on success, which * starts the processing of user handlers. * * Parameters: * (XMLElement) elem - The matching stanza. * * Returns: * false to remove the handler. */ _sasl_session_cb: function (elem) { if (elem.getAttribute("type") === "result") { this.authenticated = true; this._changeConnectStatus(Strophe.Status.CONNECTED, null); } else if (elem.getAttribute("type") === "error") { Strophe.info("Session creation failed."); this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); return false; } return false; }, /** PrivateFunction: _sasl_failure_cb * _Private_ handler for SASL authentication failure. * * Parameters: * (XMLElement) elem - The matching stanza. * * Returns: * false to remove the handler. */ /* jshint unused:false */ _sasl_failure_cb: function (elem) { // delete unneeded handlers if (this._sasl_success_handler) { this.deleteHandler(this._sasl_success_handler); this._sasl_success_handler = null; } if (this._sasl_challenge_handler) { this.deleteHandler(this._sasl_challenge_handler); this._sasl_challenge_handler = null; } if(this._sasl_mechanism) this._sasl_mechanism.onFailure(); this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); return false; }, /* jshint unused:true */ /** PrivateFunction: _auth2_cb * _Private_ handler to finish legacy authentication. * * This handler is called when the result from the jabber:iq:auth * stanza is returned. * * Parameters: * (XMLElement) elem - The stanza that triggered the callback. * * Returns: * false to remove the handler. */ _auth2_cb: function (elem) { if (elem.getAttribute("type") === "result") { this.authenticated = true; this._changeConnectStatus(Strophe.Status.CONNECTED, null); } else if (elem.getAttribute("type") === "error") { this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); this.disconnect('authentication failed'); } return false; }, /** PrivateFunction: _addSysTimedHandler * _Private_ function to add a system level timed handler. * * This function is used to add a Strophe.TimedHandler for the * library code. System timed handlers are allowed to run before * authentication is complete. * * Parameters: * (Integer) period - The period of the handler. * (Function) handler - The callback function. */ _addSysTimedHandler: function (period, handler) { var thand = new Strophe.TimedHandler(period, handler); thand.user = false; this.addTimeds.push(thand); return thand; }, /** PrivateFunction: _addSysHandler * _Private_ function to add a system level stanza handler. * * This function is used to add a Strophe.Handler for the * library code. System stanza handlers are allowed to run before * authentication is complete. * * Parameters: * (Function) handler - The callback function. * (String) ns - The namespace to match. * (String) name - The stanza name to match. * (String) type - The stanza type attribute to match. * (String) id - The stanza id attribute to match. */ _addSysHandler: function (handler, ns, name, type, id) { var hand = new Strophe.Handler(handler, ns, name, type, id); hand.user = false; this.addHandlers.push(hand); return hand; }, /** PrivateFunction: _onDisconnectTimeout * _Private_ timeout handler for handling non-graceful disconnection. * * If the graceful disconnect process does not complete within the * time allotted, this handler finishes the disconnect anyway. * * Returns: * false to remove the handler. */ _onDisconnectTimeout: function () { Strophe.info("_onDisconnectTimeout was called"); this._changeConnectStatus(Strophe.Status.CONNTIMEOUT, null); this._proto._onDisconnectTimeout(); // actually disconnect this._doDisconnect(); return false; }, /** PrivateFunction: _onIdle * _Private_ handler to process events during idle cycle. * * This handler is called every 100ms to fire timed handlers that * are ready and keep poll requests going. */ _onIdle: function () { var i, thand, since, newList; // add timed handlers scheduled for addition // NOTE: we add before remove in the case a timed handler is // added and then deleted before the next _onIdle() call. while (this.addTimeds.length > 0) { this.timedHandlers.push(this.addTimeds.pop()); } // remove timed handlers that have been scheduled for deletion while (this.removeTimeds.length > 0) { thand = this.removeTimeds.pop(); i = this.timedHandlers.indexOf(thand); if (i >= 0) { this.timedHandlers.splice(i, 1); } } // call ready timed handlers var now = new Date().getTime(); newList = []; for (i = 0; i < this.timedHandlers.length; i++) { thand = this.timedHandlers[i]; if (this.authenticated || !thand.user) { since = thand.lastCalled + thand.period; if (since - now <= 0) { if (thand.run()) { newList.push(thand); } } else { newList.push(thand); } } } this.timedHandlers = newList; clearTimeout(this._idleTimeout); this._proto._onIdle(); // reactivate the timer only if connected if (this.connected) { // XXX: setTimeout should be called only with function expressions (23974bc1) this._idleTimeout = setTimeout(function() { this._onIdle(); }.bind(this), 100); } } }; /** Class: Strophe.SASLMechanism * * encapsulates SASL authentication mechanisms. * * User code may override the priority for each mechanism or disable it completely. * See for information about changing priority and for informatian on * how to disable a mechanism. * * By default, all mechanisms are enabled and the priorities are * * OAUTHBEARER - 60 * SCRAM-SHA1 - 50 * DIGEST-MD5 - 40 * PLAIN - 30 * ANONYMOUS - 20 * EXTERNAL - 10 * * See: Strophe.Connection.addSupportedSASLMechanisms */ /** * PrivateConstructor: Strophe.SASLMechanism * SASL auth mechanism abstraction. * * Parameters: * (String) name - SASL Mechanism name. * (Boolean) isClientFirst - If client should send response first without challenge. * (Number) priority - Priority. * * Returns: * A new Strophe.SASLMechanism object. */ Strophe.SASLMechanism = function(name, isClientFirst, priority) { /** PrivateVariable: name * Mechanism name. */ this.name = name; /** PrivateVariable: isClientFirst * If client sends response without initial server challenge. */ this.isClientFirst = isClientFirst; /** Variable: priority * Determines which is chosen for authentication (Higher is better). * Users may override this to prioritize mechanisms differently. * * In the default configuration the priorities are * * SCRAM-SHA1 - 40 * DIGEST-MD5 - 30 * Plain - 20 * * Example: (This will cause Strophe to choose the mechanism that the server sent first) * * > Strophe.SASLMD5.priority = Strophe.SASLSHA1.priority; * * See for a list of available mechanisms. * */ this.priority = priority; }; Strophe.SASLMechanism.prototype = { /** * Function: test * Checks if mechanism able to run. * To disable a mechanism, make this return false; * * To disable plain authentication run * > Strophe.SASLPlain.test = function() { * > return false; * > } * * See for a list of available mechanisms. * * Parameters: * (Strophe.Connection) connection - Target Connection. * * Returns: * (Boolean) If mechanism was able to run. */ /* jshint unused:false */ test: function(connection) { return true; }, /* jshint unused:true */ /** PrivateFunction: onStart * Called before starting mechanism on some connection. * * Parameters: * (Strophe.Connection) connection - Target Connection. */ onStart: function(connection) { this._connection = connection; }, /** PrivateFunction: onChallenge * Called by protocol implementation on incoming challenge. If client is * first (isClientFirst === true) challenge will be null on the first call. * * Parameters: * (Strophe.Connection) connection - Target Connection. * (String) challenge - current challenge to handle. * * Returns: * (String) Mechanism response. */ /* jshint unused:false */ onChallenge: function(connection, challenge) { throw new Error("You should implement challenge handling!"); }, /* jshint unused:true */ /** PrivateFunction: onFailure * Protocol informs mechanism implementation about SASL failure. */ onFailure: function() { this._connection = null; }, /** PrivateFunction: onSuccess * Protocol informs mechanism implementation about SASL success. */ onSuccess: function() { this._connection = null; } }; /** Constants: SASL mechanisms * Available authentication mechanisms * * Strophe.SASLAnonymous - SASL ANONYMOUS authentication. * Strophe.SASLPlain - SASL PLAIN authentication. * Strophe.SASLMD5 - SASL DIGEST-MD5 authentication * Strophe.SASLSHA1 - SASL SCRAM-SHA1 authentication * Strophe.SASLOAuthBearer - SASL OAuth Bearer authentication * Strophe.SASLExternal - SASL EXTERNAL authentication */ // Building SASL callbacks /** PrivateConstructor: SASLAnonymous * SASL ANONYMOUS authentication. */ Strophe.SASLAnonymous = function() {}; Strophe.SASLAnonymous.prototype = new Strophe.SASLMechanism("ANONYMOUS", false, 20); Strophe.SASLAnonymous.prototype.test = function(connection) { return connection.authcid === null; }; /** PrivateConstructor: SASLPlain * SASL PLAIN authentication. */ Strophe.SASLPlain = function() {}; Strophe.SASLPlain.prototype = new Strophe.SASLMechanism("PLAIN", true, 30); Strophe.SASLPlain.prototype.test = function(connection) { return connection.authcid !== null; }; Strophe.SASLPlain.prototype.onChallenge = function(connection) { var auth_str = connection.authzid; auth_str = auth_str + "\u0000"; auth_str = auth_str + connection.authcid; auth_str = auth_str + "\u0000"; auth_str = auth_str + connection.pass; return utils.utf16to8(auth_str); }; /** PrivateConstructor: SASLSHA1 * SASL SCRAM SHA 1 authentication. */ Strophe.SASLSHA1 = function() {}; Strophe.SASLSHA1.prototype = new Strophe.SASLMechanism("SCRAM-SHA-1", true, 50); Strophe.SASLSHA1.prototype.test = function(connection) { return connection.authcid !== null; }; Strophe.SASLSHA1.prototype.onChallenge = function(connection, challenge, test_cnonce) { var cnonce = test_cnonce || MD5.hexdigest(Math.random() * 1234567890); var auth_str = "n=" + utils.utf16to8(connection.authcid); auth_str += ",r="; auth_str += cnonce; connection._sasl_data.cnonce = cnonce; connection._sasl_data["client-first-message-bare"] = auth_str; auth_str = "n,," + auth_str; this.onChallenge = function (connection, challenge) { var nonce, salt, iter, Hi, U, U_old, i, k, pass; var clientKey, serverKey, clientSignature; var responseText = "c=biws,"; var authMessage = connection._sasl_data["client-first-message-bare"] + "," + challenge + ","; var cnonce = connection._sasl_data.cnonce; var attribMatch = /([a-z]+)=([^,]+)(,|$)/; while (challenge.match(attribMatch)) { var matches = challenge.match(attribMatch); challenge = challenge.replace(matches[0], ""); switch (matches[1]) { case "r": nonce = matches[2]; break; case "s": salt = matches[2]; break; case "i": iter = matches[2]; break; } } if (nonce.substr(0, cnonce.length) !== cnonce) { connection._sasl_data = {}; return connection._sasl_failure_cb(); } responseText += "r=" + nonce; authMessage += responseText; salt = atob(salt); salt += "\x00\x00\x00\x01"; pass = utils.utf16to8(connection.pass); Hi = U_old = SHA1.core_hmac_sha1(pass, salt); for (i = 1; i < iter; i++) { U = SHA1.core_hmac_sha1(pass, SHA1.binb2str(U_old)); for (k = 0; k < 5; k++) { Hi[k] ^= U[k]; } U_old = U; } Hi = SHA1.binb2str(Hi); clientKey = SHA1.core_hmac_sha1(Hi, "Client Key"); serverKey = SHA1.str_hmac_sha1(Hi, "Server Key"); clientSignature = SHA1.core_hmac_sha1(SHA1.str_sha1(SHA1.binb2str(clientKey)), authMessage); connection._sasl_data["server-signature"] = SHA1.b64_hmac_sha1(serverKey, authMessage); for (k = 0; k < 5; k++) { clientKey[k] ^= clientSignature[k]; } responseText += ",p=" + btoa(SHA1.binb2str(clientKey)); return responseText; }.bind(this); return auth_str; }; /** PrivateConstructor: SASLMD5 * SASL DIGEST MD5 authentication. */ Strophe.SASLMD5 = function() {}; Strophe.SASLMD5.prototype = new Strophe.SASLMechanism("DIGEST-MD5", false, 40); Strophe.SASLMD5.prototype.test = function(connection) { return connection.authcid !== null; }; /** PrivateFunction: _quote * _Private_ utility function to backslash escape and quote strings. * * Parameters: * (String) str - The string to be quoted. * * Returns: * quoted string */ Strophe.SASLMD5.prototype._quote = function (str) { return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; //" end string workaround for emacs }; Strophe.SASLMD5.prototype.onChallenge = function(connection, challenge, test_cnonce) { var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/; var cnonce = test_cnonce || MD5.hexdigest("" + (Math.random() * 1234567890)); var realm = ""; var host = null; var nonce = ""; var qop = ""; var matches; while (challenge.match(attribMatch)) { matches = challenge.match(attribMatch); challenge = challenge.replace(matches[0], ""); matches[2] = matches[2].replace(/^"(.+)"$/, "$1"); switch (matches[1]) { case "realm": realm = matches[2]; break; case "nonce": nonce = matches[2]; break; case "qop": qop = matches[2]; break; case "host": host = matches[2]; break; } } var digest_uri = connection.servtype + "/" + connection.domain; if (host !== null) { digest_uri = digest_uri + "/" + host; } var cred = utils.utf16to8(connection.authcid + ":" + realm + ":" + this._connection.pass); var A1 = MD5.hash(cred) + ":" + nonce + ":" + cnonce; var A2 = 'AUTHENTICATE:' + digest_uri; var responseText = ""; responseText += 'charset=utf-8,'; responseText += 'username=' + this._quote(utils.utf16to8(connection.authcid)) + ','; responseText += 'realm=' + this._quote(realm) + ','; responseText += 'nonce=' + this._quote(nonce) + ','; responseText += 'nc=00000001,'; responseText += 'cnonce=' + this._quote(cnonce) + ','; responseText += 'digest-uri=' + this._quote(digest_uri) + ','; responseText += 'response=' + MD5.hexdigest(MD5.hexdigest(A1) + ":" + nonce + ":00000001:" + cnonce + ":auth:" + MD5.hexdigest(A2)) + ","; responseText += 'qop=auth'; this.onChallenge = function () { return ""; }; return responseText; }; /** PrivateConstructor: SASLOAuthBearer * SASL OAuth Bearer authentication. */ Strophe.SASLOAuthBearer = function() {}; Strophe.SASLOAuthBearer.prototype = new Strophe.SASLMechanism("OAUTHBEARER", true, 60); Strophe.SASLOAuthBearer.prototype.test = function(connection) { return connection.pass !== null; }; Strophe.SASLOAuthBearer.prototype.onChallenge = function(connection) { var auth_str = 'n,'; if (connection.authcid !== null) { auth_str = auth_str + 'a=' + connection.authzid; } auth_str = auth_str + ','; auth_str = auth_str + "\u0001"; auth_str = auth_str + 'auth=Bearer '; auth_str = auth_str + connection.pass; auth_str = auth_str + "\u0001"; auth_str = auth_str + "\u0001"; return utils.utf16to8(auth_str); }; /** PrivateConstructor: SASLExternal * SASL EXTERNAL authentication. * * The EXTERNAL mechanism allows a client to request the server to use * credentials established by means external to the mechanism to * authenticate the client. The external means may be, for instance, * TLS services. */ Strophe.SASLExternal = function() {}; Strophe.SASLExternal.prototype = new Strophe.SASLMechanism("EXTERNAL", true, 10); Strophe.SASLExternal.prototype.onChallenge = function(connection) { /** According to XEP-178, an authzid SHOULD NOT be presented when the * authcid contained or implied in the client certificate is the JID (i.e. * authzid) with which the user wants to log in as. * * To NOT send the authzid, the user should therefore set the authcid equal * to the JID when instantiating a new Strophe.Connection object. */ return connection.authcid === connection.authzid ? '' : connection.authzid; }; return { 'Strophe': Strophe, '$build': $build, '$iq': $iq, '$msg': $msg, '$pres': $pres, 'SHA1': SHA1, 'MD5': MD5, 'b64_hmac_sha1': SHA1.b64_hmac_sha1, 'b64_sha1': SHA1.b64_sha1, 'str_hmac_sha1': SHA1.str_hmac_sha1, 'str_sha1': SHA1.str_sha1 }; })); strophejs-1.2.14+dfsg/src/end.frag000066400000000000000000000004751320017573300167440ustar00rootroot00000000000000/* jshint ignore:start */ //The modules for your project will be inlined above //this snippet. Ask almond to synchronously require the //module value for 'main' here and return it as the //value to use for the public API for the built file. return require('strophe'); })); /* jshint ignore:end */ strophejs-1.2.14+dfsg/src/md5.js000066400000000000000000000174751320017573300163700ustar00rootroot00000000000000/* * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message * Digest Algorithm, as defined in RFC 1321. * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet * Distributed under the BSD License * See http://pajhome.org.uk/crypt/md5 for more info. */ /* * Everything that isn't used by Strophe has been stripped here! */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define([], function () { return factory(); }); } else { // Browser globals root.MD5 = factory(); } }(this, function () { /* * Add integers, wrapping at 2^32. This uses 16-bit operations internally * to work around bugs in some JS interpreters. */ var safe_add = function (x, y) { var lsw = (x & 0xFFFF) + (y & 0xFFFF); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); }; /* * Bitwise rotate a 32-bit number to the left. */ var bit_rol = function (num, cnt) { return (num << cnt) | (num >>> (32 - cnt)); }; /* * Convert a string to an array of little-endian words */ var str2binl = function (str) { var bin = []; for(var i = 0; i < str.length * 8; i += 8) { bin[i>>5] |= (str.charCodeAt(i / 8) & 255) << (i%32); } return bin; }; /* * Convert an array of little-endian words to a string */ var binl2str = function (bin) { var str = ""; for(var i = 0; i < bin.length * 32; i += 8) { str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & 255); } return str; }; /* * Convert an array of little-endian words to a hex string. */ var binl2hex = function (binarray) { var hex_tab = "0123456789abcdef"; var str = ""; for(var i = 0; i < binarray.length * 4; i++) { str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); } return str; }; /* * These functions implement the four basic operations the algorithm uses. */ var md5_cmn = function (q, a, b, x, s, t) { return safe_add(bit_rol(safe_add(safe_add(a, q),safe_add(x, t)), s),b); }; var md5_ff = function (a, b, c, d, x, s, t) { return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); }; var md5_gg = function (a, b, c, d, x, s, t) { return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); }; var md5_hh = function (a, b, c, d, x, s, t) { return md5_cmn(b ^ c ^ d, a, b, x, s, t); }; var md5_ii = function (a, b, c, d, x, s, t) { return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); }; /* * Calculate the MD5 of an array of little-endian words, and a bit length */ var core_md5 = function (x, len) { /* append padding */ x[len >> 5] |= 0x80 << ((len) % 32); x[(((len + 64) >>> 9) << 4) + 14] = len; var a = 1732584193; var b = -271733879; var c = -1732584194; var d = 271733878; var olda, oldb, oldc, oldd; for (var i = 0; i < x.length; i += 16) { olda = a; oldb = b; oldc = c; oldd = d; a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); c = md5_ff(c, d, a, b, x[i+10], 17, -42063); b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); a = safe_add(a, olda); b = safe_add(b, oldb); c = safe_add(c, oldc); d = safe_add(d, oldd); } return [a, b, c, d]; }; var obj = { /* * These are the functions you'll usually want to call. * They take string arguments and return either hex or base-64 encoded * strings. */ hexdigest: function (s) { return binl2hex(core_md5(str2binl(s), s.length * 8)); }, hash: function (s) { return binl2str(core_md5(str2binl(s), s.length * 8)); } }; return obj; })); strophejs-1.2.14+dfsg/src/polyfills.js000066400000000000000000000167201320017573300177100ustar00rootroot00000000000000/* This program is distributed under the terms of the MIT license. Please see the LICENSE file for details. Copyright 2006-2008, OGG, LLC */ /* jshint undef: true, unused: true:, noarg: true, latedef: true */ /* global define */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define([], function () { return factory(root); }); } else { // Browser globals return factory(root); } }(this, function (root) { /** Function: Function.prototype.bind * Bind a function to an instance. * * This Function object extension method creates a bound method similar * to those in Python. This means that the 'this' object will point * to the instance you want. See * and * for a complete explanation. * * This extension already exists in some browsers (namely, Firefox 3), but * we provide it to support those that don't. * * Parameters: * (Object) obj - The object that will become 'this' in the bound function. * (Object) argN - An option argument that will be prepended to the * arguments given for the function call * * Returns: * The bound function. */ if (!Function.prototype.bind) { Function.prototype.bind = function (obj /*, arg1, arg2, ... */) { var func = this; var _slice = Array.prototype.slice; var _concat = Array.prototype.concat; var _args = _slice.call(arguments, 1); return function () { return func.apply(obj ? obj : this, _concat.call(_args, _slice.call(arguments, 0))); }; }; } /** Function: Array.isArray * This is a polyfill for the ES5 Array.isArray method. */ if (!Array.isArray) { Array.isArray = function(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; }; } /** Function: Array.prototype.indexOf * Return the index of an object in an array. * * This function is not supplied by some JavaScript implementations, so * we provide it if it is missing. This code is from: * http://developer.mozilla.org/En/Core_JavaScript_1.5_Reference:Objects:Array:indexOf * * Parameters: * (Object) elt - The object to look for. * (Integer) from - The index from which to start looking. (optional). * * Returns: * The index of elt in the array or -1 if not found. */ if (!Array.prototype.indexOf) { Array.prototype.indexOf = function(elt /*, from*/) { var len = this.length; var from = Number(arguments[1]) || 0; from = (from < 0) ? Math.ceil(from) : Math.floor(from); if (from < 0) { from += len; } for (; from < len; from++) { if (from in this && this[from] === elt) { return from; } } return -1; }; } /** Function: Array.prototype.forEach * * This function is not available in IE < 9 * * See */ if (!Array.prototype.forEach) { Array.prototype.forEach = function(callback, thisArg) { var T, k; if (this === null) { throw new TypeError(' this is null or not defined'); } // 1. Let O be the result of calling toObject() passing the // |this| value as the argument. var O = Object(this); // 2. Let lenValue be the result of calling the Get() internal // method of O with the argument "length". // 3. Let len be toUint32(lenValue). var len = O.length >>> 0; // 4. If isCallable(callback) is false, throw a TypeError exception. // See: http://es5.github.com/#x9.11 if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function'); } // 5. If thisArg was supplied, let T be thisArg; else let // T be undefined. if (arguments.length > 1) { T = thisArg; } // 6. Let k be 0 k = 0; // 7. Repeat, while k < len while (k < len) { var kValue; // a. Let Pk be ToString(k). // This is implicit for LHS operands of the in operator // b. Let kPresent be the result of calling the HasProperty // internal method of O with argument Pk. // This step can be combined with c // c. If kPresent is true, then if (k in O) { // i. Let kValue be the result of calling the Get internal // method of O with argument Pk. kValue = O[k]; // ii. Call the Call internal method of callback with T as // the this value and argument list containing kValue, k, and O. callback.call(T, kValue, k, O); } // d. Increase k by 1. k++; } // 8. return undefined }; } // This code was written by Tyler Akins and has been placed in the // public domain. It would be nice if you left this header intact. // Base64 code from Tyler Akins -- http://rumkin.com var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; if (!root.btoa) { root.btoa = function (input) { /** * Encodes a string in base64 * @param {String} input The string to encode in base64. */ var output = ""; var chr1, chr2, chr3; var enc1, enc2, enc3, enc4; var i = 0; do { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc2 = ((chr1 & 3) << 4); enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4); } while (i < input.length); return output; }; } if (!root.atob) { root.atob = function (input) { /** * Decodes a base64 string. * @param {String} input The string to decode. */ var output = ""; var chr1, chr2, chr3; var enc1, enc2, enc3, enc4; var i = 0; // remove all characters that are not A-Z, a-z, 0-9, +, /, or = input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); do { enc1 = keyStr.indexOf(input.charAt(i++)); enc2 = keyStr.indexOf(input.charAt(i++)); enc3 = keyStr.indexOf(input.charAt(i++)); enc4 = keyStr.indexOf(input.charAt(i++)); chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; output = output + String.fromCharCode(chr1); if (enc3 !== 64) { output = output + String.fromCharCode(chr2); } if (enc4 !== 64) { output = output + String.fromCharCode(chr3); } } while (i < input.length); return output; }; } })); strophejs-1.2.14+dfsg/src/sha1.js000066400000000000000000000116431320017573300165260ustar00rootroot00000000000000/* * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined * in FIPS PUB 180-1 * Version 2.1a Copyright Paul Johnston 2000 - 2002. * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet * Distributed under the BSD License * See http://pajhome.org.uk/crypt/md5 for details. */ /* jshint undef: true, unused: true:, noarg: true, latedef: false */ /* global define */ /* Some functions and variables have been stripped for use with Strophe */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define('strophe-sha1', function () { return factory(); }); } else { // Browser globals root.SHA1 = factory(); } }(this, function () { /* * Calculate the SHA-1 of an array of big-endian words, and a bit length */ function core_sha1(x, len) { /* append padding */ x[len >> 5] |= 0x80 << (24 - len % 32); x[((len + 64 >> 9) << 4) + 15] = len; var w = new Array(80); var a = 1732584193; var b = -271733879; var c = -1732584194; var d = 271733878; var e = -1009589776; var i, j, t, olda, oldb, oldc, oldd, olde; for (i = 0; i < x.length; i += 16) { olda = a; oldb = b; oldc = c; oldd = d; olde = e; for (j = 0; j < 80; j++) { if (j < 16) { w[j] = x[i + j]; } else { w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); } t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), safe_add(safe_add(e, w[j]), sha1_kt(j))); e = d; d = c; c = rol(b, 30); b = a; a = t; } a = safe_add(a, olda); b = safe_add(b, oldb); c = safe_add(c, oldc); d = safe_add(d, oldd); e = safe_add(e, olde); } return [a, b, c, d, e]; } /* * Perform the appropriate triplet combination function for the current * iteration */ function sha1_ft(t, b, c, d) { if (t < 20) { return (b & c) | ((~b) & d); } if (t < 40) { return b ^ c ^ d; } if (t < 60) { return (b & c) | (b & d) | (c & d); } return b ^ c ^ d; } /* * Determine the appropriate additive constant for the current iteration */ function sha1_kt(t) { return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514; } /* * Calculate the HMAC-SHA1 of a key and some data */ function core_hmac_sha1(key, data) { var bkey = str2binb(key); if (bkey.length > 16) { bkey = core_sha1(bkey, key.length * 8); } var ipad = new Array(16), opad = new Array(16); for (var i = 0; i < 16; i++) { ipad[i] = bkey[i] ^ 0x36363636; opad[i] = bkey[i] ^ 0x5C5C5C5C; } var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * 8); return core_sha1(opad.concat(hash), 512 + 160); } /* * Add integers, wrapping at 2^32. This uses 16-bit operations internally * to work around bugs in some JS interpreters. */ function safe_add(x, y) { var lsw = (x & 0xFFFF) + (y & 0xFFFF); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); } /* * Bitwise rotate a 32-bit number to the left. */ function rol(num, cnt) { return (num << cnt) | (num >>> (32 - cnt)); } /* * Convert an 8-bit or 16-bit string to an array of big-endian words * In 8-bit function, characters >255 have their hi-byte silently ignored. */ function str2binb(str) { var bin = []; var mask = 255; for (var i = 0; i < str.length * 8; i += 8) { bin[i>>5] |= (str.charCodeAt(i / 8) & mask) << (24 - i%32); } return bin; } /* * Convert an array of big-endian words to a string */ function binb2str(bin) { var str = ""; var mask = 255; for (var i = 0; i < bin.length * 32; i += 8) { str += String.fromCharCode((bin[i>>5] >>> (24 - i%32)) & mask); } return str; } /* * Convert an array of big-endian words to a base-64 string */ function binb2b64(binarray) { var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var str = ""; var triplet, j; for (var i = 0; i < binarray.length * 4; i += 3) { triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); for (j = 0; j < 4; j++) { if (i * 8 + j * 6 > binarray.length * 32) { str += "="; } else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); } } } return str; } /* * These are the functions you'll usually want to call * They take string arguments and return either hex or base-64 encoded strings */ return { b64_hmac_sha1: function (key, data){ return binb2b64(core_hmac_sha1(key, data)); }, b64_sha1: function (s) { return binb2b64(core_sha1(str2binb(s),s.length * 8)); }, binb2str: binb2str, core_hmac_sha1: core_hmac_sha1, str_hmac_sha1: function (key, data){ return binb2str(core_hmac_sha1(key, data)); }, str_sha1: function (s) { return binb2str(core_sha1(str2binb(s),s.length * 8)); }, }; })); strophejs-1.2.14+dfsg/src/start.frag000066400000000000000000000032701320017573300173270ustar00rootroot00000000000000/** File: strophe.js * A JavaScript library for writing XMPP clients. * * This library uses either Bidirectional-streams Over Synchronous HTTP (BOSH) * to emulate a persistent, stateful, two-way connection to an XMPP server or * alternatively WebSockets. * * More information on BOSH can be found in XEP 124. * For more information on XMPP-over WebSocket see this RFC: * http://tools.ietf.org/html/rfc7395 */ /* All of the Strophe globals are defined in this special function below so * that references to the globals become closures. This will ensure that * on page reload, these references will still be available to callbacks * that are still executing. */ /* jshint ignore:start */ (function (root, factory) { if (typeof define === 'function' && define.amd) { //Allow using this built library as an AMD module //in another project. That other project will only //see this AMD call, not the internal modules in //the closure below. define([], factory); } else { //Browser globals case. var wrapper = factory(); root.Strophe = wrapper.Strophe; root.$build = wrapper.$build; root.$iq = wrapper.$iq; root.$msg = wrapper.$msg; root.$pres = wrapper.$pres; root.SHA1 = wrapper.SHA1; root.MD5 = wrapper.MD5; root.b64_hmac_sha1 = wrapper.b64_hmac_sha1; root.b64_sha1 = wrapper.b64_sha1; root.str_hmac_sha1 = wrapper.str_hmac_sha1; root.str_sha1 = wrapper.str_sha1; } }(this, function () { //almond, and your modules will be inlined here /* jshint ignore:end */ strophejs-1.2.14+dfsg/src/utils.js000066400000000000000000000050711320017573300170300ustar00rootroot00000000000000(function (root, factory) { if (typeof define === 'function' && define.amd) { define([], function () { return factory(); }); } else { // Browser globals root.stropheUtils = factory(); } }(this, function () { var utils = { utf16to8: function (str) { var i, c; var out = ""; var len = str.length; for (i = 0; i < len; i++) { c = str.charCodeAt(i); if ((c >= 0x0000) && (c <= 0x007F)) { out += str.charAt(i); } else if (c > 0x07FF) { out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F)); out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F)); out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); } else { out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F)); out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); } } return out; }, addCookies: function (cookies) { /* Parameters: * (Object) cookies - either a map of cookie names * to string values or to maps of cookie values. * * For example: * { "myCookie": "1234" } * * or: * { "myCookie": { * "value": "1234", * "domain": ".example.org", * "path": "/", * "expires": expirationDate * } * } * * These values get passed to Strophe.Connection via * options.cookies */ var cookieName, cookieObj, isObj, cookieValue, expires, domain, path; for (cookieName in (cookies || {})) { expires = ''; domain = ''; path = ''; cookieObj = cookies[cookieName]; isObj = typeof cookieObj === "object"; cookieValue = escape(unescape(isObj ? cookieObj.value : cookieObj)); if (isObj) { expires = cookieObj.expires ? ";expires="+cookieObj.expires : ''; domain = cookieObj.domain ? ";domain="+cookieObj.domain : ''; path = cookieObj.path ? ";path="+cookieObj.path : ''; } document.cookie = cookieName+'='+cookieValue + expires + domain + path; } } }; return utils; })); strophejs-1.2.14+dfsg/src/websocket.js000066400000000000000000000442421320017573300176610ustar00rootroot00000000000000/* This program is distributed under the terms of the MIT license. Please see the LICENSE file for details. Copyright 2006-2008, OGG, LLC */ /* jshint undef: true, unused: true:, noarg: true, latedef: true */ /* global define, window, clearTimeout, WebSocket, DOMParser, Strophe, $build */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define(['strophe-core'], function (core) { return factory( core.Strophe, core.$build ); }); } else { // Browser globals return factory(Strophe, $build); } }(this, function (Strophe, $build) { /** Class: Strophe.WebSocket * _Private_ helper class that handles WebSocket Connections * * The Strophe.WebSocket class is used internally by Strophe.Connection * to encapsulate WebSocket sessions. It is not meant to be used from user's code. */ /** File: websocket.js * A JavaScript library to enable XMPP over Websocket in Strophejs. * * This file implements XMPP over WebSockets for Strophejs. * If a Connection is established with a Websocket url (ws://...) * Strophe will use WebSockets. * For more information on XMPP-over-WebSocket see RFC 7395: * http://tools.ietf.org/html/rfc7395 * * WebSocket support implemented by Andreas Guth (andreas.guth@rwth-aachen.de) */ /** PrivateConstructor: Strophe.Websocket * Create and initialize a Strophe.WebSocket object. * Currently only sets the connection Object. * * Parameters: * (Strophe.Connection) connection - The Strophe.Connection that will use WebSockets. * * Returns: * A new Strophe.WebSocket object. */ Strophe.Websocket = function(connection) { this._conn = connection; this.strip = "wrapper"; var service = connection.service; if (service.indexOf("ws:") !== 0 && service.indexOf("wss:") !== 0) { // If the service is not an absolute URL, assume it is a path and put the absolute // URL together from options, current URL and the path. var new_service = ""; if (connection.options.protocol === "ws" && window.location.protocol !== "https:") { new_service += "ws"; } else { new_service += "wss"; } new_service += "://" + window.location.host; if (service.indexOf("/") !== 0) { new_service += window.location.pathname + service; } else { new_service += service; } connection.service = new_service; } }; Strophe.Websocket.prototype = { /** PrivateFunction: _buildStream * _Private_ helper function to generate the start tag for WebSockets * * Returns: * A Strophe.Builder with a element. */ _buildStream: function () { return $build("open", { "xmlns": Strophe.NS.FRAMING, "to": this._conn.domain, "version": '1.0' }); }, /** PrivateFunction: _check_streamerror * _Private_ checks a message for stream:error * * Parameters: * (Strophe.Request) bodyWrap - The received stanza. * connectstatus - The ConnectStatus that will be set on error. * Returns: * true if there was a streamerror, false otherwise. */ _check_streamerror: function (bodyWrap, connectstatus) { var errors; if (bodyWrap.getElementsByTagNameNS) { errors = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, "error"); } else { errors = bodyWrap.getElementsByTagName("stream:error"); } if (errors.length === 0) { return false; } var error = errors[0]; var condition = ""; var text = ""; var ns = "urn:ietf:params:xml:ns:xmpp-streams"; for (var i = 0; i < error.childNodes.length; i++) { var e = error.childNodes[i]; if (e.getAttribute("xmlns") !== ns) { break; } if (e.nodeName === "text") { text = e.textContent; } else { condition = e.nodeName; } } var errorString = "WebSocket stream error: "; if (condition) { errorString += condition; } else { errorString += "unknown"; } if (text) { errorString += " - " + text; } Strophe.error(errorString); // close the connection on stream_error this._conn._changeConnectStatus(connectstatus, condition); this._conn._doDisconnect(); return true; }, /** PrivateFunction: _reset * Reset the connection. * * This function is called by the reset function of the Strophe Connection. * Is not needed by WebSockets. */ _reset: function () { return; }, /** PrivateFunction: _connect * _Private_ function called by Strophe.Connection.connect * * Creates a WebSocket for a connection and assigns Callbacks to it. * Does nothing if there already is a WebSocket. */ _connect: function () { // Ensure that there is no open WebSocket from a previous Connection. this._closeSocket(); // Create the new WobSocket this.socket = new WebSocket(this._conn.service, "xmpp"); this.socket.onopen = this._onOpen.bind(this); this.socket.onerror = this._onError.bind(this); this.socket.onclose = this._onClose.bind(this); this.socket.onmessage = this._connect_cb_wrapper.bind(this); }, /** PrivateFunction: _connect_cb * _Private_ function called by Strophe.Connection._connect_cb * * checks for stream:error * * Parameters: * (Strophe.Request) bodyWrap - The received stanza. */ _connect_cb: function(bodyWrap) { var error = this._check_streamerror(bodyWrap, Strophe.Status.CONNFAIL); if (error) { return Strophe.Status.CONNFAIL; } }, /** PrivateFunction: _handleStreamStart * _Private_ function that checks the opening tag for errors. * * Disconnects if there is an error and returns false, true otherwise. * * Parameters: * (Node) message - Stanza containing the tag. */ _handleStreamStart: function(message) { var error = false; // Check for errors in the tag var ns = message.getAttribute("xmlns"); if (typeof ns !== "string") { error = "Missing xmlns in "; } else if (ns !== Strophe.NS.FRAMING) { error = "Wrong xmlns in : " + ns; } var ver = message.getAttribute("version"); if (typeof ver !== "string") { error = "Missing version in "; } else if (ver !== "1.0") { error = "Wrong version in : " + ver; } if (error) { this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, error); this._conn._doDisconnect(); return false; } return true; }, /** PrivateFunction: _connect_cb_wrapper * _Private_ function that handles the first connection messages. * * On receiving an opening stream tag this callback replaces itself with the real * message handler. On receiving a stream error the connection is terminated. */ _connect_cb_wrapper: function(message) { if (message.data.indexOf("\s*)*/, ""); if (data === '') return; var streamStart = new DOMParser().parseFromString(data, "text/xml").documentElement; this._conn.xmlInput(streamStart); this._conn.rawInput(message.data); //_handleStreamSteart will check for XML errors and disconnect on error if (this._handleStreamStart(streamStart)) { //_connect_cb will check for stream:error and disconnect on error this._connect_cb(streamStart); } } else if (message.data.indexOf(" tag."); } } this._conn._doDisconnect(); }, /** PrivateFunction: _doDisconnect * _Private_ function to disconnect. * * Just closes the Socket for WebSockets */ _doDisconnect: function () { Strophe.info("WebSockets _doDisconnect was called"); this._closeSocket(); }, /** PrivateFunction _streamWrap * _Private_ helper function to wrap a stanza in a tag. * This is used so Strophe can process stanzas from WebSockets like BOSH */ _streamWrap: function (stanza) { return "" + stanza + ''; }, /** PrivateFunction: _closeSocket * _Private_ function to close the WebSocket. * * Closes the socket if it is still open and deletes it */ _closeSocket: function () { if (this.socket) { try { this.socket.close(); } catch (e) {} } this.socket = null; }, /** PrivateFunction: _emptyQueue * _Private_ function to check if the message queue is empty. * * Returns: * True, because WebSocket messages are send immediately after queueing. */ _emptyQueue: function () { return true; }, /** PrivateFunction: _onClose * _Private_ function to handle websockets closing. * * Nothing to do here for WebSockets */ _onClose: function(e) { if(this._conn.connected && !this._conn.disconnecting) { Strophe.error("Websocket closed unexpectedly"); this._conn._doDisconnect(); } else if (e && e.code === 1006 && !this._conn.connected && this.socket) { // in case the onError callback was not called (Safari 10 does not // call onerror when the initial connection fails) we need to // dispatch a CONNFAIL status update to be consistent with the // behavior on other browsers. Strophe.error("Websocket closed unexcectedly"); this._conn._changeConnectStatus( Strophe.Status.CONNFAIL, "The WebSocket connection could not be established or was disconnected." ); this._conn._doDisconnect(); } else { Strophe.info("Websocket closed"); } }, /** PrivateFunction: _no_auth_received * * Called on stream start/restart when no stream:features * has been received. */ _no_auth_received: function (_callback) { Strophe.error("Server did not send any auth methods"); this._conn._changeConnectStatus( Strophe.Status.CONNFAIL, "Server did not send any auth methods" ); if (_callback) { _callback = _callback.bind(this._conn); _callback(); } this._conn._doDisconnect(); }, /** PrivateFunction: _onDisconnectTimeout * _Private_ timeout handler for handling non-graceful disconnection. * * This does nothing for WebSockets */ _onDisconnectTimeout: function () {}, /** PrivateFunction: _abortAllRequests * _Private_ helper function that makes sure all pending requests are aborted. */ _abortAllRequests: function () {}, /** PrivateFunction: _onError * _Private_ function to handle websockets errors. * * Parameters: * (Object) error - The websocket error. */ _onError: function(error) { Strophe.error("Websocket error " + error); this._conn._changeConnectStatus( Strophe.Status.CONNFAIL, "The WebSocket connection could not be established or was disconnected." ); this._disconnect(); }, /** PrivateFunction: _onIdle * _Private_ function called by Strophe.Connection._onIdle * * sends all queued stanzas */ _onIdle: function () { var data = this._conn._data; if (data.length > 0 && !this._conn.paused) { for (var i = 0; i < data.length; i++) { if (data[i] !== null) { var stanza, rawStanza; if (data[i] === "restart") { stanza = this._buildStream().tree(); } else { stanza = data[i]; } rawStanza = Strophe.serialize(stanza); this._conn.xmlOutput(stanza); this._conn.rawOutput(rawStanza); this.socket.send(rawStanza); } } this._conn._data = []; } }, /** PrivateFunction: _onMessage * _Private_ function to handle websockets messages. * * This function parses each of the messages as if they are full documents. * [TODO : We may actually want to use a SAX Push parser]. * * Since all XMPP traffic starts with * * * The first stanza will always fail to be parsed. * * Additionally, the seconds stanza will always be with * the stream NS defined in the previous stanza, so we need to 'force' * the inclusion of the NS in this stanza. * * Parameters: * (string) message - The websocket message. */ _onMessage: function(message) { var elem, data; // check for closing stream var close = ''; if (message.data === close) { this._conn.rawInput(close); this._conn.xmlInput(message); if (!this._conn.disconnecting) { this._conn._doDisconnect(); } return; } else if (message.data.search(" tag before we close the connection return; } this._conn._dataRecv(elem, message.data); }, /** PrivateFunction: _onOpen * _Private_ function to handle websockets connection setup. * * The opening stream tag is sent here. */ _onOpen: function() { Strophe.info("Websocket open"); var start = this._buildStream(); this._conn.xmlOutput(start.tree()); var startString = Strophe.serialize(start); this._conn.rawOutput(startString); this.socket.send(startString); }, /** PrivateFunction: _reqToData * _Private_ function to get a stanza out of a request. * * WebSockets don't use requests, so the passed argument is just returned. * * Parameters: * (Object) stanza - The stanza. * * Returns: * The stanza that was passed. */ _reqToData: function (stanza) { return stanza; }, /** PrivateFunction: _send * _Private_ part of the Connection.send function for WebSocket * * Just flushes the messages that are in the queue */ _send: function () { this._conn.flush(); }, /** PrivateFunction: _sendRestart * * Send an xmpp:restart stanza. */ _sendRestart: function () { clearTimeout(this._conn._idleTimeout); this._conn._onIdle.bind(this._conn)(); } }; return Strophe; })); strophejs-1.2.14+dfsg/src/wrapper.js000066400000000000000000000004061320017573300173450ustar00rootroot00000000000000(function(root){ if(typeof define === 'function' && define.amd){ define([ "strophe-core", "strophe-bosh", "strophe-websocket" ], function (wrapper) { return wrapper; }); } })(this); strophejs-1.2.14+dfsg/strophe.js000066400000000000000000006550641320017573300166020ustar00rootroot00000000000000/** File: strophe.js * A JavaScript library for writing XMPP clients. * * This library uses either Bidirectional-streams Over Synchronous HTTP (BOSH) * to emulate a persistent, stateful, two-way connection to an XMPP server or * alternatively WebSockets. * * More information on BOSH can be found in XEP 124. * For more information on XMPP-over WebSocket see this RFC: * http://tools.ietf.org/html/rfc7395 */ /* All of the Strophe globals are defined in this special function below so * that references to the globals become closures. This will ensure that * on page reload, these references will still be available to callbacks * that are still executing. */ /* jshint ignore:start */ (function (root, factory) { if (typeof define === 'function' && define.amd) { //Allow using this built library as an AMD module //in another project. That other project will only //see this AMD call, not the internal modules in //the closure below. define([], factory); } else { //Browser globals case. var wrapper = factory(); root.Strophe = wrapper.Strophe; root.$build = wrapper.$build; root.$iq = wrapper.$iq; root.$msg = wrapper.$msg; root.$pres = wrapper.$pres; root.SHA1 = wrapper.SHA1; root.MD5 = wrapper.MD5; root.b64_hmac_sha1 = wrapper.b64_hmac_sha1; root.b64_sha1 = wrapper.b64_sha1; root.str_hmac_sha1 = wrapper.str_hmac_sha1; root.str_sha1 = wrapper.str_sha1; } }(this, function () { //almond, and your modules will be inlined here /* jshint ignore:end */ /** * @license almond 0.3.3 Copyright jQuery Foundation and other contributors. * Released under MIT license, http://github.com/requirejs/almond/LICENSE */ //Going sloppy to avoid 'use strict' string cost, but strict practices should //be followed. /*global setTimeout: false */ var requirejs, require, define; (function (undef) { var main, req, makeMap, handlers, defined = {}, waiting = {}, config = {}, defining = {}, hasOwn = Object.prototype.hasOwnProperty, aps = [].slice, jsSuffixRegExp = /\.js$/; function hasProp(obj, prop) { return hasOwn.call(obj, prop); } /** * Given a relative module name, like ./something, normalize it to * a real name that can be mapped to a path. * @param {String} name the relative name * @param {String} baseName a real name that the name arg is relative * to. * @returns {String} normalized name */ function normalize(name, baseName) { var nameParts, nameSegment, mapValue, foundMap, lastIndex, foundI, foundStarMap, starI, i, j, part, normalizedBaseParts, baseParts = baseName && baseName.split("/"), map = config.map, starMap = (map && map['*']) || {}; //Adjust any relative paths. if (name) { name = name.split('/'); lastIndex = name.length - 1; // If wanting node ID compatibility, strip .js from end // of IDs. Have to do this here, and not in nameToUrl // because node allows either .js or non .js to map // to same file. if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); } // Starts with a '.' so need the baseName if (name[0].charAt(0) === '.' && baseParts) { //Convert baseName to array, and lop off the last part, //so that . matches that 'directory' and not name of the baseName's //module. For instance, baseName of 'one/two/three', maps to //'one/two/three.js', but we want the directory, 'one/two' for //this normalization. normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); name = normalizedBaseParts.concat(name); } //start trimDots for (i = 0; i < name.length; i++) { part = name[i]; if (part === '.') { name.splice(i, 1); i -= 1; } else if (part === '..') { // If at the start, or previous value is still .., // keep them so that when converted to a path it may // still work when converted to a path, even though // as an ID it is less than ideal. In larger point // releases, may be better to just kick out an error. if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') { continue; } else if (i > 0) { name.splice(i - 1, 2); i -= 2; } } } //end trimDots name = name.join('/'); } //Apply map config if available. if ((baseParts || starMap) && map) { nameParts = name.split('/'); for (i = nameParts.length; i > 0; i -= 1) { nameSegment = nameParts.slice(0, i).join("/"); if (baseParts) { //Find the longest baseName segment match in the config. //So, do joins on the biggest to smallest lengths of baseParts. for (j = baseParts.length; j > 0; j -= 1) { mapValue = map[baseParts.slice(0, j).join('/')]; //baseName segment has config, find if it has one for //this name. if (mapValue) { mapValue = mapValue[nameSegment]; if (mapValue) { //Match, update name to the new value. foundMap = mapValue; foundI = i; break; } } } } if (foundMap) { break; } //Check for a star map match, but just hold on to it, //if there is a shorter segment match later in a matching //config, then favor over this star map. if (!foundStarMap && starMap && starMap[nameSegment]) { foundStarMap = starMap[nameSegment]; starI = i; } } if (!foundMap && foundStarMap) { foundMap = foundStarMap; foundI = starI; } if (foundMap) { nameParts.splice(0, foundI, foundMap); name = nameParts.join('/'); } } return name; } function makeRequire(relName, forceSync) { return function () { //A version of a require function that passes a moduleName //value for items that may need to //look up paths relative to the moduleName var args = aps.call(arguments, 0); //If first arg is not require('string'), and there is only //one arg, it is the array form without a callback. Insert //a null so that the following concat is correct. if (typeof args[0] !== 'string' && args.length === 1) { args.push(null); } return req.apply(undef, args.concat([relName, forceSync])); }; } function makeNormalize(relName) { return function (name) { return normalize(name, relName); }; } function makeLoad(depName) { return function (value) { defined[depName] = value; }; } function callDep(name) { if (hasProp(waiting, name)) { var args = waiting[name]; delete waiting[name]; defining[name] = true; main.apply(undef, args); } if (!hasProp(defined, name) && !hasProp(defining, name)) { throw new Error('No ' + name); } return defined[name]; } //Turns a plugin!resource to [plugin, resource] //with the plugin being undefined if the name //did not have a plugin prefix. function splitPrefix(name) { var prefix, index = name ? name.indexOf('!') : -1; if (index > -1) { prefix = name.substring(0, index); name = name.substring(index + 1, name.length); } return [prefix, name]; } //Creates a parts array for a relName where first part is plugin ID, //second part is resource ID. Assumes relName has already been normalized. function makeRelParts(relName) { return relName ? splitPrefix(relName) : []; } /** * Makes a name map, normalizing the name, and using a plugin * for normalization if necessary. Grabs a ref to plugin * too, as an optimization. */ makeMap = function (name, relParts) { var plugin, parts = splitPrefix(name), prefix = parts[0], relResourceName = relParts[1]; name = parts[1]; if (prefix) { prefix = normalize(prefix, relResourceName); plugin = callDep(prefix); } //Normalize according if (prefix) { if (plugin && plugin.normalize) { name = plugin.normalize(name, makeNormalize(relResourceName)); } else { name = normalize(name, relResourceName); } } else { name = normalize(name, relResourceName); parts = splitPrefix(name); prefix = parts[0]; name = parts[1]; if (prefix) { plugin = callDep(prefix); } } //Using ridiculous property names for space reasons return { f: prefix ? prefix + '!' + name : name, //fullName n: name, pr: prefix, p: plugin }; }; function makeConfig(name) { return function () { return (config && config.config && config.config[name]) || {}; }; } handlers = { require: function (name) { return makeRequire(name); }, exports: function (name) { var e = defined[name]; if (typeof e !== 'undefined') { return e; } else { return (defined[name] = {}); } }, module: function (name) { return { id: name, uri: '', exports: defined[name], config: makeConfig(name) }; } }; main = function (name, deps, callback, relName) { var cjsModule, depName, ret, map, i, relParts, args = [], callbackType = typeof callback, usingExports; //Use name if no relName relName = relName || name; relParts = makeRelParts(relName); //Call the callback to define the module, if necessary. if (callbackType === 'undefined' || callbackType === 'function') { //Pull out the defined dependencies and pass the ordered //values to the callback. //Default to [require, exports, module] if no deps deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; for (i = 0; i < deps.length; i += 1) { map = makeMap(deps[i], relParts); depName = map.f; //Fast path CommonJS standard dependencies. if (depName === "require") { args[i] = handlers.require(name); } else if (depName === "exports") { //CommonJS module spec 1.1 args[i] = handlers.exports(name); usingExports = true; } else if (depName === "module") { //CommonJS module spec 1.1 cjsModule = args[i] = handlers.module(name); } else if (hasProp(defined, depName) || hasProp(waiting, depName) || hasProp(defining, depName)) { args[i] = callDep(depName); } else if (map.p) { map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); args[i] = defined[depName]; } else { throw new Error(name + ' missing ' + depName); } } ret = callback ? callback.apply(defined[name], args) : undefined; if (name) { //If setting exports via "module" is in play, //favor that over return value and exports. After that, //favor a non-undefined return value over exports use. if (cjsModule && cjsModule.exports !== undef && cjsModule.exports !== defined[name]) { defined[name] = cjsModule.exports; } else if (ret !== undef || !usingExports) { //Use the return value from the function. defined[name] = ret; } } } else if (name) { //May just be an object definition for the module. Only //worry about defining if have a module name. defined[name] = callback; } }; requirejs = require = req = function (deps, callback, relName, forceSync, alt) { if (typeof deps === "string") { if (handlers[deps]) { //callback in this case is really relName return handlers[deps](callback); } //Just return the module wanted. In this scenario, the //deps arg is the module name, and second arg (if passed) //is just the relName. //Normalize module name, if it contains . or .. return callDep(makeMap(deps, makeRelParts(callback)).f); } else if (!deps.splice) { //deps is a config object, not an array. config = deps; if (config.deps) { req(config.deps, config.callback); } if (!callback) { return; } if (callback.splice) { //callback is an array, which means it is a dependency list. //Adjust args if there are dependencies deps = callback; callback = relName; relName = null; } else { deps = undef; } } //Support require(['a']) callback = callback || function () {}; //If relName is a function, it is an errback handler, //so remove it. if (typeof relName === 'function') { relName = forceSync; forceSync = alt; } //Simulate async callback; if (forceSync) { main(undef, deps, callback, relName); } else { //Using a non-zero value because of concern for what old browsers //do, and latest browsers "upgrade" to 4 if lower value is used: //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: //If want a value immediately, use require('id') instead -- something //that works in almond on the global level, but not guaranteed and //unlikely to work in other AMD implementations. setTimeout(function () { main(undef, deps, callback, relName); }, 4); } return req; }; /** * Just drops the config on the floor, but returns req in case * the config return value is used. */ req.config = function (cfg) { return req(cfg); }; /** * Expose module registry for debugging and tooling */ requirejs._defined = defined; define = function (name, deps, callback) { if (typeof name !== 'string') { throw new Error('See almond README: incorrect module build, no module name'); } //This module may not have dependencies if (!deps.splice) { //deps is not an array, so probably means //an object literal or factory function for //the value. Adjust args. callback = deps; deps = []; } if (!hasProp(defined, name) && !hasProp(waiting, name)) { waiting[name] = [name, deps, callback]; } }; define.amd = { jQuery: true }; }()); define("node_modules/almond/almond.js", function(){}); /* This program is distributed under the terms of the MIT license. Please see the LICENSE file for details. Copyright 2006-2008, OGG, LLC */ /* jshint undef: true, unused: true:, noarg: true, latedef: true */ /* global define */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define('strophe-polyfill',[], function () { return factory(root); }); } else { // Browser globals return factory(root); } }(this, function (root) { /** Function: Function.prototype.bind * Bind a function to an instance. * * This Function object extension method creates a bound method similar * to those in Python. This means that the 'this' object will point * to the instance you want. See * and * for a complete explanation. * * This extension already exists in some browsers (namely, Firefox 3), but * we provide it to support those that don't. * * Parameters: * (Object) obj - The object that will become 'this' in the bound function. * (Object) argN - An option argument that will be prepended to the * arguments given for the function call * * Returns: * The bound function. */ if (!Function.prototype.bind) { Function.prototype.bind = function (obj /*, arg1, arg2, ... */) { var func = this; var _slice = Array.prototype.slice; var _concat = Array.prototype.concat; var _args = _slice.call(arguments, 1); return function () { return func.apply(obj ? obj : this, _concat.call(_args, _slice.call(arguments, 0))); }; }; } /** Function: Array.isArray * This is a polyfill for the ES5 Array.isArray method. */ if (!Array.isArray) { Array.isArray = function(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; }; } /** Function: Array.prototype.indexOf * Return the index of an object in an array. * * This function is not supplied by some JavaScript implementations, so * we provide it if it is missing. This code is from: * http://developer.mozilla.org/En/Core_JavaScript_1.5_Reference:Objects:Array:indexOf * * Parameters: * (Object) elt - The object to look for. * (Integer) from - The index from which to start looking. (optional). * * Returns: * The index of elt in the array or -1 if not found. */ if (!Array.prototype.indexOf) { Array.prototype.indexOf = function(elt /*, from*/) { var len = this.length; var from = Number(arguments[1]) || 0; from = (from < 0) ? Math.ceil(from) : Math.floor(from); if (from < 0) { from += len; } for (; from < len; from++) { if (from in this && this[from] === elt) { return from; } } return -1; }; } /** Function: Array.prototype.forEach * * This function is not available in IE < 9 * * See */ if (!Array.prototype.forEach) { Array.prototype.forEach = function(callback, thisArg) { var T, k; if (this === null) { throw new TypeError(' this is null or not defined'); } // 1. Let O be the result of calling toObject() passing the // |this| value as the argument. var O = Object(this); // 2. Let lenValue be the result of calling the Get() internal // method of O with the argument "length". // 3. Let len be toUint32(lenValue). var len = O.length >>> 0; // 4. If isCallable(callback) is false, throw a TypeError exception. // See: http://es5.github.com/#x9.11 if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function'); } // 5. If thisArg was supplied, let T be thisArg; else let // T be undefined. if (arguments.length > 1) { T = thisArg; } // 6. Let k be 0 k = 0; // 7. Repeat, while k < len while (k < len) { var kValue; // a. Let Pk be ToString(k). // This is implicit for LHS operands of the in operator // b. Let kPresent be the result of calling the HasProperty // internal method of O with argument Pk. // This step can be combined with c // c. If kPresent is true, then if (k in O) { // i. Let kValue be the result of calling the Get internal // method of O with argument Pk. kValue = O[k]; // ii. Call the Call internal method of callback with T as // the this value and argument list containing kValue, k, and O. callback.call(T, kValue, k, O); } // d. Increase k by 1. k++; } // 8. return undefined }; } // This code was written by Tyler Akins and has been placed in the // public domain. It would be nice if you left this header intact. // Base64 code from Tyler Akins -- http://rumkin.com var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; if (!root.btoa) { root.btoa = function (input) { /** * Encodes a string in base64 * @param {String} input The string to encode in base64. */ var output = ""; var chr1, chr2, chr3; var enc1, enc2, enc3, enc4; var i = 0; do { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc2 = ((chr1 & 3) << 4); enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4); } while (i < input.length); return output; }; } if (!root.atob) { root.atob = function (input) { /** * Decodes a base64 string. * @param {String} input The string to decode. */ var output = ""; var chr1, chr2, chr3; var enc1, enc2, enc3, enc4; var i = 0; // remove all characters that are not A-Z, a-z, 0-9, +, /, or = input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); do { enc1 = keyStr.indexOf(input.charAt(i++)); enc2 = keyStr.indexOf(input.charAt(i++)); enc3 = keyStr.indexOf(input.charAt(i++)); enc4 = keyStr.indexOf(input.charAt(i++)); chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; output = output + String.fromCharCode(chr1); if (enc3 !== 64) { output = output + String.fromCharCode(chr2); } if (enc4 !== 64) { output = output + String.fromCharCode(chr3); } } while (i < input.length); return output; }; } })); /* * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined * in FIPS PUB 180-1 * Version 2.1a Copyright Paul Johnston 2000 - 2002. * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet * Distributed under the BSD License * See http://pajhome.org.uk/crypt/md5 for details. */ /* jshint undef: true, unused: true:, noarg: true, latedef: false */ /* global define */ /* Some functions and variables have been stripped for use with Strophe */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define('strophe-sha1', [],function () { return factory(); }); } else { // Browser globals root.SHA1 = factory(); } }(this, function () { /* * Calculate the SHA-1 of an array of big-endian words, and a bit length */ function core_sha1(x, len) { /* append padding */ x[len >> 5] |= 0x80 << (24 - len % 32); x[((len + 64 >> 9) << 4) + 15] = len; var w = new Array(80); var a = 1732584193; var b = -271733879; var c = -1732584194; var d = 271733878; var e = -1009589776; var i, j, t, olda, oldb, oldc, oldd, olde; for (i = 0; i < x.length; i += 16) { olda = a; oldb = b; oldc = c; oldd = d; olde = e; for (j = 0; j < 80; j++) { if (j < 16) { w[j] = x[i + j]; } else { w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); } t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), safe_add(safe_add(e, w[j]), sha1_kt(j))); e = d; d = c; c = rol(b, 30); b = a; a = t; } a = safe_add(a, olda); b = safe_add(b, oldb); c = safe_add(c, oldc); d = safe_add(d, oldd); e = safe_add(e, olde); } return [a, b, c, d, e]; } /* * Perform the appropriate triplet combination function for the current * iteration */ function sha1_ft(t, b, c, d) { if (t < 20) { return (b & c) | ((~b) & d); } if (t < 40) { return b ^ c ^ d; } if (t < 60) { return (b & c) | (b & d) | (c & d); } return b ^ c ^ d; } /* * Determine the appropriate additive constant for the current iteration */ function sha1_kt(t) { return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514; } /* * Calculate the HMAC-SHA1 of a key and some data */ function core_hmac_sha1(key, data) { var bkey = str2binb(key); if (bkey.length > 16) { bkey = core_sha1(bkey, key.length * 8); } var ipad = new Array(16), opad = new Array(16); for (var i = 0; i < 16; i++) { ipad[i] = bkey[i] ^ 0x36363636; opad[i] = bkey[i] ^ 0x5C5C5C5C; } var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * 8); return core_sha1(opad.concat(hash), 512 + 160); } /* * Add integers, wrapping at 2^32. This uses 16-bit operations internally * to work around bugs in some JS interpreters. */ function safe_add(x, y) { var lsw = (x & 0xFFFF) + (y & 0xFFFF); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); } /* * Bitwise rotate a 32-bit number to the left. */ function rol(num, cnt) { return (num << cnt) | (num >>> (32 - cnt)); } /* * Convert an 8-bit or 16-bit string to an array of big-endian words * In 8-bit function, characters >255 have their hi-byte silently ignored. */ function str2binb(str) { var bin = []; var mask = 255; for (var i = 0; i < str.length * 8; i += 8) { bin[i>>5] |= (str.charCodeAt(i / 8) & mask) << (24 - i%32); } return bin; } /* * Convert an array of big-endian words to a string */ function binb2str(bin) { var str = ""; var mask = 255; for (var i = 0; i < bin.length * 32; i += 8) { str += String.fromCharCode((bin[i>>5] >>> (24 - i%32)) & mask); } return str; } /* * Convert an array of big-endian words to a base-64 string */ function binb2b64(binarray) { var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var str = ""; var triplet, j; for (var i = 0; i < binarray.length * 4; i += 3) { triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); for (j = 0; j < 4; j++) { if (i * 8 + j * 6 > binarray.length * 32) { str += "="; } else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); } } } return str; } /* * These are the functions you'll usually want to call * They take string arguments and return either hex or base-64 encoded strings */ return { b64_hmac_sha1: function (key, data){ return binb2b64(core_hmac_sha1(key, data)); }, b64_sha1: function (s) { return binb2b64(core_sha1(str2binb(s),s.length * 8)); }, binb2str: binb2str, core_hmac_sha1: core_hmac_sha1, str_hmac_sha1: function (key, data){ return binb2str(core_hmac_sha1(key, data)); }, str_sha1: function (s) { return binb2str(core_sha1(str2binb(s),s.length * 8)); }, }; })); /* * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message * Digest Algorithm, as defined in RFC 1321. * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet * Distributed under the BSD License * See http://pajhome.org.uk/crypt/md5 for more info. */ /* * Everything that isn't used by Strophe has been stripped here! */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define('strophe-md5',[], function () { return factory(); }); } else { // Browser globals root.MD5 = factory(); } }(this, function () { /* * Add integers, wrapping at 2^32. This uses 16-bit operations internally * to work around bugs in some JS interpreters. */ var safe_add = function (x, y) { var lsw = (x & 0xFFFF) + (y & 0xFFFF); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); }; /* * Bitwise rotate a 32-bit number to the left. */ var bit_rol = function (num, cnt) { return (num << cnt) | (num >>> (32 - cnt)); }; /* * Convert a string to an array of little-endian words */ var str2binl = function (str) { var bin = []; for(var i = 0; i < str.length * 8; i += 8) { bin[i>>5] |= (str.charCodeAt(i / 8) & 255) << (i%32); } return bin; }; /* * Convert an array of little-endian words to a string */ var binl2str = function (bin) { var str = ""; for(var i = 0; i < bin.length * 32; i += 8) { str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & 255); } return str; }; /* * Convert an array of little-endian words to a hex string. */ var binl2hex = function (binarray) { var hex_tab = "0123456789abcdef"; var str = ""; for(var i = 0; i < binarray.length * 4; i++) { str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); } return str; }; /* * These functions implement the four basic operations the algorithm uses. */ var md5_cmn = function (q, a, b, x, s, t) { return safe_add(bit_rol(safe_add(safe_add(a, q),safe_add(x, t)), s),b); }; var md5_ff = function (a, b, c, d, x, s, t) { return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); }; var md5_gg = function (a, b, c, d, x, s, t) { return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); }; var md5_hh = function (a, b, c, d, x, s, t) { return md5_cmn(b ^ c ^ d, a, b, x, s, t); }; var md5_ii = function (a, b, c, d, x, s, t) { return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); }; /* * Calculate the MD5 of an array of little-endian words, and a bit length */ var core_md5 = function (x, len) { /* append padding */ x[len >> 5] |= 0x80 << ((len) % 32); x[(((len + 64) >>> 9) << 4) + 14] = len; var a = 1732584193; var b = -271733879; var c = -1732584194; var d = 271733878; var olda, oldb, oldc, oldd; for (var i = 0; i < x.length; i += 16) { olda = a; oldb = b; oldc = c; oldd = d; a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); c = md5_ff(c, d, a, b, x[i+10], 17, -42063); b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); a = safe_add(a, olda); b = safe_add(b, oldb); c = safe_add(c, oldc); d = safe_add(d, oldd); } return [a, b, c, d]; }; var obj = { /* * These are the functions you'll usually want to call. * They take string arguments and return either hex or base-64 encoded * strings. */ hexdigest: function (s) { return binl2hex(core_md5(str2binl(s), s.length * 8)); }, hash: function (s) { return binl2str(core_md5(str2binl(s), s.length * 8)); } }; return obj; })); (function (root, factory) { if (typeof define === 'function' && define.amd) { define('strophe-utils',[], function () { return factory(); }); } else { // Browser globals root.stropheUtils = factory(); } }(this, function () { var utils = { utf16to8: function (str) { var i, c; var out = ""; var len = str.length; for (i = 0; i < len; i++) { c = str.charCodeAt(i); if ((c >= 0x0000) && (c <= 0x007F)) { out += str.charAt(i); } else if (c > 0x07FF) { out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F)); out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F)); out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); } else { out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F)); out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); } } return out; }, addCookies: function (cookies) { /* Parameters: * (Object) cookies - either a map of cookie names * to string values or to maps of cookie values. * * For example: * { "myCookie": "1234" } * * or: * { "myCookie": { * "value": "1234", * "domain": ".example.org", * "path": "/", * "expires": expirationDate * } * } * * These values get passed to Strophe.Connection via * options.cookies */ var cookieName, cookieObj, isObj, cookieValue, expires, domain, path; for (cookieName in (cookies || {})) { expires = ''; domain = ''; path = ''; cookieObj = cookies[cookieName]; isObj = typeof cookieObj === "object"; cookieValue = escape(unescape(isObj ? cookieObj.value : cookieObj)); if (isObj) { expires = cookieObj.expires ? ";expires="+cookieObj.expires : ''; domain = cookieObj.domain ? ";domain="+cookieObj.domain : ''; path = cookieObj.path ? ";path="+cookieObj.path : ''; } document.cookie = cookieName+'='+cookieValue + expires + domain + path; } } }; return utils; })); /* This program is distributed under the terms of the MIT license. Please see the LICENSE file for details. Copyright 2006-2008, OGG, LLC */ /* jshint undef: true, unused: true:, noarg: true, latedef: true */ /*global define, document, sessionStorage, setTimeout, clearTimeout, ActiveXObject, DOMParser, btoa, atob */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define('strophe-core',[ 'strophe-sha1', 'strophe-md5', 'strophe-utils' ], function () { return factory.apply(this, arguments); }); } else { // Browser globals var o = factory(root.SHA1, root.MD5, root.stropheUtils); root.Strophe = o.Strophe; root.$build = o.$build; root.$iq = o.$iq; root.$msg = o.$msg; root.$pres = o.$pres; root.SHA1 = o.SHA1; root.MD5 = o.MD5; root.b64_hmac_sha1 = o.SHA1.b64_hmac_sha1; root.b64_sha1 = o.SHA1.b64_sha1; root.str_hmac_sha1 = o.SHA1.str_hmac_sha1; root.str_sha1 = o.SHA1.str_sha1; } }(this, function (SHA1, MD5, utils) { var Strophe; /** Function: $build * Create a Strophe.Builder. * This is an alias for 'new Strophe.Builder(name, attrs)'. * * Parameters: * (String) name - The root element name. * (Object) attrs - The attributes for the root element in object notation. * * Returns: * A new Strophe.Builder object. */ function $build(name, attrs) { return new Strophe.Builder(name, attrs); } /** Function: $msg * Create a Strophe.Builder with a element as the root. * * Parameters: * (Object) attrs - The element attributes in object notation. * * Returns: * A new Strophe.Builder object. */ function $msg(attrs) { return new Strophe.Builder("message", attrs); } /** Function: $iq * Create a Strophe.Builder with an element as the root. * * Parameters: * (Object) attrs - The element attributes in object notation. * * Returns: * A new Strophe.Builder object. */ function $iq(attrs) { return new Strophe.Builder("iq", attrs); } /** Function: $pres * Create a Strophe.Builder with a element as the root. * * Parameters: * (Object) attrs - The element attributes in object notation. * * Returns: * A new Strophe.Builder object. */ function $pres(attrs) { return new Strophe.Builder("presence", attrs); } /** Class: Strophe * An object container for all Strophe library functions. * * This class is just a container for all the objects and constants * used in the library. It is not meant to be instantiated, but to * provide a namespace for library objects, constants, and functions. */ Strophe = { /** Constant: VERSION */ VERSION: "1.2.14", /** Constants: XMPP Namespace Constants * Common namespace constants from the XMPP RFCs and XEPs. * * NS.HTTPBIND - HTTP BIND namespace from XEP 124. * NS.BOSH - BOSH namespace from XEP 206. * NS.CLIENT - Main XMPP client namespace. * NS.AUTH - Legacy authentication namespace. * NS.ROSTER - Roster operations namespace. * NS.PROFILE - Profile namespace. * NS.DISCO_INFO - Service discovery info namespace from XEP 30. * NS.DISCO_ITEMS - Service discovery items namespace from XEP 30. * NS.MUC - Multi-User Chat namespace from XEP 45. * NS.SASL - XMPP SASL namespace from RFC 3920. * NS.STREAM - XMPP Streams namespace from RFC 3920. * NS.BIND - XMPP Binding namespace from RFC 3920. * NS.SESSION - XMPP Session namespace from RFC 3920. * NS.XHTML_IM - XHTML-IM namespace from XEP 71. * NS.XHTML - XHTML body namespace from XEP 71. */ NS: { HTTPBIND: "http://jabber.org/protocol/httpbind", BOSH: "urn:xmpp:xbosh", CLIENT: "jabber:client", AUTH: "jabber:iq:auth", ROSTER: "jabber:iq:roster", PROFILE: "jabber:iq:profile", DISCO_INFO: "http://jabber.org/protocol/disco#info", DISCO_ITEMS: "http://jabber.org/protocol/disco#items", MUC: "http://jabber.org/protocol/muc", SASL: "urn:ietf:params:xml:ns:xmpp-sasl", STREAM: "http://etherx.jabber.org/streams", FRAMING: "urn:ietf:params:xml:ns:xmpp-framing", BIND: "urn:ietf:params:xml:ns:xmpp-bind", SESSION: "urn:ietf:params:xml:ns:xmpp-session", VERSION: "jabber:iq:version", STANZAS: "urn:ietf:params:xml:ns:xmpp-stanzas", XHTML_IM: "http://jabber.org/protocol/xhtml-im", XHTML: "http://www.w3.org/1999/xhtml" }, /** Constants: XHTML_IM Namespace * contains allowed tags, tag attributes, and css properties. * Used in the createHtml function to filter incoming html into the allowed XHTML-IM subset. * See http://xmpp.org/extensions/xep-0071.html#profile-summary for the list of recommended * allowed tags and their attributes. */ XHTML: { tags: ['a','blockquote','br','cite','em','img','li','ol','p','span','strong','ul','body'], attributes: { 'a': ['href'], 'blockquote': ['style'], 'br': [], 'cite': ['style'], 'em': [], 'img': ['src', 'alt', 'style', 'height', 'width'], 'li': ['style'], 'ol': ['style'], 'p': ['style'], 'span': ['style'], 'strong': [], 'ul': ['style'], 'body': [] }, css: ['background-color','color','font-family','font-size','font-style','font-weight','margin-left','margin-right','text-align','text-decoration'], /** Function: XHTML.validTag * * Utility method to determine whether a tag is allowed * in the XHTML_IM namespace. * * XHTML tag names are case sensitive and must be lower case. */ validTag: function(tag) { for (var i = 0; i < Strophe.XHTML.tags.length; i++) { if (tag === Strophe.XHTML.tags[i]) { return true; } } return false; }, /** Function: XHTML.validAttribute * * Utility method to determine whether an attribute is allowed * as recommended per XEP-0071 * * XHTML attribute names are case sensitive and must be lower case. */ validAttribute: function(tag, attribute) { if (typeof Strophe.XHTML.attributes[tag] !== 'undefined' && Strophe.XHTML.attributes[tag].length > 0) { for (var i = 0; i < Strophe.XHTML.attributes[tag].length; i++) { if (attribute === Strophe.XHTML.attributes[tag][i]) { return true; } } } return false; }, validCSS: function(style) { for (var i = 0; i < Strophe.XHTML.css.length; i++) { if (style === Strophe.XHTML.css[i]) { return true; } } return false; } }, /** Constants: Connection Status Constants * Connection status constants for use by the connection handler * callback. * * Status.ERROR - An error has occurred * Status.CONNECTING - The connection is currently being made * Status.CONNFAIL - The connection attempt failed * Status.AUTHENTICATING - The connection is authenticating * Status.AUTHFAIL - The authentication attempt failed * Status.CONNECTED - The connection has succeeded * Status.DISCONNECTED - The connection has been terminated * Status.DISCONNECTING - The connection is currently being terminated * Status.ATTACHED - The connection has been attached * Status.REDIRECT - The connection has been redirected * Status.CONNTIMEOUT - The connection has timed out */ Status: { ERROR: 0, CONNECTING: 1, CONNFAIL: 2, AUTHENTICATING: 3, AUTHFAIL: 4, CONNECTED: 5, DISCONNECTED: 6, DISCONNECTING: 7, ATTACHED: 8, REDIRECT: 9, CONNTIMEOUT: 10 }, /** Constants: Log Level Constants * Logging level indicators. * * LogLevel.DEBUG - Debug output * LogLevel.INFO - Informational output * LogLevel.WARN - Warnings * LogLevel.ERROR - Errors * LogLevel.FATAL - Fatal errors */ LogLevel: { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, FATAL: 4 }, /** PrivateConstants: DOM Element Type Constants * DOM element types. * * ElementType.NORMAL - Normal element. * ElementType.TEXT - Text data element. * ElementType.FRAGMENT - XHTML fragment element. */ ElementType: { NORMAL: 1, TEXT: 3, CDATA: 4, FRAGMENT: 11 }, /** PrivateConstants: Timeout Values * Timeout values for error states. These values are in seconds. * These should not be changed unless you know exactly what you are * doing. * * TIMEOUT - Timeout multiplier. A waiting request will be considered * failed after Math.floor(TIMEOUT * wait) seconds have elapsed. * This defaults to 1.1, and with default wait, 66 seconds. * SECONDARY_TIMEOUT - Secondary timeout multiplier. In cases where * Strophe can detect early failure, it will consider the request * failed if it doesn't return after * Math.floor(SECONDARY_TIMEOUT * wait) seconds have elapsed. * This defaults to 0.1, and with default wait, 6 seconds. */ TIMEOUT: 1.1, SECONDARY_TIMEOUT: 0.1, /** Function: addNamespace * This function is used to extend the current namespaces in * Strophe.NS. It takes a key and a value with the key being the * name of the new namespace, with its actual value. * For example: * Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub"); * * Parameters: * (String) name - The name under which the namespace will be * referenced under Strophe.NS * (String) value - The actual namespace. */ addNamespace: function (name, value) { Strophe.NS[name] = value; }, /** Function: forEachChild * Map a function over some or all child elements of a given element. * * This is a small convenience function for mapping a function over * some or all of the children of an element. If elemName is null, all * children will be passed to the function, otherwise only children * whose tag names match elemName will be passed. * * Parameters: * (XMLElement) elem - The element to operate on. * (String) elemName - The child element tag name filter. * (Function) func - The function to apply to each child. This * function should take a single argument, a DOM element. */ forEachChild: function (elem, elemName, func) { var i, childNode; for (i = 0; i < elem.childNodes.length; i++) { childNode = elem.childNodes[i]; if (childNode.nodeType === Strophe.ElementType.NORMAL && (!elemName || this.isTagEqual(childNode, elemName))) { func(childNode); } } }, /** Function: isTagEqual * Compare an element's tag name with a string. * * This function is case sensitive. * * Parameters: * (XMLElement) el - A DOM element. * (String) name - The element name. * * Returns: * true if the element's tag name matches _el_, and false * otherwise. */ isTagEqual: function (el, name) { return el.tagName === name; }, /** PrivateVariable: _xmlGenerator * _Private_ variable that caches a DOM document to * generate elements. */ _xmlGenerator: null, /** PrivateFunction: _makeGenerator * _Private_ function that creates a dummy XML DOM document to serve as * an element and text node generator. */ _makeGenerator: function () { var doc; // IE9 does implement createDocument(); however, using it will cause the browser to leak memory on page unload. // Here, we test for presence of createDocument() plus IE's proprietary documentMode attribute, which would be // less than 10 in the case of IE9 and below. if (document.implementation.createDocument === undefined || document.implementation.createDocument && document.documentMode && document.documentMode < 10) { doc = this._getIEXmlDom(); doc.appendChild(doc.createElement('strophe')); } else { doc = document.implementation .createDocument('jabber:client', 'strophe', null); } return doc; }, /** Function: xmlGenerator * Get the DOM document to generate elements. * * Returns: * The currently used DOM document. */ xmlGenerator: function () { if (!Strophe._xmlGenerator) { Strophe._xmlGenerator = Strophe._makeGenerator(); } return Strophe._xmlGenerator; }, /** PrivateFunction: _getIEXmlDom * Gets IE xml doc object * * Returns: * A Microsoft XML DOM Object * See Also: * http://msdn.microsoft.com/en-us/library/ms757837%28VS.85%29.aspx */ _getIEXmlDom : function() { var doc = null; var docStrings = [ "Msxml2.DOMDocument.6.0", "Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "MSXML2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM" ]; for (var d = 0; d < docStrings.length; d++) { if (doc === null) { try { doc = new ActiveXObject(docStrings[d]); } catch (e) { doc = null; } } else { break; } } return doc; }, /** Function: xmlElement * Create an XML DOM element. * * This function creates an XML DOM element correctly across all * implementations. Note that these are not HTML DOM elements, which * aren't appropriate for XMPP stanzas. * * Parameters: * (String) name - The name for the element. * (Array|Object) attrs - An optional array or object containing * key/value pairs to use as element attributes. The object should * be in the format {'key': 'value'} or {key: 'value'}. The array * should have the format [['key1', 'value1'], ['key2', 'value2']]. * (String) text - The text child data for the element. * * Returns: * A new XML DOM element. */ xmlElement: function (name) { if (!name) { return null; } var node = Strophe.xmlGenerator().createElement(name); // FIXME: this should throw errors if args are the wrong type or // there are more than two optional args var a, i, k; for (a = 1; a < arguments.length; a++) { var arg = arguments[a]; if (!arg) { continue; } if (typeof(arg) === "string" || typeof(arg) === "number") { node.appendChild(Strophe.xmlTextNode(arg)); } else if (typeof(arg) === "object" && typeof(arg.sort) === "function") { for (i = 0; i < arg.length; i++) { var attr = arg[i]; if (typeof(attr) === "object" && typeof(attr.sort) === "function" && attr[1] !== undefined && attr[1] !== null) { node.setAttribute(attr[0], attr[1]); } } } else if (typeof(arg) === "object") { for (k in arg) { if (arg.hasOwnProperty(k)) { if (arg[k] !== undefined && arg[k] !== null) { node.setAttribute(k, arg[k]); } } } } } return node; }, /* Function: xmlescape * Excapes invalid xml characters. * * Parameters: * (String) text - text to escape. * * Returns: * Escaped text. */ xmlescape: function(text) { text = text.replace(/\&/g, "&"); text = text.replace(//g, ">"); text = text.replace(/'/g, "'"); text = text.replace(/"/g, """); return text; }, /* Function: xmlunescape * Unexcapes invalid xml characters. * * Parameters: * (String) text - text to unescape. * * Returns: * Unescaped text. */ xmlunescape: function(text) { text = text.replace(/\&/g, "&"); text = text.replace(/</g, "<"); text = text.replace(/>/g, ">"); text = text.replace(/'/g, "'"); text = text.replace(/"/g, "\""); return text; }, /** Function: xmlTextNode * Creates an XML DOM text node. * * Provides a cross implementation version of document.createTextNode. * * Parameters: * (String) text - The content of the text node. * * Returns: * A new XML DOM text node. */ xmlTextNode: function (text) { return Strophe.xmlGenerator().createTextNode(text); }, /** Function: xmlHtmlNode * Creates an XML DOM html node. * * Parameters: * (String) html - The content of the html node. * * Returns: * A new XML DOM text node. */ xmlHtmlNode: function (html) { var node; //ensure text is escaped if (DOMParser) { var parser = new DOMParser(); node = parser.parseFromString(html, "text/xml"); } else { node = new ActiveXObject("Microsoft.XMLDOM"); node.async="false"; node.loadXML(html); } return node; }, /** Function: getText * Get the concatenation of all text children of an element. * * Parameters: * (XMLElement) elem - A DOM element. * * Returns: * A String with the concatenated text of all text element children. */ getText: function (elem) { if (!elem) { return null; } var str = ""; if (elem.childNodes.length === 0 && elem.nodeType === Strophe.ElementType.TEXT) { str += elem.nodeValue; } for (var i = 0; i < elem.childNodes.length; i++) { if (elem.childNodes[i].nodeType === Strophe.ElementType.TEXT) { str += elem.childNodes[i].nodeValue; } } return Strophe.xmlescape(str); }, /** Function: copyElement * Copy an XML DOM element. * * This function copies a DOM element and all its descendants and returns * the new copy. * * Parameters: * (XMLElement) elem - A DOM element. * * Returns: * A new, copied DOM element tree. */ copyElement: function (elem) { var i, el; if (elem.nodeType === Strophe.ElementType.NORMAL) { el = Strophe.xmlElement(elem.tagName); for (i = 0; i < elem.attributes.length; i++) { el.setAttribute(elem.attributes[i].nodeName, elem.attributes[i].value); } for (i = 0; i < elem.childNodes.length; i++) { el.appendChild(Strophe.copyElement(elem.childNodes[i])); } } else if (elem.nodeType === Strophe.ElementType.TEXT) { el = Strophe.xmlGenerator().createTextNode(elem.nodeValue); } return el; }, /** Function: createHtml * Copy an HTML DOM element into an XML DOM. * * This function copies a DOM element and all its descendants and returns * the new copy. * * Parameters: * (HTMLElement) elem - A DOM element. * * Returns: * A new, copied DOM element tree. */ createHtml: function (elem) { var i, el, j, tag, attribute, value, css, cssAttrs, attr, cssName, cssValue; if (elem.nodeType === Strophe.ElementType.NORMAL) { tag = elem.nodeName.toLowerCase(); // XHTML tags must be lower case. if(Strophe.XHTML.validTag(tag)) { try { el = Strophe.xmlElement(tag); for(i = 0; i < Strophe.XHTML.attributes[tag].length; i++) { attribute = Strophe.XHTML.attributes[tag][i]; value = elem.getAttribute(attribute); if(typeof value === 'undefined' || value === null || value === '' || value === false || value === 0) { continue; } if(attribute === 'style' && typeof value === 'object') { if(typeof value.cssText !== 'undefined') { value = value.cssText; // we're dealing with IE, need to get CSS out } } // filter out invalid css styles if(attribute === 'style') { css = []; cssAttrs = value.split(';'); for(j = 0; j < cssAttrs.length; j++) { attr = cssAttrs[j].split(':'); cssName = attr[0].replace(/^\s*/, "").replace(/\s*$/, "").toLowerCase(); if(Strophe.XHTML.validCSS(cssName)) { cssValue = attr[1].replace(/^\s*/, "").replace(/\s*$/, ""); css.push(cssName + ': ' + cssValue); } } if(css.length > 0) { value = css.join('; '); el.setAttribute(attribute, value); } } else { el.setAttribute(attribute, value); } } for (i = 0; i < elem.childNodes.length; i++) { el.appendChild(Strophe.createHtml(elem.childNodes[i])); } } catch(e) { // invalid elements el = Strophe.xmlTextNode(''); } } else { el = Strophe.xmlGenerator().createDocumentFragment(); for (i = 0; i < elem.childNodes.length; i++) { el.appendChild(Strophe.createHtml(elem.childNodes[i])); } } } else if (elem.nodeType === Strophe.ElementType.FRAGMENT) { el = Strophe.xmlGenerator().createDocumentFragment(); for (i = 0; i < elem.childNodes.length; i++) { el.appendChild(Strophe.createHtml(elem.childNodes[i])); } } else if (elem.nodeType === Strophe.ElementType.TEXT) { el = Strophe.xmlTextNode(elem.nodeValue); } return el; }, /** Function: escapeNode * Escape the node part (also called local part) of a JID. * * Parameters: * (String) node - A node (or local part). * * Returns: * An escaped node (or local part). */ escapeNode: function (node) { if (typeof node !== "string") { return node; } return node.replace(/^\s+|\s+$/g, '') .replace(/\\/g, "\\5c") .replace(/ /g, "\\20") .replace(/\"/g, "\\22") .replace(/\&/g, "\\26") .replace(/\'/g, "\\27") .replace(/\//g, "\\2f") .replace(/:/g, "\\3a") .replace(//g, "\\3e") .replace(/@/g, "\\40"); }, /** Function: unescapeNode * Unescape a node part (also called local part) of a JID. * * Parameters: * (String) node - A node (or local part). * * Returns: * An unescaped node (or local part). */ unescapeNode: function (node) { if (typeof node !== "string") { return node; } return node.replace(/\\20/g, " ") .replace(/\\22/g, '"') .replace(/\\26/g, "&") .replace(/\\27/g, "'") .replace(/\\2f/g, "/") .replace(/\\3a/g, ":") .replace(/\\3c/g, "<") .replace(/\\3e/g, ">") .replace(/\\40/g, "@") .replace(/\\5c/g, "\\"); }, /** Function: getNodeFromJid * Get the node portion of a JID String. * * Parameters: * (String) jid - A JID. * * Returns: * A String containing the node. */ getNodeFromJid: function (jid) { if (jid.indexOf("@") < 0) { return null; } return jid.split("@")[0]; }, /** Function: getDomainFromJid * Get the domain portion of a JID String. * * Parameters: * (String) jid - A JID. * * Returns: * A String containing the domain. */ getDomainFromJid: function (jid) { var bare = Strophe.getBareJidFromJid(jid); if (bare.indexOf("@") < 0) { return bare; } else { var parts = bare.split("@"); parts.splice(0, 1); return parts.join('@'); } }, /** Function: getResourceFromJid * Get the resource portion of a JID String. * * Parameters: * (String) jid - A JID. * * Returns: * A String containing the resource. */ getResourceFromJid: function (jid) { var s = jid.split("/"); if (s.length < 2) { return null; } s.splice(0, 1); return s.join('/'); }, /** Function: getBareJidFromJid * Get the bare JID from a JID String. * * Parameters: * (String) jid - A JID. * * Returns: * A String containing the bare JID. */ getBareJidFromJid: function (jid) { return jid ? jid.split("/")[0] : null; }, /** PrivateFunction: _handleError * _Private_ function that properly logs an error to the console */ _handleError: function (e) { if (typeof e.stack !== "undefined") { Strophe.fatal(e.stack); } if (e.sourceURL) { Strophe.fatal("error: " + this.handler + " " + e.sourceURL + ":" + e.line + " - " + e.name + ": " + e.message); } else if (e.fileName) { Strophe.fatal("error: " + this.handler + " " + e.fileName + ":" + e.lineNumber + " - " + e.name + ": " + e.message); } else { Strophe.fatal("error: " + e.message); } }, /** Function: log * User overrideable logging function. * * This function is called whenever the Strophe library calls any * of the logging functions. The default implementation of this * function does nothing. If client code wishes to handle the logging * messages, it should override this with * > Strophe.log = function (level, msg) { * > (user code here) * > }; * * Please note that data sent and received over the wire is logged * via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput(). * * The different levels and their meanings are * * DEBUG - Messages useful for debugging purposes. * INFO - Informational messages. This is mostly information like * 'disconnect was called' or 'SASL auth succeeded'. * WARN - Warnings about potential problems. This is mostly used * to report transient connection errors like request timeouts. * ERROR - Some error occurred. * FATAL - A non-recoverable fatal error occurred. * * Parameters: * (Integer) level - The log level of the log message. This will * be one of the values in Strophe.LogLevel. * (String) msg - The log message. */ /* jshint ignore:start */ log: function (level, msg) { return; }, /* jshint ignore:end */ /** Function: debug * Log a message at the Strophe.LogLevel.DEBUG level. * * Parameters: * (String) msg - The log message. */ debug: function(msg) { this.log(this.LogLevel.DEBUG, msg); }, /** Function: info * Log a message at the Strophe.LogLevel.INFO level. * * Parameters: * (String) msg - The log message. */ info: function (msg) { this.log(this.LogLevel.INFO, msg); }, /** Function: warn * Log a message at the Strophe.LogLevel.WARN level. * * Parameters: * (String) msg - The log message. */ warn: function (msg) { this.log(this.LogLevel.WARN, msg); }, /** Function: error * Log a message at the Strophe.LogLevel.ERROR level. * * Parameters: * (String) msg - The log message. */ error: function (msg) { this.log(this.LogLevel.ERROR, msg); }, /** Function: fatal * Log a message at the Strophe.LogLevel.FATAL level. * * Parameters: * (String) msg - The log message. */ fatal: function (msg) { this.log(this.LogLevel.FATAL, msg); }, /** Function: serialize * Render a DOM element and all descendants to a String. * * Parameters: * (XMLElement) elem - A DOM element. * * Returns: * The serialized element tree as a String. */ serialize: function (elem) { var result; if (!elem) { return null; } if (typeof(elem.tree) === "function") { elem = elem.tree(); } var nodeName = elem.nodeName; var i, child; if (elem.getAttribute("_realname")) { nodeName = elem.getAttribute("_realname"); } result = "<" + nodeName; for (i = 0; i < elem.attributes.length; i++) { if(elem.attributes[i].nodeName !== "_realname") { result += " " + elem.attributes[i].nodeName + "='" + Strophe.xmlescape(elem.attributes[i].value) + "'"; } } if (elem.childNodes.length > 0) { result += ">"; for (i = 0; i < elem.childNodes.length; i++) { child = elem.childNodes[i]; switch( child.nodeType ){ case Strophe.ElementType.NORMAL: // normal element, so recurse result += Strophe.serialize(child); break; case Strophe.ElementType.TEXT: // text element to escape values result += Strophe.xmlescape(child.nodeValue); break; case Strophe.ElementType.CDATA: // cdata section so don't escape values result += ""; } } result += ""; } else { result += "/>"; } return result; }, /** PrivateVariable: _requestId * _Private_ variable that keeps track of the request ids for * connections. */ _requestId: 0, /** PrivateVariable: Strophe.connectionPlugins * _Private_ variable Used to store plugin names that need * initialization on Strophe.Connection construction. */ _connectionPlugins: {}, /** Function: addConnectionPlugin * Extends the Strophe.Connection object with the given plugin. * * Parameters: * (String) name - The name of the extension. * (Object) ptype - The plugin's prototype. */ addConnectionPlugin: function (name, ptype) { Strophe._connectionPlugins[name] = ptype; } }; /** Class: Strophe.Builder * XML DOM builder. * * This object provides an interface similar to JQuery but for building * DOM elements easily and rapidly. All the functions except for toString() * and tree() return the object, so calls can be chained. Here's an * example using the $iq() builder helper. * > $iq({to: 'you', from: 'me', type: 'get', id: '1'}) * > .c('query', {xmlns: 'strophe:example'}) * > .c('example') * > .toString() * * The above generates this XML fragment * > * > * > * > * > * The corresponding DOM manipulations to get a similar fragment would be * a lot more tedious and probably involve several helper variables. * * Since adding children makes new operations operate on the child, up() * is provided to traverse up the tree. To add two children, do * > builder.c('child1', ...).up().c('child2', ...) * The next operation on the Builder will be relative to the second child. */ /** Constructor: Strophe.Builder * Create a Strophe.Builder object. * * The attributes should be passed in object notation. For example * > var b = new Builder('message', {to: 'you', from: 'me'}); * or * > var b = new Builder('messsage', {'xml:lang': 'en'}); * * Parameters: * (String) name - The name of the root element. * (Object) attrs - The attributes for the root element in object notation. * * Returns: * A new Strophe.Builder. */ Strophe.Builder = function (name, attrs) { // Set correct namespace for jabber:client elements if (name === "presence" || name === "message" || name === "iq") { if (attrs && !attrs.xmlns) { attrs.xmlns = Strophe.NS.CLIENT; } else if (!attrs) { attrs = {xmlns: Strophe.NS.CLIENT}; } } // Holds the tree being built. this.nodeTree = Strophe.xmlElement(name, attrs); // Points to the current operation node. this.node = this.nodeTree; }; Strophe.Builder.prototype = { /** Function: tree * Return the DOM tree. * * This function returns the current DOM tree as an element object. This * is suitable for passing to functions like Strophe.Connection.send(). * * Returns: * The DOM tree as a element object. */ tree: function () { return this.nodeTree; }, /** Function: toString * Serialize the DOM tree to a String. * * This function returns a string serialization of the current DOM * tree. It is often used internally to pass data to a * Strophe.Request object. * * Returns: * The serialized DOM tree in a String. */ toString: function () { return Strophe.serialize(this.nodeTree); }, /** Function: up * Make the current parent element the new current element. * * This function is often used after c() to traverse back up the tree. * For example, to add two children to the same element * > builder.c('child1', {}).up().c('child2', {}); * * Returns: * The Stophe.Builder object. */ up: function () { this.node = this.node.parentNode; return this; }, /** Function: root * Make the root element the new current element. * * When at a deeply nested element in the tree, this function can be used * to jump back to the root of the tree, instead of having to repeatedly * call up(). * * Returns: * The Stophe.Builder object. */ root: function () { this.node = this.nodeTree; return this; }, /** Function: attrs * Add or modify attributes of the current element. * * The attributes should be passed in object notation. This function * does not move the current element pointer. * * Parameters: * (Object) moreattrs - The attributes to add/modify in object notation. * * Returns: * The Strophe.Builder object. */ attrs: function (moreattrs) { for (var k in moreattrs) { if (moreattrs.hasOwnProperty(k)) { if (moreattrs[k] === undefined) { this.node.removeAttribute(k); } else { this.node.setAttribute(k, moreattrs[k]); } } } return this; }, /** Function: c * Add a child to the current element and make it the new current * element. * * This function moves the current element pointer to the child, * unless text is provided. If you need to add another child, it * is necessary to use up() to go back to the parent in the tree. * * Parameters: * (String) name - The name of the child. * (Object) attrs - The attributes of the child in object notation. * (String) text - The text to add to the child. * * Returns: * The Strophe.Builder object. */ c: function (name, attrs, text) { var child = Strophe.xmlElement(name, attrs, text); this.node.appendChild(child); if (typeof text !== "string" && typeof text !=="number") { this.node = child; } return this; }, /** Function: cnode * Add a child to the current element and make it the new current * element. * * This function is the same as c() except that instead of using a * name and an attributes object to create the child it uses an * existing DOM element object. * * Parameters: * (XMLElement) elem - A DOM element. * * Returns: * The Strophe.Builder object. */ cnode: function (elem) { var impNode; var xmlGen = Strophe.xmlGenerator(); try { impNode = (xmlGen.importNode !== undefined); } catch (e) { impNode = false; } var newElem = impNode ? xmlGen.importNode(elem, true) : Strophe.copyElement(elem); this.node.appendChild(newElem); this.node = newElem; return this; }, /** Function: t * Add a child text element. * * This *does not* make the child the new current element since there * are no children of text elements. * * Parameters: * (String) text - The text data to append to the current element. * * Returns: * The Strophe.Builder object. */ t: function (text) { var child = Strophe.xmlTextNode(text); this.node.appendChild(child); return this; }, /** Function: h * Replace current element contents with the HTML passed in. * * This *does not* make the child the new current element * * Parameters: * (String) html - The html to insert as contents of current element. * * Returns: * The Strophe.Builder object. */ h: function (html) { var fragment = document.createElement('body'); // force the browser to try and fix any invalid HTML tags fragment.innerHTML = html; // copy cleaned html into an xml dom var xhtml = Strophe.createHtml(fragment); while(xhtml.childNodes.length > 0) { this.node.appendChild(xhtml.childNodes[0]); } return this; } }; /** PrivateClass: Strophe.Handler * _Private_ helper class for managing stanza handlers. * * A Strophe.Handler encapsulates a user provided callback function to be * executed when matching stanzas are received by the connection. * Handlers can be either one-off or persistant depending on their * return value. Returning true will cause a Handler to remain active, and * returning false will remove the Handler. * * Users will not use Strophe.Handler objects directly, but instead they * will use Strophe.Connection.addHandler() and * Strophe.Connection.deleteHandler(). */ /** PrivateConstructor: Strophe.Handler * Create and initialize a new Strophe.Handler. * * Parameters: * (Function) handler - A function to be executed when the handler is run. * (String) ns - The namespace to match. * (String) name - The element name to match. * (String) type - The element type to match. * (String) id - The element id attribute to match. * (String) from - The element from attribute to match. * (Object) options - Handler options * * Returns: * A new Strophe.Handler object. */ Strophe.Handler = function (handler, ns, name, type, id, from, options) { this.handler = handler; this.ns = ns; this.name = name; this.type = type; this.id = id; this.options = options || {'matchBareFromJid': false, 'ignoreNamespaceFragment': false}; // BBB: Maintain backward compatibility with old `matchBare` option if (this.options.matchBare) { Strophe.warn('The "matchBare" option is deprecated, use "matchBareFromJid" instead.'); this.options.matchBareFromJid = this.options.matchBare; delete this.options.matchBare; } if (this.options.matchBareFromJid) { this.from = from ? Strophe.getBareJidFromJid(from) : null; } else { this.from = from; } // whether the handler is a user handler or a system handler this.user = true; }; Strophe.Handler.prototype = { /** PrivateFunction: getNamespace * Returns the XML namespace attribute on an element. * If `ignoreNamespaceFragment` was passed in for this handler, then the * URL fragment will be stripped. * * Parameters: * (XMLElement) elem - The XML element with the namespace. * * Returns: * The namespace, with optionally the fragment stripped. */ getNamespace: function (elem) { var elNamespace = elem.getAttribute("xmlns"); if (elNamespace && this.options.ignoreNamespaceFragment) { elNamespace = elNamespace.split('#')[0]; } return elNamespace; }, /** PrivateFunction: namespaceMatch * Tests if a stanza matches the namespace set for this Strophe.Handler. * * Parameters: * (XMLElement) elem - The XML element to test. * * Returns: * true if the stanza matches and false otherwise. */ namespaceMatch: function (elem) { var nsMatch = false; if (!this.ns) { return true; } else { var that = this; Strophe.forEachChild(elem, null, function (elem) { if (that.getNamespace(elem) === that.ns) { nsMatch = true; } }); nsMatch = nsMatch || this.getNamespace(elem) === this.ns; } return nsMatch; }, /** PrivateFunction: isMatch * Tests if a stanza matches the Strophe.Handler. * * Parameters: * (XMLElement) elem - The XML element to test. * * Returns: * true if the stanza matches and false otherwise. */ isMatch: function (elem) { var from = elem.getAttribute('from'); if (this.options.matchBareFromJid) { from = Strophe.getBareJidFromJid(from); } var elem_type = elem.getAttribute("type"); if (this.namespaceMatch(elem) && (!this.name || Strophe.isTagEqual(elem, this.name)) && (!this.type || (Array.isArray(this.type) ? this.type.indexOf(elem_type) !== -1 : elem_type === this.type)) && (!this.id || elem.getAttribute("id") === this.id) && (!this.from || from === this.from)) { return true; } return false; }, /** PrivateFunction: run * Run the callback on a matching stanza. * * Parameters: * (XMLElement) elem - The DOM element that triggered the * Strophe.Handler. * * Returns: * A boolean indicating if the handler should remain active. */ run: function (elem) { var result = null; try { result = this.handler(elem); } catch (e) { Strophe._handleError(e); throw e; } return result; }, /** PrivateFunction: toString * Get a String representation of the Strophe.Handler object. * * Returns: * A String. */ toString: function () { return "{Handler: " + this.handler + "(" + this.name + "," + this.id + "," + this.ns + ")}"; } }; /** PrivateClass: Strophe.TimedHandler * _Private_ helper class for managing timed handlers. * * A Strophe.TimedHandler encapsulates a user provided callback that * should be called after a certain period of time or at regular * intervals. The return value of the callback determines whether the * Strophe.TimedHandler will continue to fire. * * Users will not use Strophe.TimedHandler objects directly, but instead * they will use Strophe.Connection.addTimedHandler() and * Strophe.Connection.deleteTimedHandler(). */ /** PrivateConstructor: Strophe.TimedHandler * Create and initialize a new Strophe.TimedHandler object. * * Parameters: * (Integer) period - The number of milliseconds to wait before the * handler is called. * (Function) handler - The callback to run when the handler fires. This * function should take no arguments. * * Returns: * A new Strophe.TimedHandler object. */ Strophe.TimedHandler = function (period, handler) { this.period = period; this.handler = handler; this.lastCalled = new Date().getTime(); this.user = true; }; Strophe.TimedHandler.prototype = { /** PrivateFunction: run * Run the callback for the Strophe.TimedHandler. * * Returns: * true if the Strophe.TimedHandler should be called again, and false * otherwise. */ run: function () { this.lastCalled = new Date().getTime(); return this.handler(); }, /** PrivateFunction: reset * Reset the last called time for the Strophe.TimedHandler. */ reset: function () { this.lastCalled = new Date().getTime(); }, /** PrivateFunction: toString * Get a string representation of the Strophe.TimedHandler object. * * Returns: * The string representation. */ toString: function () { return "{TimedHandler: " + this.handler + "(" + this.period +")}"; } }; /** Class: Strophe.Connection * XMPP Connection manager. * * This class is the main part of Strophe. It manages a BOSH or websocket * connection to an XMPP server and dispatches events to the user callbacks * as data arrives. It supports SASL PLAIN, SASL DIGEST-MD5, SASL SCRAM-SHA1 * and legacy authentication. * * After creating a Strophe.Connection object, the user will typically * call connect() with a user supplied callback to handle connection level * events like authentication failure, disconnection, or connection * complete. * * The user will also have several event handlers defined by using * addHandler() and addTimedHandler(). These will allow the user code to * respond to interesting stanzas or do something periodically with the * connection. These handlers will be active once authentication is * finished. * * To send data to the connection, use send(). */ /** Constructor: Strophe.Connection * Create and initialize a Strophe.Connection object. * * The transport-protocol for this connection will be chosen automatically * based on the given service parameter. URLs starting with "ws://" or * "wss://" will use WebSockets, URLs starting with "http://", "https://" * or without a protocol will use BOSH. * * To make Strophe connect to the current host you can leave out the protocol * and host part and just pass the path, e.g. * * > var conn = new Strophe.Connection("/http-bind/"); * * Options common to both Websocket and BOSH: * ------------------------------------------ * * cookies: * * The *cookies* option allows you to pass in cookies to be added to the * document. These cookies will then be included in the BOSH XMLHttpRequest * or in the websocket connection. * * The passed in value must be a map of cookie names and string values. * * > { "myCookie": { * > "value": "1234", * > "domain": ".example.org", * > "path": "/", * > "expires": expirationDate * > } * > } * * Note that cookies can't be set in this way for other domains (i.e. cross-domain). * Those cookies need to be set under those domains, for example they can be * set server-side by making a XHR call to that domain to ask it to set any * necessary cookies. * * mechanisms: * * The *mechanisms* option allows you to specify the SASL mechanisms that this * instance of Strophe.Connection (and therefore your XMPP client) will * support. * * The value must be an array of objects with Strophe.SASLMechanism * prototypes. * * If nothing is specified, then the following mechanisms (and their * priorities) are registered: * * OAUTHBEARER - 60 * SCRAM-SHA1 - 50 * DIGEST-MD5 - 40 * PLAIN - 30 * ANONYMOUS - 20 * EXTERNAL - 10 * * WebSocket options: * ------------------ * * If you want to connect to the current host with a WebSocket connection you * can tell Strophe to use WebSockets through a "protocol" attribute in the * optional options parameter. Valid values are "ws" for WebSocket and "wss" * for Secure WebSocket. * So to connect to "wss://CURRENT_HOSTNAME/xmpp-websocket" you would call * * > var conn = new Strophe.Connection("/xmpp-websocket/", {protocol: "wss"}); * * Note that relative URLs _NOT_ starting with a "/" will also include the path * of the current site. * * Also because downgrading security is not permitted by browsers, when using * relative URLs both BOSH and WebSocket connections will use their secure * variants if the current connection to the site is also secure (https). * * BOSH options: * ------------- * * By adding "sync" to the options, you can control if requests will * be made synchronously or not. The default behaviour is asynchronous. * If you want to make requests synchronous, make "sync" evaluate to true. * > var conn = new Strophe.Connection("/http-bind/", {sync: true}); * * You can also toggle this on an already established connection. * > conn.options.sync = true; * * The *customHeaders* option can be used to provide custom HTTP headers to be * included in the XMLHttpRequests made. * * The *keepalive* option can be used to instruct Strophe to maintain the * current BOSH session across interruptions such as webpage reloads. * * It will do this by caching the sessions tokens in sessionStorage, and when * "restore" is called it will check whether there are cached tokens with * which it can resume an existing session. * * The *withCredentials* option should receive a Boolean value and is used to * indicate wether cookies should be included in ajax requests (by default * they're not). * Set this value to true if you are connecting to a BOSH service * and for some reason need to send cookies to it. * In order for this to work cross-domain, the server must also enable * credentials by setting the Access-Control-Allow-Credentials response header * to "true". For most usecases however this setting should be false (which * is the default). * Additionally, when using Access-Control-Allow-Credentials, the * Access-Control-Allow-Origin header can't be set to the wildcard "*", but * instead must be restricted to actual domains. * * The *contentType* option can be set to change the default Content-Type * of "text/xml; charset=utf-8", which can be useful to reduce the amount of * CORS preflight requests that are sent to the server. * * Parameters: * (String) service - The BOSH or WebSocket service URL. * (Object) options - A hash of configuration options * * Returns: * A new Strophe.Connection object. */ Strophe.Connection = function (service, options) { // The service URL this.service = service; // Configuration options this.options = options || {}; var proto = this.options.protocol || ""; // Select protocal based on service or options if (service.indexOf("ws:") === 0 || service.indexOf("wss:") === 0 || proto.indexOf("ws") === 0) { this._proto = new Strophe.Websocket(this); } else { this._proto = new Strophe.Bosh(this); } /* The connected JID. */ this.jid = ""; /* the JIDs domain */ this.domain = null; /* stream:features */ this.features = null; // SASL this._sasl_data = {}; this.do_session = false; this.do_bind = false; // handler lists this.timedHandlers = []; this.handlers = []; this.removeTimeds = []; this.removeHandlers = []; this.addTimeds = []; this.addHandlers = []; this.protocolErrorHandlers = { 'HTTP': {}, 'websocket': {} }; this._idleTimeout = null; this._disconnectTimeout = null; this.authenticated = false; this.connected = false; this.disconnecting = false; this.do_authentication = true; this.paused = false; this.restored = false; this._data = []; this._uniqueId = 0; this._sasl_success_handler = null; this._sasl_failure_handler = null; this._sasl_challenge_handler = null; // Max retries before disconnecting this.maxRetries = 5; // Call onIdle callback every 1/10th of a second // XXX: setTimeout should be called only with function expressions (23974bc1) this._idleTimeout = setTimeout(function() { this._onIdle(); }.bind(this), 100); utils.addCookies(this.options.cookies); this.registerSASLMechanisms(this.options.mechanisms); // initialize plugins for (var k in Strophe._connectionPlugins) { if (Strophe._connectionPlugins.hasOwnProperty(k)) { var ptype = Strophe._connectionPlugins[k]; // jslint complaints about the below line, but this is fine var F = function () {}; // jshint ignore:line F.prototype = ptype; this[k] = new F(); this[k].init(this); } } }; Strophe.Connection.prototype = { /** Function: reset * Reset the connection. * * This function should be called after a connection is disconnected * before that connection is reused. */ reset: function () { this._proto._reset(); // SASL this.do_session = false; this.do_bind = false; // handler lists this.timedHandlers = []; this.handlers = []; this.removeTimeds = []; this.removeHandlers = []; this.addTimeds = []; this.addHandlers = []; this.authenticated = false; this.connected = false; this.disconnecting = false; this.restored = false; this._data = []; this._requests = []; this._uniqueId = 0; }, /** Function: pause * Pause the request manager. * * This will prevent Strophe from sending any more requests to the * server. This is very useful for temporarily pausing * BOSH-Connections while a lot of send() calls are happening quickly. * This causes Strophe to send the data in a single request, saving * many request trips. */ pause: function () { this.paused = true; }, /** Function: resume * Resume the request manager. * * This resumes after pause() has been called. */ resume: function () { this.paused = false; }, /** Function: getUniqueId * Generate a unique ID for use in elements. * * All stanzas are required to have unique id attributes. This * function makes creating these easy. Each connection instance has * a counter which starts from zero, and the value of this counter * plus a colon followed by the suffix becomes the unique id. If no * suffix is supplied, the counter is used as the unique id. * * Suffixes are used to make debugging easier when reading the stream * data, and their use is recommended. The counter resets to 0 for * every new connection for the same reason. For connections to the * same server that authenticate the same way, all the ids should be * the same, which makes it easy to see changes. This is useful for * automated testing as well. * * Parameters: * (String) suffix - A optional suffix to append to the id. * * Returns: * A unique string to be used for the id attribute. */ getUniqueId: function(suffix) { var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c === 'x' ? r : r & 0x3 | 0x8; return v.toString(16); }); if (typeof(suffix) === "string" || typeof(suffix) === "number") { return uuid + ":" + suffix; } else { return uuid + ""; } }, /** Function: addProtocolErrorHandler * Register a handler function for when a protocol (websocker or HTTP) * error occurs. * * NOTE: Currently only HTTP errors for BOSH requests are handled. * Patches that handle websocket errors would be very welcome. * * Parameters: * (String) protocol - 'HTTP' or 'websocket' * (Integer) status_code - Error status code (e.g 500, 400 or 404) * (Function) callback - Function that will fire on Http error * * Example: * function onError(err_code){ * //do stuff * } * * var conn = Strophe.connect('http://example.com/http-bind'); * conn.addProtocolErrorHandler('HTTP', 500, onError); * // Triggers HTTP 500 error and onError handler will be called * conn.connect('user_jid@incorrect_jabber_host', 'secret', onConnect); */ addProtocolErrorHandler: function(protocol, status_code, callback){ this.protocolErrorHandlers[protocol][status_code] = callback; }, /** Function: connect * Starts the connection process. * * As the connection process proceeds, the user supplied callback will * be triggered multiple times with status updates. The callback * should take two arguments - the status code and the error condition. * * The status code will be one of the values in the Strophe.Status * constants. The error condition will be one of the conditions * defined in RFC 3920 or the condition 'strophe-parsererror'. * * The Parameters _wait_, _hold_ and _route_ are optional and only relevant * for BOSH connections. Please see XEP 124 for a more detailed explanation * of the optional parameters. * * Parameters: * (String) jid - The user's JID. This may be a bare JID, * or a full JID. If a node is not supplied, SASL OAUTHBEARER or * SASL ANONYMOUS authentication will be attempted (OAUTHBEARER will * process the provided password value as an access token). * (String) pass - The user's password. * (Function) callback - The connect callback function. * (Integer) wait - The optional HTTPBIND wait value. This is the * time the server will wait before returning an empty result for * a request. The default setting of 60 seconds is recommended. * (Integer) hold - The optional HTTPBIND hold value. This is the * number of connections the server will hold at one time. This * should almost always be set to 1 (the default). * (String) route - The optional route value. * (String) authcid - The optional alternative authentication identity * (username) if intending to impersonate another user. * When using the SASL-EXTERNAL authentication mechanism, for example * with client certificates, then the authcid value is used to * determine whether an authorization JID (authzid) should be sent to * the server. The authzid should not be sent to the server if the * authzid and authcid are the same. So to prevent it from being sent * (for example when the JID is already contained in the client * certificate), set authcid to that same JID. See XEP-178 for more * details. */ connect: function (jid, pass, callback, wait, hold, route, authcid) { this.jid = jid; /** Variable: authzid * Authorization identity. */ this.authzid = Strophe.getBareJidFromJid(this.jid); /** Variable: authcid * Authentication identity (User name). */ this.authcid = authcid || Strophe.getNodeFromJid(this.jid); /** Variable: pass * Authentication identity (User password). */ this.pass = pass; /** Variable: servtype * Digest MD5 compatibility. */ this.servtype = "xmpp"; this.connect_callback = callback; this.disconnecting = false; this.connected = false; this.authenticated = false; this.restored = false; // parse jid for domain this.domain = Strophe.getDomainFromJid(this.jid); this._changeConnectStatus(Strophe.Status.CONNECTING, null); this._proto._connect(wait, hold, route); }, /** Function: attach * Attach to an already created and authenticated BOSH session. * * This function is provided to allow Strophe to attach to BOSH * sessions which have been created externally, perhaps by a Web * application. This is often used to support auto-login type features * without putting user credentials into the page. * * Parameters: * (String) jid - The full JID that is bound by the session. * (String) sid - The SID of the BOSH session. * (String) rid - The current RID of the BOSH session. This RID * will be used by the next request. * (Function) callback The connect callback function. * (Integer) wait - The optional HTTPBIND wait value. This is the * time the server will wait before returning an empty result for * a request. The default setting of 60 seconds is recommended. * Other settings will require tweaks to the Strophe.TIMEOUT value. * (Integer) hold - The optional HTTPBIND hold value. This is the * number of connections the server will hold at one time. This * should almost always be set to 1 (the default). * (Integer) wind - The optional HTTBIND window value. This is the * allowed range of request ids that are valid. The default is 5. */ attach: function (jid, sid, rid, callback, wait, hold, wind) { if (this._proto instanceof Strophe.Bosh) { this._proto._attach(jid, sid, rid, callback, wait, hold, wind); } else { throw { name: 'StropheSessionError', message: 'The "attach" method can only be used with a BOSH connection.' }; } }, /** Function: restore * Attempt to restore a cached BOSH session. * * This function is only useful in conjunction with providing the * "keepalive":true option when instantiating a new Strophe.Connection. * * When "keepalive" is set to true, Strophe will cache the BOSH tokens * RID (Request ID) and SID (Session ID) and then when this function is * called, it will attempt to restore the session from those cached * tokens. * * This function must therefore be called instead of connect or attach. * * For an example on how to use it, please see examples/restore.js * * Parameters: * (String) jid - The user's JID. This may be a bare JID or a full JID. * (Function) callback - The connect callback function. * (Integer) wait - The optional HTTPBIND wait value. This is the * time the server will wait before returning an empty result for * a request. The default setting of 60 seconds is recommended. * (Integer) hold - The optional HTTPBIND hold value. This is the * number of connections the server will hold at one time. This * should almost always be set to 1 (the default). * (Integer) wind - The optional HTTBIND window value. This is the * allowed range of request ids that are valid. The default is 5. */ restore: function (jid, callback, wait, hold, wind) { if (this._sessionCachingSupported()) { this._proto._restore(jid, callback, wait, hold, wind); } else { throw { name: 'StropheSessionError', message: 'The "restore" method can only be used with a BOSH connection.' }; } }, /** PrivateFunction: _sessionCachingSupported * Checks whether sessionStorage and JSON are supported and whether we're * using BOSH. */ _sessionCachingSupported: function () { if (this._proto instanceof Strophe.Bosh) { if (!JSON) { return false; } try { sessionStorage.setItem('_strophe_', '_strophe_'); sessionStorage.removeItem('_strophe_'); } catch (e) { return false; } return true; } return false; }, /** Function: xmlInput * User overrideable function that receives XML data coming into the * connection. * * The default function does nothing. User code can override this with * > Strophe.Connection.xmlInput = function (elem) { * > (user code) * > }; * * Due to limitations of current Browsers' XML-Parsers the opening and closing * tag for WebSocket-Connoctions will be passed as selfclosing here. * * BOSH-Connections will have all stanzas wrapped in a tag. See * if you want to strip this tag. * * Parameters: * (XMLElement) elem - The XML data received by the connection. */ /* jshint unused:false */ xmlInput: function (elem) { return; }, /* jshint unused:true */ /** Function: xmlOutput * User overrideable function that receives XML data sent to the * connection. * * The default function does nothing. User code can override this with * > Strophe.Connection.xmlOutput = function (elem) { * > (user code) * > }; * * Due to limitations of current Browsers' XML-Parsers the opening and closing * tag for WebSocket-Connoctions will be passed as selfclosing here. * * BOSH-Connections will have all stanzas wrapped in a tag. See * if you want to strip this tag. * * Parameters: * (XMLElement) elem - The XMLdata sent by the connection. */ /* jshint unused:false */ xmlOutput: function (elem) { return; }, /* jshint unused:true */ /** Function: rawInput * User overrideable function that receives raw data coming into the * connection. * * The default function does nothing. User code can override this with * > Strophe.Connection.rawInput = function (data) { * > (user code) * > }; * * Parameters: * (String) data - The data received by the connection. */ /* jshint unused:false */ rawInput: function (data) { return; }, /* jshint unused:true */ /** Function: rawOutput * User overrideable function that receives raw data sent to the * connection. * * The default function does nothing. User code can override this with * > Strophe.Connection.rawOutput = function (data) { * > (user code) * > }; * * Parameters: * (String) data - The data sent by the connection. */ /* jshint unused:false */ rawOutput: function (data) { return; }, /* jshint unused:true */ /** Function: nextValidRid * User overrideable function that receives the new valid rid. * * The default function does nothing. User code can override this with * > Strophe.Connection.nextValidRid = function (rid) { * > (user code) * > }; * * Parameters: * (Number) rid - The next valid rid */ /* jshint unused:false */ nextValidRid: function (rid) { return; }, /* jshint unused:true */ /** Function: send * Send a stanza. * * This function is called to push data onto the send queue to * go out over the wire. Whenever a request is sent to the BOSH * server, all pending data is sent and the queue is flushed. * * Parameters: * (XMLElement | * [XMLElement] | * Strophe.Builder) elem - The stanza to send. */ send: function (elem) { if (elem === null) { return ; } if (typeof(elem.sort) === "function") { for (var i = 0; i < elem.length; i++) { this._queueData(elem[i]); } } else if (typeof(elem.tree) === "function") { this._queueData(elem.tree()); } else { this._queueData(elem); } this._proto._send(); }, /** Function: flush * Immediately send any pending outgoing data. * * Normally send() queues outgoing data until the next idle period * (100ms), which optimizes network use in the common cases when * several send()s are called in succession. flush() can be used to * immediately send all pending data. */ flush: function () { // cancel the pending idle period and run the idle function // immediately clearTimeout(this._idleTimeout); this._onIdle(); }, /** Function: sendPresence * Helper function to send presence stanzas. The main benefit is for * sending presence stanzas for which you expect a responding presence * stanza with the same id (for example when leaving a chat room). * * Parameters: * (XMLElement) elem - The stanza to send. * (Function) callback - The callback function for a successful request. * (Function) errback - The callback function for a failed or timed * out request. On timeout, the stanza will be null. * (Integer) timeout - The time specified in milliseconds for a * timeout to occur. * * Returns: * The id used to send the presence. */ sendPresence: function(elem, callback, errback, timeout) { var timeoutHandler = null; var that = this; if (typeof(elem.tree) === "function") { elem = elem.tree(); } var id = elem.getAttribute('id'); if (!id) { // inject id if not found id = this.getUniqueId("sendPresence"); elem.setAttribute("id", id); } if (typeof callback === "function" || typeof errback === "function") { var handler = this.addHandler(function (stanza) { // remove timeout handler if there is one if (timeoutHandler) { that.deleteTimedHandler(timeoutHandler); } var type = stanza.getAttribute('type'); if (type === 'error') { if (errback) { errback(stanza); } } else if (callback) { callback(stanza); } }, null, 'presence', null, id); // if timeout specified, set up a timeout handler. if (timeout) { timeoutHandler = this.addTimedHandler(timeout, function () { // get rid of normal handler that.deleteHandler(handler); // call errback on timeout with null stanza if (errback) { errback(null); } return false; }); } } this.send(elem); return id; }, /** Function: sendIQ * Helper function to send IQ stanzas. * * Parameters: * (XMLElement) elem - The stanza to send. * (Function) callback - The callback function for a successful request. * (Function) errback - The callback function for a failed or timed * out request. On timeout, the stanza will be null. * (Integer) timeout - The time specified in milliseconds for a * timeout to occur. * * Returns: * The id used to send the IQ. */ sendIQ: function(elem, callback, errback, timeout) { var timeoutHandler = null; var that = this; if (typeof(elem.tree) === "function") { elem = elem.tree(); } var id = elem.getAttribute('id'); if (!id) { // inject id if not found id = this.getUniqueId("sendIQ"); elem.setAttribute("id", id); } if (typeof callback === "function" || typeof errback === "function") { var handler = this.addHandler(function (stanza) { // remove timeout handler if there is one if (timeoutHandler) { that.deleteTimedHandler(timeoutHandler); } var iqtype = stanza.getAttribute('type'); if (iqtype === 'result') { if (callback) { callback(stanza); } } else if (iqtype === 'error') { if (errback) { errback(stanza); } } else { throw { name: "StropheError", message: "Got bad IQ type of " + iqtype }; } }, null, 'iq', ['error', 'result'], id); // if timeout specified, set up a timeout handler. if (timeout) { timeoutHandler = this.addTimedHandler(timeout, function () { // get rid of normal handler that.deleteHandler(handler); // call errback on timeout with null stanza if (errback) { errback(null); } return false; }); } } this.send(elem); return id; }, /** PrivateFunction: _queueData * Queue outgoing data for later sending. Also ensures that the data * is a DOMElement. */ _queueData: function (element) { if (element === null || !element.tagName || !element.childNodes) { throw { name: "StropheError", message: "Cannot queue non-DOMElement." }; } this._data.push(element); }, /** PrivateFunction: _sendRestart * Send an xmpp:restart stanza. */ _sendRestart: function () { this._data.push("restart"); this._proto._sendRestart(); // XXX: setTimeout should be called only with function expressions (23974bc1) this._idleTimeout = setTimeout(function() { this._onIdle(); }.bind(this), 100); }, /** Function: addTimedHandler * Add a timed handler to the connection. * * This function adds a timed handler. The provided handler will * be called every period milliseconds until it returns false, * the connection is terminated, or the handler is removed. Handlers * that wish to continue being invoked should return true. * * Because of method binding it is necessary to save the result of * this function if you wish to remove a handler with * deleteTimedHandler(). * * Note that user handlers are not active until authentication is * successful. * * Parameters: * (Integer) period - The period of the handler. * (Function) handler - The callback function. * * Returns: * A reference to the handler that can be used to remove it. */ addTimedHandler: function (period, handler) { var thand = new Strophe.TimedHandler(period, handler); this.addTimeds.push(thand); return thand; }, /** Function: deleteTimedHandler * Delete a timed handler for a connection. * * This function removes a timed handler from the connection. The * handRef parameter is *not* the function passed to addTimedHandler(), * but is the reference returned from addTimedHandler(). * * Parameters: * (Strophe.TimedHandler) handRef - The handler reference. */ deleteTimedHandler: function (handRef) { // this must be done in the Idle loop so that we don't change // the handlers during iteration this.removeTimeds.push(handRef); }, /** Function: addHandler * Add a stanza handler for the connection. * * This function adds a stanza handler to the connection. The * handler callback will be called for any stanza that matches * the parameters. Note that if multiple parameters are supplied, * they must all match for the handler to be invoked. * * The handler will receive the stanza that triggered it as its argument. * *The handler should return true if it is to be invoked again; * returning false will remove the handler after it returns.* * * As a convenience, the ns parameters applies to the top level element * and also any of its immediate children. This is primarily to make * matching /iq/query elements easy. * * Options * ~~~~~~~ * With the options argument, you can specify boolean flags that affect how * matches are being done. * * Currently two flags exist: * * - matchBareFromJid: * When set to true, the from parameter and the * from attribute on the stanza will be matched as bare JIDs instead * of full JIDs. To use this, pass {matchBareFromJid: true} as the * value of options. The default value for matchBareFromJid is false. * * - ignoreNamespaceFragment: * When set to true, a fragment specified on the stanza's namespace * URL will be ignored when it's matched with the one configured for * the handler. * * This means that if you register like this: * > connection.addHandler( * > handler, * > 'http://jabber.org/protocol/muc', * > null, null, null, null, * > {'ignoreNamespaceFragment': true} * > ); * * Then a stanza with XML namespace of * 'http://jabber.org/protocol/muc#user' will also be matched. If * 'ignoreNamespaceFragment' is false, then only stanzas with * 'http://jabber.org/protocol/muc' will be matched. * * Deleting the handler * ~~~~~~~~~~~~~~~~~~~~ * The return value should be saved if you wish to remove the handler * with deleteHandler(). * * Parameters: * (Function) handler - The user callback. * (String) ns - The namespace to match. * (String) name - The stanza name to match. * (String|Array) type - The stanza type (or types if an array) to match. * (String) id - The stanza id attribute to match. * (String) from - The stanza from attribute to match. * (String) options - The handler options * * Returns: * A reference to the handler that can be used to remove it. */ addHandler: function (handler, ns, name, type, id, from, options) { var hand = new Strophe.Handler(handler, ns, name, type, id, from, options); this.addHandlers.push(hand); return hand; }, /** Function: deleteHandler * Delete a stanza handler for a connection. * * This function removes a stanza handler from the connection. The * handRef parameter is *not* the function passed to addHandler(), * but is the reference returned from addHandler(). * * Parameters: * (Strophe.Handler) handRef - The handler reference. */ deleteHandler: function (handRef) { // this must be done in the Idle loop so that we don't change // the handlers during iteration this.removeHandlers.push(handRef); // If a handler is being deleted while it is being added, // prevent it from getting added var i = this.addHandlers.indexOf(handRef); if (i >= 0) { this.addHandlers.splice(i, 1); } }, /** Function: registerSASLMechanisms * * Register the SASL mechanisms which will be supported by this instance of * Strophe.Connection (i.e. which this XMPP client will support). * * Parameters: * (Array) mechanisms - Array of objects with Strophe.SASLMechanism prototypes * */ registerSASLMechanisms: function (mechanisms) { this.mechanisms = {}; mechanisms = mechanisms || [ Strophe.SASLAnonymous, Strophe.SASLExternal, Strophe.SASLMD5, Strophe.SASLOAuthBearer, Strophe.SASLPlain, Strophe.SASLSHA1 ]; mechanisms.forEach(this.registerSASLMechanism.bind(this)); }, /** Function: registerSASLMechanism * * Register a single SASL mechanism, to be supported by this client. * * Parameters: * (Object) mechanism - Object with a Strophe.SASLMechanism prototype * */ registerSASLMechanism: function (mechanism) { this.mechanisms[mechanism.prototype.name] = mechanism; }, /** Function: disconnect * Start the graceful disconnection process. * * This function starts the disconnection process. This process starts * by sending unavailable presence and sending BOSH body of type * terminate. A timeout handler makes sure that disconnection happens * even if the BOSH server does not respond. * If the Connection object isn't connected, at least tries to abort all pending requests * so the connection object won't generate successful requests (which were already opened). * * The user supplied connection callback will be notified of the * progress as this process happens. * * Parameters: * (String) reason - The reason the disconnect is occuring. */ disconnect: function (reason) { this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason); Strophe.info("Disconnect was called because: " + reason); if (this.connected) { var pres = false; this.disconnecting = true; if (this.authenticated) { pres = $pres({ xmlns: Strophe.NS.CLIENT, type: 'unavailable' }); } // setup timeout handler this._disconnectTimeout = this._addSysTimedHandler( 3000, this._onDisconnectTimeout.bind(this)); this._proto._disconnect(pres); } else { Strophe.info("Disconnect was called before Strophe connected to the server"); this._proto._abortAllRequests(); this._doDisconnect(); } }, /** PrivateFunction: _changeConnectStatus * _Private_ helper function that makes sure plugins and the user's * callback are notified of connection status changes. * * Parameters: * (Integer) status - the new connection status, one of the values * in Strophe.Status * (String) condition - the error condition or null */ _changeConnectStatus: function (status, condition) { // notify all plugins listening for status changes for (var k in Strophe._connectionPlugins) { if (Strophe._connectionPlugins.hasOwnProperty(k)) { var plugin = this[k]; if (plugin.statusChanged) { try { plugin.statusChanged(status, condition); } catch (err) { Strophe.error("" + k + " plugin caused an exception " + "changing status: " + err); } } } } // notify the user's callback if (this.connect_callback) { try { this.connect_callback(status, condition); } catch (e) { Strophe._handleError(e); Strophe.error( "User connection callback caused an "+"exception: "+e); } } }, /** PrivateFunction: _doDisconnect * _Private_ function to disconnect. * * This is the last piece of the disconnection logic. This resets the * connection and alerts the user's connection callback. */ _doDisconnect: function (condition) { if (typeof this._idleTimeout === "number") { clearTimeout(this._idleTimeout); } // Cancel Disconnect Timeout if (this._disconnectTimeout !== null) { this.deleteTimedHandler(this._disconnectTimeout); this._disconnectTimeout = null; } Strophe.info("_doDisconnect was called"); this._proto._doDisconnect(); this.authenticated = false; this.disconnecting = false; this.restored = false; // delete handlers this.handlers = []; this.timedHandlers = []; this.removeTimeds = []; this.removeHandlers = []; this.addTimeds = []; this.addHandlers = []; // tell the parent we disconnected this._changeConnectStatus(Strophe.Status.DISCONNECTED, condition); this.connected = false; }, /** PrivateFunction: _dataRecv * _Private_ handler to processes incoming data from the the connection. * * Except for _connect_cb handling the initial connection request, * this function handles the incoming data for all requests. This * function also fires stanza handlers that match each incoming * stanza. * * Parameters: * (Strophe.Request) req - The request that has data ready. * (string) req - The stanza a raw string (optiona). */ _dataRecv: function (req, raw) { Strophe.info("_dataRecv called"); var elem = this._proto._reqToData(req); if (elem === null) { return; } if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) { if (elem.nodeName === this._proto.strip && elem.childNodes.length) { this.xmlInput(elem.childNodes[0]); } else { this.xmlInput(elem); } } if (this.rawInput !== Strophe.Connection.prototype.rawInput) { if (raw) { this.rawInput(raw); } else { this.rawInput(Strophe.serialize(elem)); } } // remove handlers scheduled for deletion var i, hand; while (this.removeHandlers.length > 0) { hand = this.removeHandlers.pop(); i = this.handlers.indexOf(hand); if (i >= 0) { this.handlers.splice(i, 1); } } // add handlers scheduled for addition while (this.addHandlers.length > 0) { this.handlers.push(this.addHandlers.pop()); } // handle graceful disconnect if (this.disconnecting && this._proto._emptyQueue()) { this._doDisconnect(); return; } var type = elem.getAttribute("type"); var cond, conflict; if (type !== null && type === "terminate") { // Don't process stanzas that come in after disconnect if (this.disconnecting) { return; } // an error occurred cond = elem.getAttribute("condition"); conflict = elem.getElementsByTagName("conflict"); if (cond !== null) { if (cond === "remote-stream-error" && conflict.length > 0) { cond = "conflict"; } this._changeConnectStatus(Strophe.Status.CONNFAIL, cond); } else { this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown"); } this._doDisconnect(cond); return; } // send each incoming stanza through the handler chain var that = this; Strophe.forEachChild(elem, null, function (child) { var i, newList; // process handlers newList = that.handlers; that.handlers = []; for (i = 0; i < newList.length; i++) { var hand = newList[i]; // encapsulate 'handler.run' not to lose the whole handler list if // one of the handlers throws an exception try { if (hand.isMatch(child) && (that.authenticated || !hand.user)) { if (hand.run(child)) { that.handlers.push(hand); } } else { that.handlers.push(hand); } } catch(e) { // if the handler throws an exception, we consider it as false Strophe.warn('Removing Strophe handlers due to uncaught exception: '+e.message); } } }); }, /** Attribute: mechanisms * SASL Mechanisms available for Connection. */ mechanisms: {}, /** PrivateFunction: _connect_cb * _Private_ handler for initial connection request. * * This handler is used to process the initial connection request * response from the BOSH server. It is used to set up authentication * handlers and start the authentication process. * * SASL authentication will be attempted if available, otherwise * the code will fall back to legacy authentication. * * Parameters: * (Strophe.Request) req - The current request. * (Function) _callback - low level (xmpp) connect callback function. * Useful for plugins with their own xmpp connect callback (when their) * want to do something special). */ _connect_cb: function (req, _callback, raw) { Strophe.info("_connect_cb was called"); this.connected = true; var bodyWrap; try { bodyWrap = this._proto._reqToData(req); } catch (e) { if (e !== "badformat") { throw e; } this._changeConnectStatus(Strophe.Status.CONNFAIL, 'bad-format'); this._doDisconnect('bad-format'); } if (!bodyWrap) { return; } if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) { if (bodyWrap.nodeName === this._proto.strip && bodyWrap.childNodes.length) { this.xmlInput(bodyWrap.childNodes[0]); } else { this.xmlInput(bodyWrap); } } if (this.rawInput !== Strophe.Connection.prototype.rawInput) { if (raw) { this.rawInput(raw); } else { this.rawInput(Strophe.serialize(bodyWrap)); } } var conncheck = this._proto._connect_cb(bodyWrap); if (conncheck === Strophe.Status.CONNFAIL) { return; } // Check for the stream:features tag var hasFeatures; if (bodyWrap.getElementsByTagNameNS) { hasFeatures = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, "features").length > 0; } else { hasFeatures = bodyWrap.getElementsByTagName("stream:features").length > 0 || bodyWrap.getElementsByTagName("features").length > 0; } if (!hasFeatures) { this._proto._no_auth_received(_callback); return; } var matched = [], i, mech; var mechanisms = bodyWrap.getElementsByTagName("mechanism"); if (mechanisms.length > 0) { for (i = 0; i < mechanisms.length; i++) { mech = Strophe.getText(mechanisms[i]); if (this.mechanisms[mech]) matched.push(this.mechanisms[mech]); } } if (matched.length === 0) { if (bodyWrap.getElementsByTagName("auth").length === 0) { // There are no matching SASL mechanisms and also no legacy // auth available. this._proto._no_auth_received(_callback); return; } } if (this.do_authentication !== false) { this.authenticate(matched); } }, /** Function: sortMechanismsByPriority * * Sorts an array of objects with prototype SASLMechanism according to * their priorities. * * Parameters: * (Array) mechanisms - Array of SASL mechanisms. * */ sortMechanismsByPriority: function (mechanisms) { // Sorting mechanisms according to priority. var i, j, higher, swap; for (i = 0; i < mechanisms.length - 1; ++i) { higher = i; for (j = i + 1; j < mechanisms.length; ++j) { if (mechanisms[j].prototype.priority > mechanisms[higher].prototype.priority) { higher = j; } } if (higher !== i) { swap = mechanisms[i]; mechanisms[i] = mechanisms[higher]; mechanisms[higher] = swap; } } return mechanisms; }, /** PrivateFunction: _attemptSASLAuth * * Iterate through an array of SASL mechanisms and attempt authentication * with the highest priority (enabled) mechanism. * * Parameters: * (Array) mechanisms - Array of SASL mechanisms. * * Returns: * (Boolean) mechanism_found - true or false, depending on whether a * valid SASL mechanism was found with which authentication could be * started. */ _attemptSASLAuth: function (mechanisms) { mechanisms = this.sortMechanismsByPriority(mechanisms || []); var i = 0, mechanism_found = false; for (i = 0; i < mechanisms.length; ++i) { if (!mechanisms[i].prototype.test(this)) { continue; } this._sasl_success_handler = this._addSysHandler( this._sasl_success_cb.bind(this), null, "success", null, null); this._sasl_failure_handler = this._addSysHandler( this._sasl_failure_cb.bind(this), null, "failure", null, null); this._sasl_challenge_handler = this._addSysHandler( this._sasl_challenge_cb.bind(this), null, "challenge", null, null); this._sasl_mechanism = new mechanisms[i](); this._sasl_mechanism.onStart(this); var request_auth_exchange = $build("auth", { xmlns: Strophe.NS.SASL, mechanism: this._sasl_mechanism.name }); if (this._sasl_mechanism.isClientFirst) { var response = this._sasl_mechanism.onChallenge(this, null); request_auth_exchange.t(btoa(response)); } this.send(request_auth_exchange.tree()); mechanism_found = true; break; } return mechanism_found; }, /** PrivateFunction: _attemptLegacyAuth * * Attempt legacy (i.e. non-SASL) authentication. * */ _attemptLegacyAuth: function () { if (Strophe.getNodeFromJid(this.jid) === null) { // we don't have a node, which is required for non-anonymous // client connections this._changeConnectStatus( Strophe.Status.CONNFAIL, 'x-strophe-bad-non-anon-jid' ); this.disconnect('x-strophe-bad-non-anon-jid'); } else { // Fall back to legacy authentication this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null); this._addSysHandler( this._auth1_cb.bind(this), null, null, null, "_auth_1" ); this.send($iq({ 'type': "get", 'to': this.domain, 'id': "_auth_1" }).c("query", {xmlns: Strophe.NS.AUTH}) .c("username", {}).t(Strophe.getNodeFromJid(this.jid)) .tree()); } }, /** Function: authenticate * Set up authentication * * Continues the initial connection request by setting up authentication * handlers and starting the authentication process. * * SASL authentication will be attempted if available, otherwise * the code will fall back to legacy authentication. * * Parameters: * (Array) matched - Array of SASL mechanisms supported. * */ authenticate: function (matched) { if (!this._attemptSASLAuth(matched)) { this._attemptLegacyAuth(); } }, /** PrivateFunction: _sasl_challenge_cb * _Private_ handler for the SASL challenge * */ _sasl_challenge_cb: function(elem) { var challenge = atob(Strophe.getText(elem)); var response = this._sasl_mechanism.onChallenge(this, challenge); var stanza = $build('response', { 'xmlns': Strophe.NS.SASL }); if (response !== "") { stanza.t(btoa(response)); } this.send(stanza.tree()); return true; }, /** PrivateFunction: _auth1_cb * _Private_ handler for legacy authentication. * * This handler is called in response to the initial * for legacy authentication. It builds an authentication and * sends it, creating a handler (calling back to _auth2_cb()) to * handle the result * * Parameters: * (XMLElement) elem - The stanza that triggered the callback. * * Returns: * false to remove the handler. */ /* jshint unused:false */ _auth1_cb: function (elem) { // build plaintext auth iq var iq = $iq({type: "set", id: "_auth_2"}) .c('query', {xmlns: Strophe.NS.AUTH}) .c('username', {}).t(Strophe.getNodeFromJid(this.jid)) .up() .c('password').t(this.pass); if (!Strophe.getResourceFromJid(this.jid)) { // since the user has not supplied a resource, we pick // a default one here. unlike other auth methods, the server // cannot do this for us. this.jid = Strophe.getBareJidFromJid(this.jid) + '/strophe'; } iq.up().c('resource', {}).t(Strophe.getResourceFromJid(this.jid)); this._addSysHandler(this._auth2_cb.bind(this), null, null, null, "_auth_2"); this.send(iq.tree()); return false; }, /* jshint unused:true */ /** PrivateFunction: _sasl_success_cb * _Private_ handler for succesful SASL authentication. * * Parameters: * (XMLElement) elem - The matching stanza. * * Returns: * false to remove the handler. */ _sasl_success_cb: function (elem) { if (this._sasl_data["server-signature"]) { var serverSignature; var success = atob(Strophe.getText(elem)); var attribMatch = /([a-z]+)=([^,]+)(,|$)/; var matches = success.match(attribMatch); if (matches[1] === "v") { serverSignature = matches[2]; } if (serverSignature !== this._sasl_data["server-signature"]) { // remove old handlers this.deleteHandler(this._sasl_failure_handler); this._sasl_failure_handler = null; if (this._sasl_challenge_handler) { this.deleteHandler(this._sasl_challenge_handler); this._sasl_challenge_handler = null; } this._sasl_data = {}; return this._sasl_failure_cb(null); } } Strophe.info("SASL authentication succeeded."); if (this._sasl_mechanism) { this._sasl_mechanism.onSuccess(); } // remove old handlers this.deleteHandler(this._sasl_failure_handler); this._sasl_failure_handler = null; if (this._sasl_challenge_handler) { this.deleteHandler(this._sasl_challenge_handler); this._sasl_challenge_handler = null; } var streamfeature_handlers = []; var wrapper = function(handlers, elem) { while (handlers.length) { this.deleteHandler(handlers.pop()); } this._sasl_auth1_cb.bind(this)(elem); return false; }; streamfeature_handlers.push(this._addSysHandler(function(elem) { wrapper.bind(this)(streamfeature_handlers, elem); }.bind(this), null, "stream:features", null, null)); streamfeature_handlers.push(this._addSysHandler(function(elem) { wrapper.bind(this)(streamfeature_handlers, elem); }.bind(this), Strophe.NS.STREAM, "features", null, null)); // we must send an xmpp:restart now this._sendRestart(); return false; }, /** PrivateFunction: _sasl_auth1_cb * _Private_ handler to start stream binding. * * Parameters: * (XMLElement) elem - The matching stanza. * * Returns: * false to remove the handler. */ _sasl_auth1_cb: function (elem) { // save stream:features for future usage this.features = elem; var i, child; for (i = 0; i < elem.childNodes.length; i++) { child = elem.childNodes[i]; if (child.nodeName === 'bind') { this.do_bind = true; } if (child.nodeName === 'session') { this.do_session = true; } } if (!this.do_bind) { this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); return false; } else { this._addSysHandler(this._sasl_bind_cb.bind(this), null, null, null, "_bind_auth_2"); var resource = Strophe.getResourceFromJid(this.jid); if (resource) { this.send($iq({type: "set", id: "_bind_auth_2"}) .c('bind', {xmlns: Strophe.NS.BIND}) .c('resource', {}).t(resource).tree()); } else { this.send($iq({type: "set", id: "_bind_auth_2"}) .c('bind', {xmlns: Strophe.NS.BIND}) .tree()); } } return false; }, /** PrivateFunction: _sasl_bind_cb * _Private_ handler for binding result and session start. * * Parameters: * (XMLElement) elem - The matching stanza. * * Returns: * false to remove the handler. */ _sasl_bind_cb: function (elem) { if (elem.getAttribute("type") === "error") { Strophe.info("SASL binding failed."); var conflict = elem.getElementsByTagName("conflict"), condition; if (conflict.length > 0) { condition = 'conflict'; } this._changeConnectStatus(Strophe.Status.AUTHFAIL, condition); return false; } // TODO - need to grab errors var bind = elem.getElementsByTagName("bind"); var jidNode; if (bind.length > 0) { // Grab jid jidNode = bind[0].getElementsByTagName("jid"); if (jidNode.length > 0) { this.jid = Strophe.getText(jidNode[0]); if (this.do_session) { this._addSysHandler(this._sasl_session_cb.bind(this), null, null, null, "_session_auth_2"); this.send($iq({type: "set", id: "_session_auth_2"}) .c('session', {xmlns: Strophe.NS.SESSION}) .tree()); } else { this.authenticated = true; this._changeConnectStatus(Strophe.Status.CONNECTED, null); } } } else { Strophe.info("SASL binding failed."); this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); return false; } }, /** PrivateFunction: _sasl_session_cb * _Private_ handler to finish successful SASL connection. * * This sets Connection.authenticated to true on success, which * starts the processing of user handlers. * * Parameters: * (XMLElement) elem - The matching stanza. * * Returns: * false to remove the handler. */ _sasl_session_cb: function (elem) { if (elem.getAttribute("type") === "result") { this.authenticated = true; this._changeConnectStatus(Strophe.Status.CONNECTED, null); } else if (elem.getAttribute("type") === "error") { Strophe.info("Session creation failed."); this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); return false; } return false; }, /** PrivateFunction: _sasl_failure_cb * _Private_ handler for SASL authentication failure. * * Parameters: * (XMLElement) elem - The matching stanza. * * Returns: * false to remove the handler. */ /* jshint unused:false */ _sasl_failure_cb: function (elem) { // delete unneeded handlers if (this._sasl_success_handler) { this.deleteHandler(this._sasl_success_handler); this._sasl_success_handler = null; } if (this._sasl_challenge_handler) { this.deleteHandler(this._sasl_challenge_handler); this._sasl_challenge_handler = null; } if(this._sasl_mechanism) this._sasl_mechanism.onFailure(); this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); return false; }, /* jshint unused:true */ /** PrivateFunction: _auth2_cb * _Private_ handler to finish legacy authentication. * * This handler is called when the result from the jabber:iq:auth * stanza is returned. * * Parameters: * (XMLElement) elem - The stanza that triggered the callback. * * Returns: * false to remove the handler. */ _auth2_cb: function (elem) { if (elem.getAttribute("type") === "result") { this.authenticated = true; this._changeConnectStatus(Strophe.Status.CONNECTED, null); } else if (elem.getAttribute("type") === "error") { this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); this.disconnect('authentication failed'); } return false; }, /** PrivateFunction: _addSysTimedHandler * _Private_ function to add a system level timed handler. * * This function is used to add a Strophe.TimedHandler for the * library code. System timed handlers are allowed to run before * authentication is complete. * * Parameters: * (Integer) period - The period of the handler. * (Function) handler - The callback function. */ _addSysTimedHandler: function (period, handler) { var thand = new Strophe.TimedHandler(period, handler); thand.user = false; this.addTimeds.push(thand); return thand; }, /** PrivateFunction: _addSysHandler * _Private_ function to add a system level stanza handler. * * This function is used to add a Strophe.Handler for the * library code. System stanza handlers are allowed to run before * authentication is complete. * * Parameters: * (Function) handler - The callback function. * (String) ns - The namespace to match. * (String) name - The stanza name to match. * (String) type - The stanza type attribute to match. * (String) id - The stanza id attribute to match. */ _addSysHandler: function (handler, ns, name, type, id) { var hand = new Strophe.Handler(handler, ns, name, type, id); hand.user = false; this.addHandlers.push(hand); return hand; }, /** PrivateFunction: _onDisconnectTimeout * _Private_ timeout handler for handling non-graceful disconnection. * * If the graceful disconnect process does not complete within the * time allotted, this handler finishes the disconnect anyway. * * Returns: * false to remove the handler. */ _onDisconnectTimeout: function () { Strophe.info("_onDisconnectTimeout was called"); this._changeConnectStatus(Strophe.Status.CONNTIMEOUT, null); this._proto._onDisconnectTimeout(); // actually disconnect this._doDisconnect(); return false; }, /** PrivateFunction: _onIdle * _Private_ handler to process events during idle cycle. * * This handler is called every 100ms to fire timed handlers that * are ready and keep poll requests going. */ _onIdle: function () { var i, thand, since, newList; // add timed handlers scheduled for addition // NOTE: we add before remove in the case a timed handler is // added and then deleted before the next _onIdle() call. while (this.addTimeds.length > 0) { this.timedHandlers.push(this.addTimeds.pop()); } // remove timed handlers that have been scheduled for deletion while (this.removeTimeds.length > 0) { thand = this.removeTimeds.pop(); i = this.timedHandlers.indexOf(thand); if (i >= 0) { this.timedHandlers.splice(i, 1); } } // call ready timed handlers var now = new Date().getTime(); newList = []; for (i = 0; i < this.timedHandlers.length; i++) { thand = this.timedHandlers[i]; if (this.authenticated || !thand.user) { since = thand.lastCalled + thand.period; if (since - now <= 0) { if (thand.run()) { newList.push(thand); } } else { newList.push(thand); } } } this.timedHandlers = newList; clearTimeout(this._idleTimeout); this._proto._onIdle(); // reactivate the timer only if connected if (this.connected) { // XXX: setTimeout should be called only with function expressions (23974bc1) this._idleTimeout = setTimeout(function() { this._onIdle(); }.bind(this), 100); } } }; /** Class: Strophe.SASLMechanism * * encapsulates SASL authentication mechanisms. * * User code may override the priority for each mechanism or disable it completely. * See for information about changing priority and for informatian on * how to disable a mechanism. * * By default, all mechanisms are enabled and the priorities are * * OAUTHBEARER - 60 * SCRAM-SHA1 - 50 * DIGEST-MD5 - 40 * PLAIN - 30 * ANONYMOUS - 20 * EXTERNAL - 10 * * See: Strophe.Connection.addSupportedSASLMechanisms */ /** * PrivateConstructor: Strophe.SASLMechanism * SASL auth mechanism abstraction. * * Parameters: * (String) name - SASL Mechanism name. * (Boolean) isClientFirst - If client should send response first without challenge. * (Number) priority - Priority. * * Returns: * A new Strophe.SASLMechanism object. */ Strophe.SASLMechanism = function(name, isClientFirst, priority) { /** PrivateVariable: name * Mechanism name. */ this.name = name; /** PrivateVariable: isClientFirst * If client sends response without initial server challenge. */ this.isClientFirst = isClientFirst; /** Variable: priority * Determines which is chosen for authentication (Higher is better). * Users may override this to prioritize mechanisms differently. * * In the default configuration the priorities are * * SCRAM-SHA1 - 40 * DIGEST-MD5 - 30 * Plain - 20 * * Example: (This will cause Strophe to choose the mechanism that the server sent first) * * > Strophe.SASLMD5.priority = Strophe.SASLSHA1.priority; * * See for a list of available mechanisms. * */ this.priority = priority; }; Strophe.SASLMechanism.prototype = { /** * Function: test * Checks if mechanism able to run. * To disable a mechanism, make this return false; * * To disable plain authentication run * > Strophe.SASLPlain.test = function() { * > return false; * > } * * See for a list of available mechanisms. * * Parameters: * (Strophe.Connection) connection - Target Connection. * * Returns: * (Boolean) If mechanism was able to run. */ /* jshint unused:false */ test: function(connection) { return true; }, /* jshint unused:true */ /** PrivateFunction: onStart * Called before starting mechanism on some connection. * * Parameters: * (Strophe.Connection) connection - Target Connection. */ onStart: function(connection) { this._connection = connection; }, /** PrivateFunction: onChallenge * Called by protocol implementation on incoming challenge. If client is * first (isClientFirst === true) challenge will be null on the first call. * * Parameters: * (Strophe.Connection) connection - Target Connection. * (String) challenge - current challenge to handle. * * Returns: * (String) Mechanism response. */ /* jshint unused:false */ onChallenge: function(connection, challenge) { throw new Error("You should implement challenge handling!"); }, /* jshint unused:true */ /** PrivateFunction: onFailure * Protocol informs mechanism implementation about SASL failure. */ onFailure: function() { this._connection = null; }, /** PrivateFunction: onSuccess * Protocol informs mechanism implementation about SASL success. */ onSuccess: function() { this._connection = null; } }; /** Constants: SASL mechanisms * Available authentication mechanisms * * Strophe.SASLAnonymous - SASL ANONYMOUS authentication. * Strophe.SASLPlain - SASL PLAIN authentication. * Strophe.SASLMD5 - SASL DIGEST-MD5 authentication * Strophe.SASLSHA1 - SASL SCRAM-SHA1 authentication * Strophe.SASLOAuthBearer - SASL OAuth Bearer authentication * Strophe.SASLExternal - SASL EXTERNAL authentication */ // Building SASL callbacks /** PrivateConstructor: SASLAnonymous * SASL ANONYMOUS authentication. */ Strophe.SASLAnonymous = function() {}; Strophe.SASLAnonymous.prototype = new Strophe.SASLMechanism("ANONYMOUS", false, 20); Strophe.SASLAnonymous.prototype.test = function(connection) { return connection.authcid === null; }; /** PrivateConstructor: SASLPlain * SASL PLAIN authentication. */ Strophe.SASLPlain = function() {}; Strophe.SASLPlain.prototype = new Strophe.SASLMechanism("PLAIN", true, 30); Strophe.SASLPlain.prototype.test = function(connection) { return connection.authcid !== null; }; Strophe.SASLPlain.prototype.onChallenge = function(connection) { var auth_str = connection.authzid; auth_str = auth_str + "\u0000"; auth_str = auth_str + connection.authcid; auth_str = auth_str + "\u0000"; auth_str = auth_str + connection.pass; return utils.utf16to8(auth_str); }; /** PrivateConstructor: SASLSHA1 * SASL SCRAM SHA 1 authentication. */ Strophe.SASLSHA1 = function() {}; Strophe.SASLSHA1.prototype = new Strophe.SASLMechanism("SCRAM-SHA-1", true, 50); Strophe.SASLSHA1.prototype.test = function(connection) { return connection.authcid !== null; }; Strophe.SASLSHA1.prototype.onChallenge = function(connection, challenge, test_cnonce) { var cnonce = test_cnonce || MD5.hexdigest(Math.random() * 1234567890); var auth_str = "n=" + utils.utf16to8(connection.authcid); auth_str += ",r="; auth_str += cnonce; connection._sasl_data.cnonce = cnonce; connection._sasl_data["client-first-message-bare"] = auth_str; auth_str = "n,," + auth_str; this.onChallenge = function (connection, challenge) { var nonce, salt, iter, Hi, U, U_old, i, k, pass; var clientKey, serverKey, clientSignature; var responseText = "c=biws,"; var authMessage = connection._sasl_data["client-first-message-bare"] + "," + challenge + ","; var cnonce = connection._sasl_data.cnonce; var attribMatch = /([a-z]+)=([^,]+)(,|$)/; while (challenge.match(attribMatch)) { var matches = challenge.match(attribMatch); challenge = challenge.replace(matches[0], ""); switch (matches[1]) { case "r": nonce = matches[2]; break; case "s": salt = matches[2]; break; case "i": iter = matches[2]; break; } } if (nonce.substr(0, cnonce.length) !== cnonce) { connection._sasl_data = {}; return connection._sasl_failure_cb(); } responseText += "r=" + nonce; authMessage += responseText; salt = atob(salt); salt += "\x00\x00\x00\x01"; pass = utils.utf16to8(connection.pass); Hi = U_old = SHA1.core_hmac_sha1(pass, salt); for (i = 1; i < iter; i++) { U = SHA1.core_hmac_sha1(pass, SHA1.binb2str(U_old)); for (k = 0; k < 5; k++) { Hi[k] ^= U[k]; } U_old = U; } Hi = SHA1.binb2str(Hi); clientKey = SHA1.core_hmac_sha1(Hi, "Client Key"); serverKey = SHA1.str_hmac_sha1(Hi, "Server Key"); clientSignature = SHA1.core_hmac_sha1(SHA1.str_sha1(SHA1.binb2str(clientKey)), authMessage); connection._sasl_data["server-signature"] = SHA1.b64_hmac_sha1(serverKey, authMessage); for (k = 0; k < 5; k++) { clientKey[k] ^= clientSignature[k]; } responseText += ",p=" + btoa(SHA1.binb2str(clientKey)); return responseText; }.bind(this); return auth_str; }; /** PrivateConstructor: SASLMD5 * SASL DIGEST MD5 authentication. */ Strophe.SASLMD5 = function() {}; Strophe.SASLMD5.prototype = new Strophe.SASLMechanism("DIGEST-MD5", false, 40); Strophe.SASLMD5.prototype.test = function(connection) { return connection.authcid !== null; }; /** PrivateFunction: _quote * _Private_ utility function to backslash escape and quote strings. * * Parameters: * (String) str - The string to be quoted. * * Returns: * quoted string */ Strophe.SASLMD5.prototype._quote = function (str) { return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; //" end string workaround for emacs }; Strophe.SASLMD5.prototype.onChallenge = function(connection, challenge, test_cnonce) { var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/; var cnonce = test_cnonce || MD5.hexdigest("" + (Math.random() * 1234567890)); var realm = ""; var host = null; var nonce = ""; var qop = ""; var matches; while (challenge.match(attribMatch)) { matches = challenge.match(attribMatch); challenge = challenge.replace(matches[0], ""); matches[2] = matches[2].replace(/^"(.+)"$/, "$1"); switch (matches[1]) { case "realm": realm = matches[2]; break; case "nonce": nonce = matches[2]; break; case "qop": qop = matches[2]; break; case "host": host = matches[2]; break; } } var digest_uri = connection.servtype + "/" + connection.domain; if (host !== null) { digest_uri = digest_uri + "/" + host; } var cred = utils.utf16to8(connection.authcid + ":" + realm + ":" + this._connection.pass); var A1 = MD5.hash(cred) + ":" + nonce + ":" + cnonce; var A2 = 'AUTHENTICATE:' + digest_uri; var responseText = ""; responseText += 'charset=utf-8,'; responseText += 'username=' + this._quote(utils.utf16to8(connection.authcid)) + ','; responseText += 'realm=' + this._quote(realm) + ','; responseText += 'nonce=' + this._quote(nonce) + ','; responseText += 'nc=00000001,'; responseText += 'cnonce=' + this._quote(cnonce) + ','; responseText += 'digest-uri=' + this._quote(digest_uri) + ','; responseText += 'response=' + MD5.hexdigest(MD5.hexdigest(A1) + ":" + nonce + ":00000001:" + cnonce + ":auth:" + MD5.hexdigest(A2)) + ","; responseText += 'qop=auth'; this.onChallenge = function () { return ""; }; return responseText; }; /** PrivateConstructor: SASLOAuthBearer * SASL OAuth Bearer authentication. */ Strophe.SASLOAuthBearer = function() {}; Strophe.SASLOAuthBearer.prototype = new Strophe.SASLMechanism("OAUTHBEARER", true, 60); Strophe.SASLOAuthBearer.prototype.test = function(connection) { return connection.pass !== null; }; Strophe.SASLOAuthBearer.prototype.onChallenge = function(connection) { var auth_str = 'n,'; if (connection.authcid !== null) { auth_str = auth_str + 'a=' + connection.authzid; } auth_str = auth_str + ','; auth_str = auth_str + "\u0001"; auth_str = auth_str + 'auth=Bearer '; auth_str = auth_str + connection.pass; auth_str = auth_str + "\u0001"; auth_str = auth_str + "\u0001"; return utils.utf16to8(auth_str); }; /** PrivateConstructor: SASLExternal * SASL EXTERNAL authentication. * * The EXTERNAL mechanism allows a client to request the server to use * credentials established by means external to the mechanism to * authenticate the client. The external means may be, for instance, * TLS services. */ Strophe.SASLExternal = function() {}; Strophe.SASLExternal.prototype = new Strophe.SASLMechanism("EXTERNAL", true, 10); Strophe.SASLExternal.prototype.onChallenge = function(connection) { /** According to XEP-178, an authzid SHOULD NOT be presented when the * authcid contained or implied in the client certificate is the JID (i.e. * authzid) with which the user wants to log in as. * * To NOT send the authzid, the user should therefore set the authcid equal * to the JID when instantiating a new Strophe.Connection object. */ return connection.authcid === connection.authzid ? '' : connection.authzid; }; return { 'Strophe': Strophe, '$build': $build, '$iq': $iq, '$msg': $msg, '$pres': $pres, 'SHA1': SHA1, 'MD5': MD5, 'b64_hmac_sha1': SHA1.b64_hmac_sha1, 'b64_sha1': SHA1.b64_sha1, 'str_hmac_sha1': SHA1.str_hmac_sha1, 'str_sha1': SHA1.str_sha1 }; })); /* This program is distributed under the terms of the MIT license. Please see the LICENSE file for details. Copyright 2006-2008, OGG, LLC */ /* jshint undef: true, unused: true:, noarg: true, latedef: true */ /* global define, window, setTimeout, clearTimeout, XMLHttpRequest, ActiveXObject, Strophe, $build */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define('strophe-bosh',['strophe-core'], function (core) { return factory( core.Strophe, core.$build ); }); } else { // Browser globals return factory(Strophe, $build); } }(this, function (Strophe, $build) { /** PrivateClass: Strophe.Request * _Private_ helper class that provides a cross implementation abstraction * for a BOSH related XMLHttpRequest. * * The Strophe.Request class is used internally to encapsulate BOSH request * information. It is not meant to be used from user's code. */ /** PrivateConstructor: Strophe.Request * Create and initialize a new Strophe.Request object. * * Parameters: * (XMLElement) elem - The XML data to be sent in the request. * (Function) func - The function that will be called when the * XMLHttpRequest readyState changes. * (Integer) rid - The BOSH rid attribute associated with this request. * (Integer) sends - The number of times this same request has been sent. */ Strophe.Request = function (elem, func, rid, sends) { this.id = ++Strophe._requestId; this.xmlData = elem; this.data = Strophe.serialize(elem); // save original function in case we need to make a new request // from this one. this.origFunc = func; this.func = func; this.rid = rid; this.date = NaN; this.sends = sends || 0; this.abort = false; this.dead = null; this.age = function () { if (!this.date) { return 0; } var now = new Date(); return (now - this.date) / 1000; }; this.timeDead = function () { if (!this.dead) { return 0; } var now = new Date(); return (now - this.dead) / 1000; }; this.xhr = this._newXHR(); }; Strophe.Request.prototype = { /** PrivateFunction: getResponse * Get a response from the underlying XMLHttpRequest. * * This function attempts to get a response from the request and checks * for errors. * * Throws: * "parsererror" - A parser error occured. * "badformat" - The entity has sent XML that cannot be processed. * * Returns: * The DOM element tree of the response. */ getResponse: function () { var node = null; if (this.xhr.responseXML && this.xhr.responseXML.documentElement) { node = this.xhr.responseXML.documentElement; if (node.tagName === "parsererror") { Strophe.error("invalid response received"); Strophe.error("responseText: " + this.xhr.responseText); Strophe.error("responseXML: " + Strophe.serialize(this.xhr.responseXML)); throw "parsererror"; } } else if (this.xhr.responseText) { Strophe.error("invalid response received"); Strophe.error("responseText: " + this.xhr.responseText); throw "badformat"; } return node; }, /** PrivateFunction: _newXHR * _Private_ helper function to create XMLHttpRequests. * * This function creates XMLHttpRequests across all implementations. * * Returns: * A new XMLHttpRequest. */ _newXHR: function () { var xhr = null; if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); if (xhr.overrideMimeType) { xhr.overrideMimeType("text/xml; charset=utf-8"); } } else if (window.ActiveXObject) { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } // use Function.bind() to prepend ourselves as an argument xhr.onreadystatechange = this.func.bind(null, this); return xhr; } }; /** Class: Strophe.Bosh * _Private_ helper class that handles BOSH Connections * * The Strophe.Bosh class is used internally by Strophe.Connection * to encapsulate BOSH sessions. It is not meant to be used from user's code. */ /** File: bosh.js * A JavaScript library to enable BOSH in Strophejs. * * this library uses Bidirectional-streams Over Synchronous HTTP (BOSH) * to emulate a persistent, stateful, two-way connection to an XMPP server. * More information on BOSH can be found in XEP 124. */ /** PrivateConstructor: Strophe.Bosh * Create and initialize a Strophe.Bosh object. * * Parameters: * (Strophe.Connection) connection - The Strophe.Connection that will use BOSH. * * Returns: * A new Strophe.Bosh object. */ Strophe.Bosh = function(connection) { this._conn = connection; /* request id for body tags */ this.rid = Math.floor(Math.random() * 4294967295); /* The current session ID. */ this.sid = null; // default BOSH values this.hold = 1; this.wait = 60; this.window = 5; this.errors = 0; this.inactivity = null; this._requests = []; }; Strophe.Bosh.prototype = { /** Variable: strip * * BOSH-Connections will have all stanzas wrapped in a tag when * passed to or . * To strip this tag, User code can set to "body": * * > Strophe.Bosh.prototype.strip = "body"; * * This will enable stripping of the body tag in both * and . */ strip: null, /** PrivateFunction: _buildBody * _Private_ helper function to generate the wrapper for BOSH. * * Returns: * A Strophe.Builder with a element. */ _buildBody: function () { var bodyWrap = $build('body', { rid: this.rid++, xmlns: Strophe.NS.HTTPBIND }); if (this.sid !== null) { bodyWrap.attrs({sid: this.sid}); } if (this._conn.options.keepalive && this._conn._sessionCachingSupported()) { this._cacheSession(); } return bodyWrap; }, /** PrivateFunction: _reset * Reset the connection. * * This function is called by the reset function of the Strophe Connection */ _reset: function () { this.rid = Math.floor(Math.random() * 4294967295); this.sid = null; this.errors = 0; if (this._conn._sessionCachingSupported()) { window.sessionStorage.removeItem('strophe-bosh-session'); } this._conn.nextValidRid(this.rid); }, /** PrivateFunction: _connect * _Private_ function that initializes the BOSH connection. * * Creates and sends the Request that initializes the BOSH connection. */ _connect: function (wait, hold, route) { this.wait = wait || this.wait; this.hold = hold || this.hold; this.errors = 0; // build the body tag var body = this._buildBody().attrs({ to: this._conn.domain, "xml:lang": "en", wait: this.wait, hold: this.hold, content: "text/xml; charset=utf-8", ver: "1.6", "xmpp:version": "1.0", "xmlns:xmpp": Strophe.NS.BOSH }); if(route){ body.attrs({ route: route }); } var _connect_cb = this._conn._connect_cb; this._requests.push( new Strophe.Request(body.tree(), this._onRequestStateChange.bind( this, _connect_cb.bind(this._conn)), body.tree().getAttribute("rid"))); this._throttledRequestHandler(); }, /** PrivateFunction: _attach * Attach to an already created and authenticated BOSH session. * * This function is provided to allow Strophe to attach to BOSH * sessions which have been created externally, perhaps by a Web * application. This is often used to support auto-login type features * without putting user credentials into the page. * * Parameters: * (String) jid - The full JID that is bound by the session. * (String) sid - The SID of the BOSH session. * (String) rid - The current RID of the BOSH session. This RID * will be used by the next request. * (Function) callback The connect callback function. * (Integer) wait - The optional HTTPBIND wait value. This is the * time the server will wait before returning an empty result for * a request. The default setting of 60 seconds is recommended. * Other settings will require tweaks to the Strophe.TIMEOUT value. * (Integer) hold - The optional HTTPBIND hold value. This is the * number of connections the server will hold at one time. This * should almost always be set to 1 (the default). * (Integer) wind - The optional HTTBIND window value. This is the * allowed range of request ids that are valid. The default is 5. */ _attach: function (jid, sid, rid, callback, wait, hold, wind) { this._conn.jid = jid; this.sid = sid; this.rid = rid; this._conn.connect_callback = callback; this._conn.domain = Strophe.getDomainFromJid(this._conn.jid); this._conn.authenticated = true; this._conn.connected = true; this.wait = wait || this.wait; this.hold = hold || this.hold; this.window = wind || this.window; this._conn._changeConnectStatus(Strophe.Status.ATTACHED, null); }, /** PrivateFunction: _restore * Attempt to restore a cached BOSH session * * Parameters: * (String) jid - The full JID that is bound by the session. * This parameter is optional but recommended, specifically in cases * where prebinded BOSH sessions are used where it's important to know * that the right session is being restored. * (Function) callback The connect callback function. * (Integer) wait - The optional HTTPBIND wait value. This is the * time the server will wait before returning an empty result for * a request. The default setting of 60 seconds is recommended. * Other settings will require tweaks to the Strophe.TIMEOUT value. * (Integer) hold - The optional HTTPBIND hold value. This is the * number of connections the server will hold at one time. This * should almost always be set to 1 (the default). * (Integer) wind - The optional HTTBIND window value. This is the * allowed range of request ids that are valid. The default is 5. */ _restore: function (jid, callback, wait, hold, wind) { var session = JSON.parse(window.sessionStorage.getItem('strophe-bosh-session')); if (typeof session !== "undefined" && session !== null && session.rid && session.sid && session.jid && ( typeof jid === "undefined" || jid === null || Strophe.getBareJidFromJid(session.jid) === Strophe.getBareJidFromJid(jid) || // If authcid is null, then it's an anonymous login, so // we compare only the domains: ((Strophe.getNodeFromJid(jid) === null) && (Strophe.getDomainFromJid(session.jid) === jid)) ) ) { this._conn.restored = true; this._attach(session.jid, session.sid, session.rid, callback, wait, hold, wind); } else { throw { name: "StropheSessionError", message: "_restore: no restoreable session." }; } }, /** PrivateFunction: _cacheSession * _Private_ handler for the beforeunload event. * * This handler is used to process the Bosh-part of the initial request. * Parameters: * (Strophe.Request) bodyWrap - The received stanza. */ _cacheSession: function () { if (this._conn.authenticated) { if (this._conn.jid && this.rid && this.sid) { window.sessionStorage.setItem('strophe-bosh-session', JSON.stringify({ 'jid': this._conn.jid, 'rid': this.rid, 'sid': this.sid })); } } else { window.sessionStorage.removeItem('strophe-bosh-session'); } }, /** PrivateFunction: _connect_cb * _Private_ handler for initial connection request. * * This handler is used to process the Bosh-part of the initial request. * Parameters: * (Strophe.Request) bodyWrap - The received stanza. */ _connect_cb: function (bodyWrap) { var typ = bodyWrap.getAttribute("type"); var cond, conflict; if (typ !== null && typ === "terminate") { // an error occurred cond = bodyWrap.getAttribute("condition"); Strophe.error("BOSH-Connection failed: " + cond); conflict = bodyWrap.getElementsByTagName("conflict"); if (cond !== null) { if (cond === "remote-stream-error" && conflict.length > 0) { cond = "conflict"; } this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, cond); } else { this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown"); } this._conn._doDisconnect(cond); return Strophe.Status.CONNFAIL; } // check to make sure we don't overwrite these if _connect_cb is // called multiple times in the case of missing stream:features if (!this.sid) { this.sid = bodyWrap.getAttribute("sid"); } var wind = bodyWrap.getAttribute('requests'); if (wind) { this.window = parseInt(wind, 10); } var hold = bodyWrap.getAttribute('hold'); if (hold) { this.hold = parseInt(hold, 10); } var wait = bodyWrap.getAttribute('wait'); if (wait) { this.wait = parseInt(wait, 10); } var inactivity = bodyWrap.getAttribute('inactivity'); if (inactivity) { this.inactivity = parseInt(inactivity, 10); } }, /** PrivateFunction: _disconnect * _Private_ part of Connection.disconnect for Bosh * * Parameters: * (Request) pres - This stanza will be sent before disconnecting. */ _disconnect: function (pres) { this._sendTerminate(pres); }, /** PrivateFunction: _doDisconnect * _Private_ function to disconnect. * * Resets the SID and RID. */ _doDisconnect: function () { this.sid = null; this.rid = Math.floor(Math.random() * 4294967295); if (this._conn._sessionCachingSupported()) { window.sessionStorage.removeItem('strophe-bosh-session'); } this._conn.nextValidRid(this.rid); }, /** PrivateFunction: _emptyQueue * _Private_ function to check if the Request queue is empty. * * Returns: * True, if there are no Requests queued, False otherwise. */ _emptyQueue: function () { return this._requests.length === 0; }, /** PrivateFunction: _callProtocolErrorHandlers * _Private_ function to call error handlers registered for HTTP errors. * * Parameters: * (Strophe.Request) req - The request that is changing readyState. */ _callProtocolErrorHandlers: function (req) { var reqStatus = this._getRequestStatus(req), err_callback; err_callback = this._conn.protocolErrorHandlers.HTTP[reqStatus]; if (err_callback) { err_callback.call(this, reqStatus); } }, /** PrivateFunction: _hitError * _Private_ function to handle the error count. * * Requests are resent automatically until their error count reaches * 5. Each time an error is encountered, this function is called to * increment the count and disconnect if the count is too high. * * Parameters: * (Integer) reqStatus - The request status. */ _hitError: function (reqStatus) { this.errors++; Strophe.warn("request errored, status: " + reqStatus + ", number of errors: " + this.errors); if (this.errors > 4) { this._conn._onDisconnectTimeout(); } }, /** PrivateFunction: _no_auth_received * * Called on stream start/restart when no stream:features * has been received and sends a blank poll request. */ _no_auth_received: function (_callback) { if (_callback) { _callback = _callback.bind(this._conn); } else { _callback = this._conn._connect_cb.bind(this._conn); } var body = this._buildBody(); this._requests.push( new Strophe.Request(body.tree(), this._onRequestStateChange.bind( this, _callback.bind(this._conn)), body.tree().getAttribute("rid"))); this._throttledRequestHandler(); }, /** PrivateFunction: _onDisconnectTimeout * _Private_ timeout handler for handling non-graceful disconnection. * * Cancels all remaining Requests and clears the queue. */ _onDisconnectTimeout: function () { this._abortAllRequests(); }, /** PrivateFunction: _abortAllRequests * _Private_ helper function that makes sure all pending requests are aborted. */ _abortAllRequests: function _abortAllRequests() { var req; while (this._requests.length > 0) { req = this._requests.pop(); req.abort = true; req.xhr.abort(); // jslint complains, but this is fine. setting to empty func // is necessary for IE6 req.xhr.onreadystatechange = function () {}; // jshint ignore:line } }, /** PrivateFunction: _onIdle * _Private_ handler called by Strophe.Connection._onIdle * * Sends all queued Requests or polls with empty Request if there are none. */ _onIdle: function () { var data = this._conn._data; // if no requests are in progress, poll if (this._conn.authenticated && this._requests.length === 0 && data.length === 0 && !this._conn.disconnecting) { Strophe.info("no requests during idle cycle, sending " + "blank request"); data.push(null); } if (this._conn.paused) { return; } if (this._requests.length < 2 && data.length > 0) { var body = this._buildBody(); for (var i = 0; i < data.length; i++) { if (data[i] !== null) { if (data[i] === "restart") { body.attrs({ to: this._conn.domain, "xml:lang": "en", "xmpp:restart": "true", "xmlns:xmpp": Strophe.NS.BOSH }); } else { body.cnode(data[i]).up(); } } } delete this._conn._data; this._conn._data = []; this._requests.push( new Strophe.Request(body.tree(), this._onRequestStateChange.bind( this, this._conn._dataRecv.bind(this._conn)), body.tree().getAttribute("rid"))); this._throttledRequestHandler(); } if (this._requests.length > 0) { var time_elapsed = this._requests[0].age(); if (this._requests[0].dead !== null) { if (this._requests[0].timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) { this._throttledRequestHandler(); } } if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) { Strophe.warn("Request " + this._requests[0].id + " timed out, over " + Math.floor(Strophe.TIMEOUT * this.wait) + " seconds since last activity"); this._throttledRequestHandler(); } } }, /** PrivateFunction: _getRequestStatus * * Returns the HTTP status code from a Strophe.Request * * Parameters: * (Strophe.Request) req - The Strophe.Request instance. * (Integer) def - The default value that should be returned if no * status value was found. */ _getRequestStatus: function (req, def) { var reqStatus; if (req.xhr.readyState === 4) { try { reqStatus = req.xhr.status; } catch (e) { // ignore errors from undefined status attribute. Works // around a browser bug Strophe.error( "Caught an error while retrieving a request's status, " + "reqStatus: " + reqStatus); } } if (typeof(reqStatus) === "undefined") { reqStatus = typeof def === 'number' ? def : 0; } return reqStatus; }, /** PrivateFunction: _onRequestStateChange * _Private_ handler for Strophe.Request state changes. * * This function is called when the XMLHttpRequest readyState changes. * It contains a lot of error handling logic for the many ways that * requests can fail, and calls the request callback when requests * succeed. * * Parameters: * (Function) func - The handler for the request. * (Strophe.Request) req - The request that is changing readyState. */ _onRequestStateChange: function (func, req) { Strophe.debug("request id "+req.id+"."+req.sends+ " state changed to "+req.xhr.readyState); if (req.abort) { req.abort = false; return; } if (req.xhr.readyState !== 4) { // The request is not yet complete return; } var reqStatus = this._getRequestStatus(req); if (this.disconnecting && reqStatus >= 400) { this._hitError(reqStatus); this._callProtocolErrorHandlers(req); return; } var valid_request = reqStatus > 0 && reqStatus < 500; var too_many_retries = req.sends > this._conn.maxRetries; if (valid_request || too_many_retries) { // remove from internal queue this._removeRequest(req); Strophe.debug("request id "+req.id+" should now be removed"); } if (reqStatus === 200) { // request succeeded var reqIs0 = (this._requests[0] === req); var reqIs1 = (this._requests[1] === req); // if request 1 finished, or request 0 finished and request // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to // restart the other - both will be in the first spot, as the // completed request has been removed from the queue already if (reqIs1 || (reqIs0 && this._requests.length > 0 && this._requests[0].age() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait))) { this._restartRequest(0); } this._conn.nextValidRid(Number(req.rid) + 1); Strophe.debug("request id "+req.id+"."+req.sends+" got 200"); func(req); // call handler this.errors = 0; } else if (reqStatus === 0 || (reqStatus >= 400 && reqStatus < 600) || reqStatus >= 12000) { // request failed Strophe.error("request id "+req.id+"."+req.sends+" error "+reqStatus+" happened"); this._hitError(reqStatus); this._callProtocolErrorHandlers(req); if (reqStatus >= 400 && reqStatus < 500) { this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING, null); this._conn._doDisconnect(); } } else { Strophe.error("request id "+req.id+"."+req.sends+" error "+reqStatus+" happened"); } if (!valid_request && !too_many_retries) { this._throttledRequestHandler(); } else if (too_many_retries && !this._conn.connected) { this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "giving-up"); } }, /** PrivateFunction: _processRequest * _Private_ function to process a request in the queue. * * This function takes requests off the queue and sends them and * restarts dead requests. * * Parameters: * (Integer) i - The index of the request in the queue. */ _processRequest: function (i) { var self = this; var req = this._requests[i]; var reqStatus = this._getRequestStatus(req, -1); // make sure we limit the number of retries if (req.sends > this._conn.maxRetries) { this._conn._onDisconnectTimeout(); return; } var time_elapsed = req.age(); var primaryTimeout = (!isNaN(time_elapsed) && time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)); var secondaryTimeout = (req.dead !== null && req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)); var requestCompletedWithServerError = (req.xhr.readyState === 4 && (reqStatus < 1 || reqStatus >= 500)); if (primaryTimeout || secondaryTimeout || requestCompletedWithServerError) { if (secondaryTimeout) { Strophe.error("Request " + this._requests[i].id + " timed out (secondary), restarting"); } req.abort = true; req.xhr.abort(); // setting to null fails on IE6, so set to empty function req.xhr.onreadystatechange = function () {}; this._requests[i] = new Strophe.Request(req.xmlData, req.origFunc, req.rid, req.sends); req = this._requests[i]; } if (req.xhr.readyState === 0) { Strophe.debug("request id "+req.id+"."+req.sends+" posting"); try { var contentType = this._conn.options.contentType || "text/xml; charset=utf-8"; req.xhr.open("POST", this._conn.service, this._conn.options.sync ? false : true); if (typeof req.xhr.setRequestHeader !== 'undefined') { // IE9 doesn't have setRequestHeader req.xhr.setRequestHeader("Content-Type", contentType); } if (this._conn.options.withCredentials) { req.xhr.withCredentials = true; } } catch (e2) { Strophe.error("XHR open failed: " + e2.toString()); if (!this._conn.connected) { this._conn._changeConnectStatus( Strophe.Status.CONNFAIL, "bad-service"); } this._conn.disconnect(); return; } // Fires the XHR request -- may be invoked immediately // or on a gradually expanding retry window for reconnects var sendFunc = function () { req.date = new Date(); if (self._conn.options.customHeaders){ var headers = self._conn.options.customHeaders; for (var header in headers) { if (headers.hasOwnProperty(header)) { req.xhr.setRequestHeader(header, headers[header]); } } } req.xhr.send(req.data); }; // Implement progressive backoff for reconnects -- // First retry (send === 1) should also be instantaneous if (req.sends > 1) { // Using a cube of the retry number creates a nicely // expanding retry window var backoff = Math.min(Math.floor(Strophe.TIMEOUT * this.wait), Math.pow(req.sends, 3)) * 1000; setTimeout(function() { // XXX: setTimeout should be called only with function expressions (23974bc1) sendFunc(); }, backoff); } else { sendFunc(); } req.sends++; if (this._conn.xmlOutput !== Strophe.Connection.prototype.xmlOutput) { if (req.xmlData.nodeName === this.strip && req.xmlData.childNodes.length) { this._conn.xmlOutput(req.xmlData.childNodes[0]); } else { this._conn.xmlOutput(req.xmlData); } } if (this._conn.rawOutput !== Strophe.Connection.prototype.rawOutput) { this._conn.rawOutput(req.data); } } else { Strophe.debug("_processRequest: " + (i === 0 ? "first" : "second") + " request has readyState of " + req.xhr.readyState); } }, /** PrivateFunction: _removeRequest * _Private_ function to remove a request from the queue. * * Parameters: * (Strophe.Request) req - The request to remove. */ _removeRequest: function (req) { Strophe.debug("removing request"); var i; for (i = this._requests.length - 1; i >= 0; i--) { if (req === this._requests[i]) { this._requests.splice(i, 1); } } // IE6 fails on setting to null, so set to empty function req.xhr.onreadystatechange = function () {}; this._throttledRequestHandler(); }, /** PrivateFunction: _restartRequest * _Private_ function to restart a request that is presumed dead. * * Parameters: * (Integer) i - The index of the request in the queue. */ _restartRequest: function (i) { var req = this._requests[i]; if (req.dead === null) { req.dead = new Date(); } this._processRequest(i); }, /** PrivateFunction: _reqToData * _Private_ function to get a stanza out of a request. * * Tries to extract a stanza out of a Request Object. * When this fails the current connection will be disconnected. * * Parameters: * (Object) req - The Request. * * Returns: * The stanza that was passed. */ _reqToData: function (req) { try { return req.getResponse(); } catch (e) { if (e !== "parsererror") { throw e; } this._conn.disconnect("strophe-parsererror"); } }, /** PrivateFunction: _sendTerminate * _Private_ function to send initial disconnect sequence. * * This is the first step in a graceful disconnect. It sends * the BOSH server a terminate body and includes an unavailable * presence if authentication has completed. */ _sendTerminate: function (pres) { Strophe.info("_sendTerminate was called"); var body = this._buildBody().attrs({type: "terminate"}); if (pres) { body.cnode(pres.tree()); } var req = new Strophe.Request( body.tree(), this._onRequestStateChange.bind( this, this._conn._dataRecv.bind(this._conn)), body.tree().getAttribute("rid") ); this._requests.push(req); this._throttledRequestHandler(); }, /** PrivateFunction: _send * _Private_ part of the Connection.send function for BOSH * * Just triggers the RequestHandler to send the messages that are in the queue */ _send: function () { clearTimeout(this._conn._idleTimeout); this._throttledRequestHandler(); // XXX: setTimeout should be called only with function expressions (23974bc1) this._conn._idleTimeout = setTimeout(function() { this._onIdle(); }.bind(this._conn), 100); }, /** PrivateFunction: _sendRestart * * Send an xmpp:restart stanza. */ _sendRestart: function () { this._throttledRequestHandler(); clearTimeout(this._conn._idleTimeout); }, /** PrivateFunction: _throttledRequestHandler * _Private_ function to throttle requests to the connection window. * * This function makes sure we don't send requests so fast that the * request ids overflow the connection window in the case that one * request died. */ _throttledRequestHandler: function () { if (!this._requests) { Strophe.debug("_throttledRequestHandler called with " + "undefined requests"); } else { Strophe.debug("_throttledRequestHandler called with " + this._requests.length + " requests"); } if (!this._requests || this._requests.length === 0) { return; } if (this._requests.length > 0) { this._processRequest(0); } if (this._requests.length > 1 && Math.abs(this._requests[0].rid - this._requests[1].rid) < this.window) { this._processRequest(1); } } }; return Strophe; })); /* This program is distributed under the terms of the MIT license. Please see the LICENSE file for details. Copyright 2006-2008, OGG, LLC */ /* jshint undef: true, unused: true:, noarg: true, latedef: true */ /* global define, window, clearTimeout, WebSocket, DOMParser, Strophe, $build */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define('strophe-websocket',['strophe-core'], function (core) { return factory( core.Strophe, core.$build ); }); } else { // Browser globals return factory(Strophe, $build); } }(this, function (Strophe, $build) { /** Class: Strophe.WebSocket * _Private_ helper class that handles WebSocket Connections * * The Strophe.WebSocket class is used internally by Strophe.Connection * to encapsulate WebSocket sessions. It is not meant to be used from user's code. */ /** File: websocket.js * A JavaScript library to enable XMPP over Websocket in Strophejs. * * This file implements XMPP over WebSockets for Strophejs. * If a Connection is established with a Websocket url (ws://...) * Strophe will use WebSockets. * For more information on XMPP-over-WebSocket see RFC 7395: * http://tools.ietf.org/html/rfc7395 * * WebSocket support implemented by Andreas Guth (andreas.guth@rwth-aachen.de) */ /** PrivateConstructor: Strophe.Websocket * Create and initialize a Strophe.WebSocket object. * Currently only sets the connection Object. * * Parameters: * (Strophe.Connection) connection - The Strophe.Connection that will use WebSockets. * * Returns: * A new Strophe.WebSocket object. */ Strophe.Websocket = function(connection) { this._conn = connection; this.strip = "wrapper"; var service = connection.service; if (service.indexOf("ws:") !== 0 && service.indexOf("wss:") !== 0) { // If the service is not an absolute URL, assume it is a path and put the absolute // URL together from options, current URL and the path. var new_service = ""; if (connection.options.protocol === "ws" && window.location.protocol !== "https:") { new_service += "ws"; } else { new_service += "wss"; } new_service += "://" + window.location.host; if (service.indexOf("/") !== 0) { new_service += window.location.pathname + service; } else { new_service += service; } connection.service = new_service; } }; Strophe.Websocket.prototype = { /** PrivateFunction: _buildStream * _Private_ helper function to generate the start tag for WebSockets * * Returns: * A Strophe.Builder with a element. */ _buildStream: function () { return $build("open", { "xmlns": Strophe.NS.FRAMING, "to": this._conn.domain, "version": '1.0' }); }, /** PrivateFunction: _check_streamerror * _Private_ checks a message for stream:error * * Parameters: * (Strophe.Request) bodyWrap - The received stanza. * connectstatus - The ConnectStatus that will be set on error. * Returns: * true if there was a streamerror, false otherwise. */ _check_streamerror: function (bodyWrap, connectstatus) { var errors; if (bodyWrap.getElementsByTagNameNS) { errors = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, "error"); } else { errors = bodyWrap.getElementsByTagName("stream:error"); } if (errors.length === 0) { return false; } var error = errors[0]; var condition = ""; var text = ""; var ns = "urn:ietf:params:xml:ns:xmpp-streams"; for (var i = 0; i < error.childNodes.length; i++) { var e = error.childNodes[i]; if (e.getAttribute("xmlns") !== ns) { break; } if (e.nodeName === "text") { text = e.textContent; } else { condition = e.nodeName; } } var errorString = "WebSocket stream error: "; if (condition) { errorString += condition; } else { errorString += "unknown"; } if (text) { errorString += " - " + text; } Strophe.error(errorString); // close the connection on stream_error this._conn._changeConnectStatus(connectstatus, condition); this._conn._doDisconnect(); return true; }, /** PrivateFunction: _reset * Reset the connection. * * This function is called by the reset function of the Strophe Connection. * Is not needed by WebSockets. */ _reset: function () { return; }, /** PrivateFunction: _connect * _Private_ function called by Strophe.Connection.connect * * Creates a WebSocket for a connection and assigns Callbacks to it. * Does nothing if there already is a WebSocket. */ _connect: function () { // Ensure that there is no open WebSocket from a previous Connection. this._closeSocket(); // Create the new WobSocket this.socket = new WebSocket(this._conn.service, "xmpp"); this.socket.onopen = this._onOpen.bind(this); this.socket.onerror = this._onError.bind(this); this.socket.onclose = this._onClose.bind(this); this.socket.onmessage = this._connect_cb_wrapper.bind(this); }, /** PrivateFunction: _connect_cb * _Private_ function called by Strophe.Connection._connect_cb * * checks for stream:error * * Parameters: * (Strophe.Request) bodyWrap - The received stanza. */ _connect_cb: function(bodyWrap) { var error = this._check_streamerror(bodyWrap, Strophe.Status.CONNFAIL); if (error) { return Strophe.Status.CONNFAIL; } }, /** PrivateFunction: _handleStreamStart * _Private_ function that checks the opening tag for errors. * * Disconnects if there is an error and returns false, true otherwise. * * Parameters: * (Node) message - Stanza containing the tag. */ _handleStreamStart: function(message) { var error = false; // Check for errors in the tag var ns = message.getAttribute("xmlns"); if (typeof ns !== "string") { error = "Missing xmlns in "; } else if (ns !== Strophe.NS.FRAMING) { error = "Wrong xmlns in : " + ns; } var ver = message.getAttribute("version"); if (typeof ver !== "string") { error = "Missing version in "; } else if (ver !== "1.0") { error = "Wrong version in : " + ver; } if (error) { this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, error); this._conn._doDisconnect(); return false; } return true; }, /** PrivateFunction: _connect_cb_wrapper * _Private_ function that handles the first connection messages. * * On receiving an opening stream tag this callback replaces itself with the real * message handler. On receiving a stream error the connection is terminated. */ _connect_cb_wrapper: function(message) { if (message.data.indexOf("\s*)*/, ""); if (data === '') return; var streamStart = new DOMParser().parseFromString(data, "text/xml").documentElement; this._conn.xmlInput(streamStart); this._conn.rawInput(message.data); //_handleStreamSteart will check for XML errors and disconnect on error if (this._handleStreamStart(streamStart)) { //_connect_cb will check for stream:error and disconnect on error this._connect_cb(streamStart); } } else if (message.data.indexOf(" tag."); } } this._conn._doDisconnect(); }, /** PrivateFunction: _doDisconnect * _Private_ function to disconnect. * * Just closes the Socket for WebSockets */ _doDisconnect: function () { Strophe.info("WebSockets _doDisconnect was called"); this._closeSocket(); }, /** PrivateFunction _streamWrap * _Private_ helper function to wrap a stanza in a tag. * This is used so Strophe can process stanzas from WebSockets like BOSH */ _streamWrap: function (stanza) { return "" + stanza + ''; }, /** PrivateFunction: _closeSocket * _Private_ function to close the WebSocket. * * Closes the socket if it is still open and deletes it */ _closeSocket: function () { if (this.socket) { try { this.socket.close(); } catch (e) {} } this.socket = null; }, /** PrivateFunction: _emptyQueue * _Private_ function to check if the message queue is empty. * * Returns: * True, because WebSocket messages are send immediately after queueing. */ _emptyQueue: function () { return true; }, /** PrivateFunction: _onClose * _Private_ function to handle websockets closing. * * Nothing to do here for WebSockets */ _onClose: function(e) { if(this._conn.connected && !this._conn.disconnecting) { Strophe.error("Websocket closed unexpectedly"); this._conn._doDisconnect(); } else if (e && e.code === 1006 && !this._conn.connected && this.socket) { // in case the onError callback was not called (Safari 10 does not // call onerror when the initial connection fails) we need to // dispatch a CONNFAIL status update to be consistent with the // behavior on other browsers. Strophe.error("Websocket closed unexcectedly"); this._conn._changeConnectStatus( Strophe.Status.CONNFAIL, "The WebSocket connection could not be established or was disconnected." ); this._conn._doDisconnect(); } else { Strophe.info("Websocket closed"); } }, /** PrivateFunction: _no_auth_received * * Called on stream start/restart when no stream:features * has been received. */ _no_auth_received: function (_callback) { Strophe.error("Server did not send any auth methods"); this._conn._changeConnectStatus( Strophe.Status.CONNFAIL, "Server did not send any auth methods" ); if (_callback) { _callback = _callback.bind(this._conn); _callback(); } this._conn._doDisconnect(); }, /** PrivateFunction: _onDisconnectTimeout * _Private_ timeout handler for handling non-graceful disconnection. * * This does nothing for WebSockets */ _onDisconnectTimeout: function () {}, /** PrivateFunction: _abortAllRequests * _Private_ helper function that makes sure all pending requests are aborted. */ _abortAllRequests: function () {}, /** PrivateFunction: _onError * _Private_ function to handle websockets errors. * * Parameters: * (Object) error - The websocket error. */ _onError: function(error) { Strophe.error("Websocket error " + error); this._conn._changeConnectStatus( Strophe.Status.CONNFAIL, "The WebSocket connection could not be established or was disconnected." ); this._disconnect(); }, /** PrivateFunction: _onIdle * _Private_ function called by Strophe.Connection._onIdle * * sends all queued stanzas */ _onIdle: function () { var data = this._conn._data; if (data.length > 0 && !this._conn.paused) { for (var i = 0; i < data.length; i++) { if (data[i] !== null) { var stanza, rawStanza; if (data[i] === "restart") { stanza = this._buildStream().tree(); } else { stanza = data[i]; } rawStanza = Strophe.serialize(stanza); this._conn.xmlOutput(stanza); this._conn.rawOutput(rawStanza); this.socket.send(rawStanza); } } this._conn._data = []; } }, /** PrivateFunction: _onMessage * _Private_ function to handle websockets messages. * * This function parses each of the messages as if they are full documents. * [TODO : We may actually want to use a SAX Push parser]. * * Since all XMPP traffic starts with * * * The first stanza will always fail to be parsed. * * Additionally, the seconds stanza will always be with * the stream NS defined in the previous stanza, so we need to 'force' * the inclusion of the NS in this stanza. * * Parameters: * (string) message - The websocket message. */ _onMessage: function(message) { var elem, data; // check for closing stream var close = ''; if (message.data === close) { this._conn.rawInput(close); this._conn.xmlInput(message); if (!this._conn.disconnecting) { this._conn._doDisconnect(); } return; } else if (message.data.search(" tag before we close the connection return; } this._conn._dataRecv(elem, message.data); }, /** PrivateFunction: _onOpen * _Private_ function to handle websockets connection setup. * * The opening stream tag is sent here. */ _onOpen: function() { Strophe.info("Websocket open"); var start = this._buildStream(); this._conn.xmlOutput(start.tree()); var startString = Strophe.serialize(start); this._conn.rawOutput(startString); this.socket.send(startString); }, /** PrivateFunction: _reqToData * _Private_ function to get a stanza out of a request. * * WebSockets don't use requests, so the passed argument is just returned. * * Parameters: * (Object) stanza - The stanza. * * Returns: * The stanza that was passed. */ _reqToData: function (stanza) { return stanza; }, /** PrivateFunction: _send * _Private_ part of the Connection.send function for WebSocket * * Just flushes the messages that are in the queue */ _send: function () { this._conn.flush(); }, /** PrivateFunction: _sendRestart * * Send an xmpp:restart stanza. */ _sendRestart: function () { clearTimeout(this._conn._idleTimeout); this._conn._onIdle.bind(this._conn)(); } }; return Strophe; })); (function(root){ if(typeof define === 'function' && define.amd){ define('strophe',[ "strophe-core", "strophe-bosh", "strophe-websocket" ], function (wrapper) { return wrapper; }); } })(this); require(["strophe-polyfill"]); /* jshint ignore:start */ //The modules for your project will be inlined above //this snippet. Ask almond to synchronously require the //module value for 'main' here and return it as the //value to use for the public API for the built file. return require('strophe'); })); /* jshint ignore:end */ strophejs-1.2.14+dfsg/tests/000077500000000000000000000000001320017573300157025ustar00rootroot00000000000000strophejs-1.2.14+dfsg/tests/index.html000066400000000000000000000013171320017573300177010ustar00rootroot00000000000000 Strophe.js Tests

Strophe.js Tests

    strophejs-1.2.14+dfsg/tests/main.js000066400000000000000000000007011320017573300171620ustar00rootroot00000000000000config.baseUrl = '../'; config.paths.jquery = "node_modules/jquery/dist/jquery"; config.paths.sinon = "node_modules/sinon/pkg/sinon"; config.paths["sinon-qunit"] = "node_modules/sinon-qunit/lib/sinon-qunit"; config.paths.tests = "tests/tests"; config.shim = { 'sinon-qunit': { deps: ['sinon']} }; require.config(config); require(["tests", "strophe-polyfill"], function(tests) { QUnit.start(); tests.run(); }); strophejs-1.2.14+dfsg/tests/tests.js000066400000000000000000001216001320017573300174020ustar00rootroot00000000000000define([ 'jquery', 'sinon', 'sinon-qunit', 'strophe' ], function($, sinon, sinon_qunit, wrapper) { var run = function () { var $build = wrapper.$build; var $iq = wrapper.$iq; var $msg = wrapper.$msg; var $pres = wrapper.$pres; var Strophe = wrapper.Strophe; module("Utility Methods"); test("validTag", function () { /* Utility method to determine whether a tag is allowed * in the XHTML_IM namespace. * * Used in the createHtml function to filter incoming html into the allowed XHTML-IM subset. * See http://xmpp.org/extensions/xep-0071.html#profile-summary for the list of recommended */ // Tags must always be lower case (as per XHMTL) equal(Strophe.XHTML.validTag('BODY'), false); equal(Strophe.XHTML.validTag('A'), false); equal(Strophe.XHTML.validTag('Img'), false); equal(Strophe.XHTML.validTag('IMg'), false); // Check all tags mentioned in XEP-0071 equal(Strophe.XHTML.validTag('a'), true); equal(Strophe.XHTML.validTag('blockquote'), true); equal(Strophe.XHTML.validTag('body'), true); equal(Strophe.XHTML.validTag('br'), true); equal(Strophe.XHTML.validTag('cite'), true); equal(Strophe.XHTML.validTag('em'), true); equal(Strophe.XHTML.validTag('img'), true); equal(Strophe.XHTML.validTag('li'), true); equal(Strophe.XHTML.validTag('ol'), true); equal(Strophe.XHTML.validTag('p'), true); equal(Strophe.XHTML.validTag('span'), true); equal(Strophe.XHTML.validTag('strong'), true); equal(Strophe.XHTML.validTag('ul'), true); // Check tags not mentioned in XEP-0071 equal(Strophe.XHTML.validTag('script'), false); equal(Strophe.XHTML.validTag('blink'), false); equal(Strophe.XHTML.validTag('article'), false); }); test("_getRequestStatus", function () { var conn = new Strophe.Connection("http://example.org"); var req = new Strophe.Request('', function(){}); req.xhr = { 'status': 200, 'readyState': 4 }; equal(conn._proto._getRequestStatus(req), 200, "Returns the status"); req.xhr = { 'status': 500, 'readyState': 4 }; equal(conn._proto._getRequestStatus(req), 500, "Returns the default if the request is not finished yet"); req.xhr = { 'status': 200, 'readyState': 3 }; equal(conn._proto._getRequestStatus(req), 0, "Returns the default if the request is not finished yet"); req.xhr = { 'readyState': 4 }; equal(conn._proto._getRequestStatus(req, -1), -1, "Returns the default if the request doesn't have a status"); equal(conn._proto._getRequestStatus(req, 0), 0, "Returns the default if the request doesn't have a status"); }); module("JIDs"); test("Normal JID", function () { var jid = "darcy@pemberley.lit/library"; equal(Strophe.getNodeFromJid(jid), "darcy", "Node should be 'darcy'"); equal(Strophe.getDomainFromJid(jid), "pemberley.lit", "Domain should be 'pemberley.lit'"); equal(Strophe.getResourceFromJid(jid), "library", "Node should be 'library'"); equal(Strophe.getBareJidFromJid(jid), "darcy@pemberley.lit", "Bare JID should be 'darcy@pemberley.lit'"); }); test("Weird node (unescaped)", function () { var jid = "darcy@netherfield.lit@pemberley.lit/library"; equal(Strophe.getNodeFromJid(jid), "darcy", "Node should be 'darcy'"); equal(Strophe.getDomainFromJid(jid), "netherfield.lit@pemberley.lit", "Domain should be 'netherfield.lit@pemberley.lit'"); equal(Strophe.getResourceFromJid(jid), "library", "Resource should be 'library'"); equal(Strophe.getBareJidFromJid(jid), "darcy@netherfield.lit@pemberley.lit", "Bare JID should be 'darcy@netherfield.lit@pemberley.lit'"); }); test("Weird node (escaped)", function () { var escapedNode = Strophe.escapeNode("darcy@netherfield.lit"); var jid = escapedNode + "@pemberley.lit/library"; equal(Strophe.getNodeFromJid(jid), "darcy\\40netherfield.lit", "Node should be 'darcy\\40netherfield.lit'"); equal(Strophe.getDomainFromJid(jid), "pemberley.lit", "Domain should be 'pemberley.lit'"); equal(Strophe.getResourceFromJid(jid), "library", "Resource should be 'library'"); equal(Strophe.getBareJidFromJid(jid), "darcy\\40netherfield.lit@pemberley.lit", "Bare JID should be 'darcy\\40netherfield.lit@pemberley.lit'"); }); test("Weird resource", function () { var jid = "books@chat.pemberley.lit/darcy@pemberley.lit/library"; equal(Strophe.getNodeFromJid(jid), "books", "Node should be 'books'"); equal(Strophe.getDomainFromJid(jid), "chat.pemberley.lit", "Domain should be 'chat.pemberley.lit'"); equal(Strophe.getResourceFromJid(jid), "darcy@pemberley.lit/library", "Resource should be 'darcy@pemberley.lit/library'"); equal(Strophe.getBareJidFromJid(jid), "books@chat.pemberley.lit", "Bare JID should be 'books@chat.pemberley.lit'"); }); module("Builder"); test("The root() method", function () { var builder = new Strophe.Builder('root'); var el = builder.c('child').c('grandchild').c('greatgrandchild').root(); equal(el.node.nodeName, 'root', 'root() jump back to the root of the tree'); }); test("Correct namespace (#32)", function () { var stanzas = [new Strophe.Builder("message", {foo: "asdf"}).tree(), $build("iq", {}).tree(), $pres().tree()]; $.each(stanzas, function () { equal($(this).attr('xmlns'), Strophe.NS.CLIENT, "Namespace should be '" + Strophe.NS.CLIENT + "'"); }); }); test("Strophe.Connection.prototype.send() accepts Builders (#27)", function () { var stanza = $pres(); var conn = new Strophe.Connection(""); var sendStub = sinon.stub(XMLHttpRequest.prototype, "send"); var timeoutStub = sinon.stub(window, "setTimeout", function (func) { // Stub setTimeout to immediately call functions, otherwise our // assertions fail due to async execution. func.apply(arguments); }); conn.send(stanza); equal(sendStub.called, true, "XMLHttpRequest.send was called"); sendStub.restore(); timeoutStub.restore(); }); module("Strophe.Connection options"); test("withCredentials can be set on the XMLHttpRequest object", function () { var stanza = $pres(); // Stub XMLHttpRequest.protototype.send so that it doesn't // actually try to send out the request. var sendStub = sinon.stub(XMLHttpRequest.prototype, "send"); // Stub setTimeout to immediately call functions, otherwise our // assertions fail due to async execution. var timeoutStub = sinon.stub(window, "setTimeout", function (func) { func.apply(arguments); }); var conn = new Strophe.Connection("example.org"); conn.send(stanza); equal(sendStub.called, true); equal(sendStub.getCalls()[0].thisValue.withCredentials, false); conn = new Strophe.Connection( "example.org", { "withCredentials": true }); conn.send(stanza); equal(sendStub.called, true); equal(sendStub.getCalls()[1].thisValue.withCredentials, true); sendStub.restore(); timeoutStub.restore(); }); test("content type can be set on the XMLHttpRequest object", function () { var stanza = $pres(); // Stub XMLHttpRequest.protototype.send so that it doesn't // actually try to send out the request. var sendStub = sinon.stub(XMLHttpRequest.prototype, "send"); // Stub setTimeout to immediately call functions, otherwise our // assertions fail due to async execution. var timeoutStub = sinon.stub(window, "setTimeout", function (func) { func.apply(arguments); }); var setRetRequestHeaderStub = sinon.stub(XMLHttpRequest.prototype, "setRequestHeader"); var conn = new Strophe.Connection("example.org"); conn.send(stanza); equal(setRetRequestHeaderStub.getCalls()[0].args[0], "Content-Type"); equal(setRetRequestHeaderStub.getCalls()[0].args[1], "text/xml; charset=utf-8"); conn = new Strophe.Connection( "example.org", { contentType: "text/plain; charset=utf-8" }); conn.send(stanza); equal(setRetRequestHeaderStub.getCalls()[1].args[0], "Content-Type"); equal(setRetRequestHeaderStub.getCalls()[1].args[1], "text/plain; charset=utf-8"); sendStub.restore(); timeoutStub.restore(); setRetRequestHeaderStub.restore(); }); test("Cookies can be added to the document passing them as options to Strophe.Connection", function () { var stanza = $pres(); var conn = new Strophe.Connection( "localhost", { "cookies": { "_xxx": { "value": "1234", "path": "/", } } }); notEqual(document.cookie.indexOf('_xxx'), -1); var start = document.cookie.indexOf('_xxx'); var end = document.cookie.indexOf(";", start); end = end == -1 ? document.cookie.length : end; equal(document.cookie.substring(start, end), '_xxx=1234'); // Also test when passing only a string conn = new Strophe.Connection( "localhost", { "cookies": { "_yyy": "4321" }, "withCredentials": true }); notEqual(document.cookie.indexOf('_yyy'), -1); start = document.cookie.indexOf('_yyy'); end = document.cookie.indexOf(";", start); end = end == -1 ? document.cookie.length : end; equal(document.cookie.substring(start, end), '_yyy=4321'); // Stub XMLHttpRequest.protototype.send so that it doesn't // actually try to send out the request. var sendStub = sinon.stub(XMLHttpRequest.prototype, "send"); // Stub setTimeout to immediately call functions, otherwise our // assertions fail due to async execution. var timeoutStub = sinon.stub(window, "setTimeout", function (func) { func.apply(arguments); }); conn.send(stanza); // Unfortunately there's no way to test the headers set in the // request (only in the response). They can however be checked with // the browser's developer tools. equal(sendStub.called, true); sendStub.restore(); timeoutStub.restore(); }); test("send() does not accept strings", function () { var stanza = ""; var conn = new Strophe.Connection(""); // fake connection callback to avoid errors conn.connect_callback = function () {}; expect(1); try { conn.send(stanza); } catch (e) { equal(e.name, "StropheError", "send() should throw exception"); } }); test("Builder with XML attribute escaping test", function () { var text = ""; var expected = ""; var pres = $pres({to: text}); equal(pres.toString(), expected, "< should be escaped"); text = "foo&bar"; expected = ""; pres = $pres({to: text}); equal(pres.toString(), expected, "& should be escaped"); }); test("c() accepts text and passes it to xmlElement", function () { var pres = $pres({from: "darcy@pemberley.lit", to: "books@chat.pemberley.lit"}) .c("nick", {xmlns: "http://jabber.org/protocol/nick"}, "Darcy"); var expected = ""+ "Darcy"+ ""; equal(pres.toString(), expected, "'Darcy' should be a child of "); }); test("c() return the child element if it is a text node.", function () { // See this issue: https://github.com/strophe/strophejs/issues/124 var pres = $pres({from: "darcy@pemberley.lit", to: "books@chat.pemberley.lit"}) .c("show", {}, "dnd") .c("status", {}, "In a meeting"); var expected = ""+ "dndIn a meeting"+ ""; equal(pres.toString(), expected, ""); pres = $pres({from: "darcy@pemberley.lit", to: "books@chat.pemberley.lit"}) .c("show", {}, "") .c("status", {}, ""); expected = ""+ ""+ ""; equal(pres.toString(), expected, ""); }); module("XML"); test("XML escaping test", function () { var text = "s & p"; var textNode = Strophe.xmlTextNode(text); equal(Strophe.getText(textNode), "s & p", "should be escaped"); var text0 = "s < & > p"; var textNode0 = Strophe.xmlTextNode(text0); equal(Strophe.getText(textNode0), "s < & > p", "should be escaped"); var text1 = "s's or \"p\""; var textNode1 = Strophe.xmlTextNode(text1); equal(Strophe.getText(textNode1), "s's or "p"", "should be escaped"); var text2 = "]]>"; var textNode2 = Strophe.xmlTextNode(text2); equal(Strophe.getText(textNode2), "<![CDATA[<foo>]]>", "should be escaped"); var text3 = "]]>"; var textNode3 = Strophe.xmlTextNode(text3); equal(Strophe.getText(textNode3), "<![CDATA[]]]]><![CDATA[>]]>", "should be escaped"); var text4 = "<foo>]]>"; var textNode4 = Strophe.xmlTextNode(text4); equal(Strophe.getText(textNode4), "&lt;foo&gt;<![CDATA[<foo>]]>", "should be escaped"); }); test("XML element creation", function () { var elem = Strophe.xmlElement("message"); equal(elem.tagName, "message", "Element name should be the same"); }); test("copyElement() double escape bug", function() { var cloned = Strophe.copyElement(Strophe.xmlGenerator() .createTextNode('<><>')); equal(cloned.nodeValue, '<><>'); }); test("XML serializing", function() { var parser = new DOMParser(); // Attributes var element1 = parser.parseFromString("bar","text/xml").documentElement; equal(Strophe.serialize(element1), "bar", "should be serialized"); var element2 = parser.parseFromString("bar","text/xml").documentElement; equal(Strophe.serialize(element2), "bar", "should be serialized"); // Escaping values var element3 = parser.parseFromString("a > 'b' & "b" < c","text/xml").documentElement; equal(Strophe.serialize(element3), "a > 'b' & "b" < c", "should be serialized"); // Escaping attributes var element4 = parser.parseFromString("bar","text/xml").documentElement; equal(Strophe.serialize(element4), "bar", "should be serialized"); var element5 = parser.parseFromString(" "b"\">bar","text/xml").documentElement; equal(Strophe.serialize(element5), "bar", "should be serialized"); // Empty elements var element6 = parser.parseFromString("","text/xml").documentElement; equal(Strophe.serialize(element6), "", "should be serialized"); // Children var element7 = parser.parseFromString("ab","text/xml").documentElement; equal(Strophe.serialize(element7), "ab", "should be serialized"); var element8 = parser.parseFromString("abcd","text/xml").documentElement; equal(Strophe.serialize(element8), "abcd", "should be serialized"); // CDATA var element9 = parser.parseFromString("]]>","text/xml").documentElement; equal(Strophe.serialize(element9), "]]>", "should be serialized"); var element10 = parser.parseFromString("]]>","text/xml").documentElement; equal(Strophe.serialize(element10), "]]>", "should be serialized"); var element11 = parser.parseFromString("<foo>]]>","text/xml").documentElement; equal(Strophe.serialize(element11), "<foo>]]>", "should be serialized"); }); module("Handler"); test("HTTP errors", function () { var spy500 = sinon.spy(); var spy401 = sinon.spy(); var conn = new Strophe.Connection("http://fake"); conn.addProtocolErrorHandler('HTTP', 500, spy500); conn.addProtocolErrorHandler('HTTP', 401, spy401); var req = new Strophe.Request('', function(){}); req.xhr = { 'status': 200, 'readyState': 4 }; conn._proto._onRequestStateChange(function () {}, req); equal(spy500.called, false, "Error handler does not get called when no HTTP error"); equal(spy401.called, false, "Error handler does not get called when no HTTP error"); req.xhr = { 'status': 401, 'readyState': 4 }; conn._proto._onRequestStateChange(function () {}, req); equal(spy500.called, false, "Error handler does not get called when no HTTP 500 error"); equal(spy401.called, true, "Error handler does get called when HTTP 401 error"); req.xhr = { 'status': 500, 'readyState': 4 }; conn._proto._onRequestStateChange(function () {}, req); equal(spy500.called, true, "Error handler gets called on HTTP 500 error"); }); test("Full JID matching", function () { var elem = $msg({from: 'darcy@pemberley.lit/library'}).tree(); var hand = new Strophe.Handler(null, null, null, null, null, 'darcy@pemberley.lit/library'); equal(hand.isMatch(elem), true, "Full JID should match"); hand = new Strophe.Handler(null, null, null, null, null, 'darcy@pemberley.lit'); equal(hand.isMatch(elem), false, "Bare JID shouldn't match"); }); test("Bare JID matching", function () { var elem = $msg({from: 'darcy@pemberley.lit/library'}).tree(); var hand = new Strophe.Handler(null, null, null, null, null, 'darcy@pemberley.lit/library', {matchBareFromJid: true}); equal(hand.isMatch(elem), true, "Full JID should match"); hand = new Strophe.Handler(null, null, null, null, null, 'darcy@pemberley.lit', {matchBareFromJid: true}); equal(hand.isMatch(elem), true, "Bare JID should match"); }); test("Namespace matching", function () { var elemNoFrag = $msg({xmlns: 'http://jabber.org/protocol/muc'}).tree(); var elemWithFrag = $msg({xmlns: 'http://jabber.org/protocol/muc#user'}).tree(); var hand = new Strophe.Handler( null, 'http://jabber.org/protocol/muc', null, null, null, null ); equal(hand.isMatch(elemNoFrag), true, "The handler should match on stanza namespace"); equal(hand.isMatch(elemWithFrag), false, "The handler should not match on stanza namespace with fragment"); hand = new Strophe.Handler( null, 'http://jabber.org/protocol/muc', null, null, null, null, {'ignoreNamespaceFragment': true} ); equal(hand.isMatch(elemNoFrag), true, "The handler should match on stanza namespace"); equal(hand.isMatch(elemWithFrag), true, "The handler should match on stanza namespace, even with fragment"); }); test("Stanza name matching", function () { var elem = $iq().tree(); var hand = new Strophe.Handler(null, null, 'iq'); equal(hand.isMatch(elem), true, "The handler should match on stanza name"); hand = new Strophe.Handler(null, null, 'message'); notEqual(hand.isMatch(elem), true, "The handler should not match wrong stanza name"); }); test("Stanza type matching", function () { var elem = $iq({type: 'error'}).tree(); var hand = new Strophe.Handler(null, null, 'iq', 'error'); equal(hand.isMatch(elem), true, "The handler should match on stanza type"); hand = new Strophe.Handler(null, null, 'iq', 'result'); notEqual(hand.isMatch(elem), true, "The handler should not match wrong stanza type"); hand = new Strophe.Handler(null, null, 'iq', ['error', 'result']); equal(hand.isMatch(elem), true, "The handler should match if stanza type is in array of types"); }); module("Misc"); test("Quoting strings", function () { var input = '"beep \\40"'; var saslmd5 = new Strophe.SASLMD5(); var output = saslmd5._quote(input); equal(output, "\"\\\"beep \\\\40\\\"\"", "string should be quoted and escaped"); }); test("Function binding", function () { var spy = sinon.spy(); var obj = {}; var arg1 = "foo"; var arg2 = "bar"; var arg3 = "baz"; var f = spy.bind(obj, arg1, arg2); f(arg3); equal(spy.called, true, "bound function should be called"); equal(spy.calledOn(obj), true, "bound function should have correct context"); equal(spy.alwaysCalledWithExactly(arg1, arg2, arg3), true, "bound function should get all arguments"); }); test("Connfail for invalid XML", function () { var req = new Strophe.Request('', function(){}); req.xhr = { responseText: 'text' }; var conn = new Strophe.Connection("http://fake"); conn.connect_callback = function(status, condition) { if(status === Strophe.Status.CONNFAIL) { equal(condition, "bad-format", "connection should fail with condition bad-format"); } }; conn._connect_cb(req); }); module("XHR error handling"); // Note that these tests are pretty dependent on the actual code. test("Aborted requests do nothing", function () { Strophe.Connection.prototype._onIdle = function () {}; var conn = new Strophe.Connection("http://fake"); // simulate a finished but aborted request var req = {id: 43, sends: 1, xhr: { readyState: 4 }, abort: true}; conn._requests = [req]; var spy = sinon.spy(); conn._proto._onRequestStateChange(spy, req); equal(req.abort, false, "abort flag should be toggled"); equal(conn._requests.length, 1, "_requests should be same length"); equal(spy.called, false, "callback should not be called"); }); test("Incomplete requests do nothing", function () { Strophe.Connection.prototype._onIdle = function () {}; var conn = new Strophe.Connection("http://fake"); // simulate a finished but aborted request var req = {id: 44, sends: 1, xhr: { readyState: 3 }}; conn._requests = [req]; var spy = sinon.spy(); conn._proto._onRequestStateChange(spy, req); equal(conn._requests.length, 1, "_requests should be same length"); equal(spy.called, false, "callback should not be called"); }); module("SASL Mechanisms"); test("Default mechanisms will be registered if none are provided", function () { var conn = new Strophe.Connection('localhost'); equal(Object.keys(conn.mechanisms).length, 6, 'Six by default registered SASL mechanisms'); equal('ANONYMOUS' in conn.mechanisms, true, 'ANONYMOUS is registered'); equal('DIGEST-MD5' in conn.mechanisms, true, 'DIGEST-MD is registered'); equal('EXTERNAL' in conn.mechanisms, true, 'EXTERNAL is registered'); equal('OAUTHBEARER' in conn.mechanisms, true, 'OAUTHBEARER is registered'); equal('PLAIN' in conn.mechanisms, true, 'PLAIN is registered'); equal('SCRAM-SHA-1' in conn.mechanisms, true, 'SCRAM-SHA-1 is registered'); }); test("Custom mechanisms be specified when instantiating Strophe.Connection", function () { var SASLFoo = function() {}; SASLFoo.prototype = new Strophe.SASLMechanism("FOO", false, 10); var conn = new Strophe.Connection('localhost', {'mechanisms': [SASLFoo]}); equal(Object.keys(conn.mechanisms).length, 1, 'Only one registered SASL mechanism'); equal('FOO' in conn.mechanisms, true, 'FOO is registered'); notEqual('PLAIN' in conn.mechanisms, true, 'PLAIN is not registered'); conn = new Strophe.Connection('localhost', { 'mechanisms': [ SASLFoo, Strophe.SASLPlain ]}); equal(Object.keys(conn.mechanisms).length, 2, 'Only two registered SASL mechanisms'); equal('FOO' in conn.mechanisms, true, 'FOO is registered'); equal('PLAIN' in conn.mechanisms, true, 'PLAIN is registered'); }); test("The supported mechanism with the highest priority will be used", function () { Strophe.SASLExternal.prototype.priority = 10; Strophe.SASLSHA1.prototype.priority = 20; conn = new Strophe.Connection('localhost', { 'mechanisms': [ Strophe.SASLSHA1, Strophe.SASLExternal ]}); var authSpy = sinon.spy(conn, '_attemptSASLAuth'); equal(authSpy.called, false); conn.connect('dummy@localhost', 'secret'); conn.authenticate([Strophe.SASLSHA1, Strophe.SASLExternal]); equal(authSpy.called, true); equal(authSpy.returnValues.length, 1); equal(authSpy.returnValues[0], true); equal(conn._sasl_mechanism.name, 'SCRAM-SHA-1'); Strophe.SASLExternal.prototype.priority = 30; Strophe.SASLSHA1.prototype.priority = 20; conn.connect('dummy@localhost', 'secret'); conn.authenticate([Strophe.SASLSHA1, Strophe.SASLExternal]); equal(conn._sasl_mechanism.name, 'EXTERNAL'); }); test("SASL PLAIN Auth", function () { var conn = {pass: "password", authcid: "user", authzid: "user@xmpp.org"}; var saslplain = new Strophe.SASLPlain(); saslplain.onStart(conn); ok(saslplain.test(conn), "PLAIN is enabled by default."); var response = saslplain.onChallenge(conn, null); equal(response, [conn.authzid, conn.authcid, conn.pass].join("\u0000"), "checking plain auth challenge"); saslplain.onSuccess(); }); test("SASL SCRAM-SHA-1 Auth", function () { /* This is a simple example of a SCRAM-SHA-1 authentication exchange * when the client doesn't support channel bindings (username 'user' and * password 'pencil' are used): * * C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL * S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92, * i=4096 * C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j, * p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts= * S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ= * */ var conn = {pass: "pencil", authcid: "user", authzid: "user@xmpp.org", _sasl_data: []}; var saslsha1 = new Strophe.SASLSHA1(); saslsha1.onStart(conn); ok(saslsha1.test(conn), "SHA-1 is enabled by default."); // test taken from example section on: // URL: http://tools.ietf.org/html/rfc5802#section-5 var response = saslsha1.onChallenge(conn, null, "fyko+d2lbbFgONRv9qkxdawL"); equal(response, "n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL", "checking first auth challenge"); response = saslsha1.onChallenge(conn, "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096"); equal(response, "c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=", "checking second auth challenge"); saslsha1.onSuccess(); }); test("SASL DIGEST-MD-5 Auth", function () { var conn = {pass: "secret", authcid: "chris", authzid: "user@xmpp.org", servtype: "imap", domain: "elwood.innosoft.com", _sasl_data: []}; var saslmd5 = new Strophe.SASLMD5(); saslmd5.onStart(conn); ok(saslmd5.test(conn), "DIGEST MD-5 is enabled by default."); // test taken from example section on: // URL: http://www.ietf.org/rfc/rfc2831.txt var response = saslmd5.onChallenge(conn, "realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",qop=\"auth\",algorithm=md5-sess,charset=utf-8", "OA6MHXh6VqTrRk"); equal(response, "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",nc=00000001,cnonce=\"OA6MHXh6VqTrRk\",digest-uri=\"imap/elwood.innosoft.com\",response=d388dad90d4bbd760a152321f2143af7,qop=auth", "checking first auth challenge"); response = saslmd5.onChallenge(conn, "rspauth=ea40f60335c427b5527b84dbabcdfffd"); equal(response, "", "checking second auth challenge"); saslmd5.onSuccess(); }); test("SASL EXTERNAL Auth", function () { var conn = {pass: "password", authcid: "user", authzid: "user@xmpp.org"}; var sasl_external = new Strophe.SASLExternal(); ok(sasl_external.test(conn), "EXTERNAL is enabled by default."); sasl_external.onStart(conn); var response = sasl_external.onChallenge(conn, null); equal(response, conn.authzid, "Response to EXTERNAL auth challenge should be authzid if different authcid was passed in."); sasl_external.onSuccess(); conn = {pass: "password", authcid: "user", authzid: "user@xmpp.org"}; sasl_external = new Strophe.SASLExternal(); ok(sasl_external.test(conn), "EXTERNAL is enabled by default."); sasl_external.onStart(conn); response = sasl_external.onChallenge(conn, null); equal(response, conn.authzid, "Response to EXTERNAL auth challenge should be empty string if authcid = authzid"); sasl_external.onSuccess(); }); module("BOSH Session resumption"); test("When passing in {keepalive: true} to Strophe.Connection, then the session tokens get cached automatically", function () { var conn = new Strophe.Connection("", {"keepalive": true}); conn.jid = 'dummy@localhost'; conn._proto.sid = "5332346"; var cacheSpy = sinon.spy(conn._proto, '_cacheSession'); equal(cacheSpy.called, false); conn._proto._buildBody(); equal(cacheSpy.called, true); equal(window.sessionStorage.getItem('strophe-bosh-session'), null); conn.authenticated = true; conn._proto._buildBody(); ok(window.sessionStorage.getItem('strophe-bosh-session')); equal(cacheSpy.called, true); conn.authenticated = false; conn._proto._buildBody(); equal(window.sessionStorage.getItem('strophe-bosh-session'), null); equal(cacheSpy.called, true); }); test('the request ID (RID) has the proper value whenever a session is restored', function () { window.sessionStorage.removeItem('strophe-bosh-session'); var conn = new Strophe.Connection("", {"keepalive": true}); conn.authenticated = true; conn.jid = 'dummy@localhost'; conn._proto.rid = '123456'; conn._proto.sid = '987654321'; conn._proto._cacheSession(); delete conn._proto.rid; conn.restore(); var body = conn._proto._buildBody(); equal(body.tree().getAttribute('rid'), '123456'); body = conn._proto._buildBody(); equal(body.tree().getAttribute('rid'), '123457'); body = conn._proto._buildBody(); equal(body.tree().getAttribute('rid'), '123458'); delete conn._proto.rid; conn.restore(); body = conn._proto._buildBody(); equal(body.tree().getAttribute('rid'), '123459'); }); test("restore can only be called with BOSH and when {keepalive: true} is passed to Strophe.Connection", function () { var conn = new Strophe.Connection(""); var boshSpy = sinon.spy(conn._proto, "_restore"); var checkSpy = sinon.spy(conn, "_sessionCachingSupported"); equal(conn.restored, false); try { conn.restore(); } catch (e) { equal(e.name, "StropheSessionError", "conn.restore() should throw an exception when keepalive is false."); equal(e.message, "_restore: no restoreable session.", "conn.restore() should throw an exception when keepalive is false"); } equal(boshSpy.called, true); equal(checkSpy.called, true); conn = new Strophe.Connection("ws:localhost"); try { conn.restore(); } catch (e) { equal(e.name, "StropheSessionError", "conn.restore() should throw an exception when keepalive is false."); equal(e.message, 'The "restore" method can only be used with a BOSH connection.', 'The conn.restore method can only be used with a BOSH connection.'); } equal(conn.restored, false); }); test('the _cacheSession method caches the BOSH session tokens', function () { window.sessionStorage.removeItem('strophe-bosh-session'); var conn = new Strophe.Connection("http://fake", {"keepalive": true}); // Nothing gets cached if there aren't tokens to cache conn._proto._cacheSession(); equal(window.sessionStorage.getItem('strophe-bosh-session'), null); // Let's create some tokens to cache conn.authenticated = true; conn.jid = 'dummy@localhost'; conn._proto.rid = '123456'; conn._proto.sid = '987654321'; equal(window.sessionStorage.getItem('strophe-bosh-session'), null); conn._proto._cacheSession(); notEqual(window.sessionStorage.getItem('strophe-bosh-session'), null); }); test('when calling "restore" without a restorable session, an exception is raised', function () { window.sessionStorage.removeItem('strophe-bosh-session'); var conn = new Strophe.Connection("", {"keepalive": true}); var boshSpy = sinon.spy(conn._proto, "_restore"); var checkSpy = sinon.spy(conn, "_sessionCachingSupported"); equal(conn.restored, false); try { conn.restore(); } catch (e) { equal(e.name, "StropheSessionError"); equal(e.message, "_restore: no restoreable session."); } equal(conn.restored, false); equal(boshSpy.called, true); equal(checkSpy.called, true); }); test('"restore" takes an optional JID argument for more precise session verification', function () { window.sessionStorage.removeItem('strophe-bosh-session'); var conn = new Strophe.Connection("", {"keepalive": true}); var boshSpy = sinon.spy(conn._proto, "_restore"); var checkSpy = sinon.spy(conn, "_sessionCachingSupported"); // Let's create some tokens to cache conn.authenticated = true; conn.jid = 'dummy@localhost'; conn._proto.rid = '1234567'; conn._proto.sid = '9876543210'; conn._proto._cacheSession(); // Check that giving a different jid causes an exception to be // raised. try { conn.restore('differentdummy@localhost'); } catch (e) { equal(e.name, "StropheSessionError"); equal(e.message, "_restore: no restoreable session."); } equal(conn.restored, false); equal(boshSpy.called, true); equal(checkSpy.called, true); // Check that passing in the right jid but with a resource is not a problem. conn.restore('dummy@localhost/with_resource'); equal(conn.jid,'dummy@localhost'); equal(conn._proto.rid,'1234567'); equal(conn._proto.sid,'9876543210'); equal(conn.restored, true); }); test('when calling "restore" with a restorable session, bosh._attach is called with the session tokens', function () { window.sessionStorage.removeItem('strophe-bosh-session'); var conn = new Strophe.Connection("", {"keepalive": true}); conn.authenticated = true; conn.jid = 'dummy@localhost'; conn._proto.rid = '123456'; conn._proto.sid = '987654321'; conn._proto._cacheSession(); delete conn._proto.rid; delete conn._proto.sid; delete conn._proto.jid; equal(conn.restored, false); var boshSpy = sinon.spy(conn._proto, "_restore"); var checkSpy = sinon.spy(conn, "_sessionCachingSupported"); var attachSpsy = sinon.spy(conn._proto, "_attach"); conn.restore(); equal(conn.jid,'dummy@localhost'); equal(conn._proto.rid,'123456'); equal(conn._proto.sid,'987654321'); equal(conn.restored, true); equal(boshSpy.called, true); equal(checkSpy.called, true); equal(attachSpsy.called, true); }); module("BOSH next valid request id"); test("nextValidRid is called after successful request", function () { Strophe.Connection.prototype._onIdle = function () {}; var conn = new Strophe.Connection("http://fake"); var spy = sinon.spy(conn, 'nextValidRid'); var req = {id: 43, sends: 1, xhr: { readyState: 4, status: 200 }, rid: 42 }; conn._requests = [req]; conn._proto._onRequestStateChange(function(){}, req); equal(spy.calledOnce, true, "nextValidRid was called only once"); equal(spy.calledWith(43), true, "The RID was valid"); }); test("nextValidRid is not called after failed request", function () { Strophe.Connection.prototype._onIdle = function () {}; var conn = new Strophe.Connection("http://fake"); var spy = sinon.spy(conn, 'nextValidRid'); var req = {id: 43, sends: 1, xhr: { readyState: 4, status: 0 }, rid: 42 }; conn._requests = [req]; conn._proto._onRequestStateChange(function(){}, req); equal(spy.called, false, "nextValidRid was not called"); }); test("nextValidRid is called after failed request with disconnection", function () { sinon.stub(Math, "random", function(){ return 1; }); Strophe.Connection.prototype._onIdle = function () {}; var conn = new Strophe.Connection("http://fake"); var spy = sinon.spy(conn, 'nextValidRid'); var req = {id: 43, sends: 1, xhr: { readyState: 4, status: 404 }, rid: 42 }; conn._requests = [req]; conn._proto._onRequestStateChange(function(){}, req); equal(spy.calledOnce, true, "nextValidRid was called only once"); equal(spy.calledWith(4294967295), true, "The RID was valid"); Math.random.restore(); }); test("nextValidRid is called after connection reset", function () { sinon.stub(Math, "random", function(){ return 1; }); Strophe.Connection.prototype._onIdle = function () {}; var conn = new Strophe.Connection("http://fake"); var spy = sinon.spy(conn, 'nextValidRid'); conn.reset(); equal(spy.calledOnce, true, "nextValidRid was called only once"); equal(spy.calledWith(4294967295), true, "The RID was valid"); Math.random.restore(); }); }; return {run: run}; }); strophejs-1.2.14+dfsg/version.txt000066400000000000000000000000071320017573300167630ustar00rootroot000000000000001.2.14