├── .gitignore ├── CHANGELOG.txt ├── LICENSE.txt ├── Makefile ├── README.md ├── contrib └── discojs │ ├── README.txt │ ├── css │ └── disco.css │ ├── index.html │ ├── punjab.tac │ └── scripts │ ├── basic.js │ └── disco.js ├── examples ├── attach │ ├── README │ ├── __init__.py │ ├── attacher │ │ ├── __init__.py │ │ └── views.py │ ├── boshclient.py │ ├── manage.py │ ├── settings.py │ ├── templates │ │ └── attacher │ │ │ └── index.html │ └── urls.py ├── basic.html ├── basic.js ├── crossdomain.html ├── crossdomain.js ├── crossdomain.xml ├── echobot.html ├── echobot.js ├── prebind.html └── prebind.js ├── package.json ├── plugins └── strophe.flxhr.js ├── release_checklist.txt ├── src ├── base64.js ├── core.js ├── md5.js └── sha1.js └── tests ├── muc.html ├── muc.js ├── pubsub.html ├── pubsub.js ├── strophe.html ├── testrunner.js ├── tests.js └── testsuite.css /.gitignore: -------------------------------------------------------------------------------- 1 | version.txt 2 | strophe.js 3 | strophe.min.js 4 | ndproj 5 | doc 6 | .taperc 7 | plugins/*.min.js 8 | *.tar.gz 9 | *.zip 10 | *~ -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | # Strophe.js Change Log 2 | 3 | ## Version 1.0.2 - 2011-06-19 4 | 5 | * Fix security bug where DIGEST-MD5 client nonce was not properly 6 | randomized. 7 | * Fix double escaping in copyElement. 8 | * Fix IE errors related to importNode. 9 | * Add ability to pass text into Builder.c(). 10 | * Improve performance by skipping debugging callbacks when not 11 | overridden. 12 | * Wrap handler runs in try/catch so they don't affect or remove later 13 | handlers. 14 | * Add ' and " to escaped characters and other escaping fixes. 15 | * Fix _throttledRequestHandler to use proper window size. 16 | * Fix timed handler management. 17 | * Fix flXHR plugin to better deal with errors. 18 | * Fix bind() to be ECMAScript 5 compatible. 19 | * Use bosh.metajack.im in examples so they work out of the box. 20 | * Add simple XHR tests. 21 | * Update basic example to HTML5. 22 | * Move community plugins to their own repository. 23 | * Fix bug causing infinite retries. 24 | * Fix 5xx error handling. 25 | * Store stream:features for later use by plugins. 26 | * Fix to prevent passing stanzas during disconnect. 27 | * Fix handling of disconnect responses. 28 | * Fix getBareJidFromJid to return null on error. 29 | * Fix equality testing in matchers so that string literals and string 30 | objects both match. 31 | * Fix bare matching on missing from attributes. 32 | * Remove use of reserved word self. 33 | * Fix various documentation errors. 34 | 35 | ## Version 1.0.1 - 2010-01-27 36 | 37 | * Fix handling of window, hold, and wait attributes. Bug #75. 38 | 39 | ## Version 1.0 - 2010-01-01 40 | 41 | * First release. 42 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2009 Collecta, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=/bin/bash 2 | 3 | SRC_DIR = src 4 | DOC_DIR = doc 5 | PLUGIN_DIR = plugins 6 | NDPROJ_DIR = ndproj 7 | 8 | BASE_FILES = $(SRC_DIR)/base64.js \ 9 | $(SRC_DIR)/sha1.js \ 10 | $(SRC_DIR)/md5.js \ 11 | $(SRC_DIR)/core.js 12 | 13 | STROPHE = strophe.js 14 | STROPHE_MIN = strophe.min.js 15 | 16 | PLUGIN_FILES = $(shell ls $(PLUGIN_DIR)/strophe.*.js | grep -v min) 17 | PLUGIN_FILES_MIN = $(PLUGIN_FILES:.js=.min.js) 18 | 19 | DIST_FILES = LICENSE.txt README.txt contrib examples plugins tests doc \ 20 | $(STROPHE) $(STROPHE_MIN) 21 | 22 | VERSION = $(shell if [ -f version.txt ]; then cat version.txt; else VERSION=`git rev-list HEAD -n1`; echo $${VERSION:0:7}; fi) 23 | 24 | all: normal min 25 | 26 | normal: $(STROPHE) 27 | 28 | min: $(STROPHE_MIN) $(PLUGIN_FILES_MIN) 29 | 30 | $(STROPHE): $(BASE_FILES) 31 | @@echo "Building" $(STROPHE) "..." 32 | @@cat $(BASE_FILES) | sed -e 's/@VERSION@/$(VERSION)/' > $(STROPHE) 33 | @@echo $(STROPHE) "built." 34 | @@echo 35 | 36 | $(STROPHE_MIN): $(STROPHE) 37 | @@echo "Building" $(STROPHE_MIN) "..." 38 | ifdef YUI_COMPRESSOR 39 | @@java -jar $(YUI_COMPRESSOR) --type js --nomunge \ 40 | $(STROPHE) > $(STROPHE_MIN) 41 | @@echo $(STROPHE_MIN) "built." 42 | else 43 | @@echo $(STROPHE_MIN) "not built." 44 | @@echo " YUI Compressor required to build minified version." 45 | @@echo " Please set YUI_COMPRESSOR to the path to the jar file." 46 | endif 47 | @@echo 48 | 49 | %.min.js: %.js 50 | @@echo "Building" $@ "..." 51 | ifdef YUI_COMPRESSOR 52 | @@java -jar $(YUI_COMPRESSOR) --type js --nomunge \ 53 | $< > $@ 54 | @@echo $@ "built." 55 | else 56 | @@echo $@ "not built." 57 | @@echo " YUI Compressor required to build minified version." 58 | @@echo " Please set YUI_COMPRESSOR to the path to the jar file." 59 | endif 60 | @@echo 61 | 62 | doc: 63 | @@echo "Building Strophe documentation..." 64 | @@if [ ! -d $(NDPROJ_DIR) ]; then mkdir $(NDPROJ_DIR); fi 65 | @@if [ ! -d $(DOC_DIR) ]; then mkdir $(DOC_DIR); fi 66 | @@NaturalDocs -q -i $(SRC_DIR) -i $(PLUGINS_DIR) -o html $(DOC_DIR) -p $(NDPROJ_DIR) 67 | @@echo "Documentation built." 68 | @@echo 69 | 70 | release: normal min doc 71 | @@echo "Creating release packages..." 72 | @@mkdir strophejs-$(VERSION) 73 | @@cp -R $(DIST_FILES) strophejs-$(VERSION) 74 | @@tar czf strophejs-$(VERSION).tar.gz strophejs-$(VERSION) 75 | @@zip -qr strophejs-$(VERSION).zip strophejs-$(VERSION) 76 | @@rm -rf strophejs-$(VERSION) 77 | @@echo "Release created." 78 | @@echo 79 | 80 | clean: 81 | @@echo "Cleaning" $(STROPHE) "..." 82 | @@rm -f $(STROPHE) 83 | @@echo $(STROPHE) "cleaned." 84 | @@echo "Cleaning" $(STROPHE_MIN) "..." 85 | @@rm -f $(STROPHE_MIN) 86 | @@echo $(STROPHE_MIN) "cleaned." 87 | @@echo "Cleaning minified plugins..." 88 | @@rm -f $(PLUGIN_FILES_MIN) 89 | @@echo "Minified plugins cleaned." 90 | @@echo "Cleaning documentation..." 91 | @@rm -rf $(NDPROJ_DIR) $(DOC_DIR) 92 | @@echo "Documentation cleaned." 93 | @@echo 94 | 95 | .PHONY: all normal min doc release clean 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Repository Moved 2 | 3 | This repository has moved to https://github.com/strophe/strophejs 4 | -------------------------------------------------------------------------------- /contrib/discojs/README.txt: -------------------------------------------------------------------------------- 1 | Disco Dancing with XMPP 2 | 3 | There are many things one can do via XMPP. The list is 4 | endlist. But one thing that some forget about is discovering 5 | services a XMPP entity or server provides. In most cases a human or 6 | user does not care about this information and should not care. But 7 | you may have a website or web application that needs this 8 | information in order to decide what options to show to your 9 | users. You can do this very easily with JQuery, Strophe, and 10 | Punjab. 11 | 12 | We start with Punjab or a BOSH connection manager. This is 13 | needed so we can connect to a XMPP server. First, lets download 14 | punjab. 15 | 16 | svn co https://code.stanziq.com/svn/punjab/trunk punjab 17 | 18 | After we have punjab go into the directory and install punjab. 19 | 20 | cd punjab 21 | python setup.py install 22 | 23 | Then create a .tac file to configure Punjab. 24 | 25 | See punjab.tac 26 | 27 | Next, we will need Strophe. Lets download thelatest version from 28 | svn too. 29 | 30 | cd /directory/where/you/configured/punjab/html 31 | 32 | svn co https://code.stanziq.com/svn/strophe/trunk/strophejs 33 | 34 | In your html directory you will then begin to create your disco browser. 35 | 36 | Version 1 we take the basic example and modify it to do disco. 37 | 38 | Version 2 we add anonymous login 39 | 40 | Version 3 we make it pretty 41 | 42 | Version 4 we add handlers for different services 43 | -------------------------------------------------------------------------------- /contrib/discojs/css/disco.css: -------------------------------------------------------------------------------- 1 | #login .leftinput 2 | { 3 | margin-bottom: .5em; 4 | } 5 | 6 | #login input 7 | { 8 | margin-bottom: 10px; 9 | } 10 | 11 | #log { 12 | width: 100%; 13 | height: 200px; 14 | overflow: auto; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /contrib/discojs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | XMPP Disco Dancing 5 | 8 | 11 | 14 | 17 | 20 | 23 | 26 | 27 | 28 | 29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Status Log : 45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /contrib/discojs/punjab.tac: -------------------------------------------------------------------------------- 1 | # punjab tac file 2 | from twisted.web import server, resource, static 3 | from twisted.application import service, internet 4 | 5 | from punjab.httpb import Httpb, HttpbService 6 | 7 | root = static.File("./") # This needs to be the directory 8 | # where you will have your html 9 | # and javascript. 10 | 11 | b = resource.IResource(HttpbService(1)) # turn on debug with 1 12 | root.putChild('bosh', b) 13 | 14 | 15 | site = server.Site(root) 16 | 17 | application = service.Application("punjab") 18 | internet.TCPServer(5280, site).setServiceParent(application) 19 | -------------------------------------------------------------------------------- /contrib/discojs/scripts/basic.js: -------------------------------------------------------------------------------- 1 | var BOSH_SERVICE = 'http://localhost:5280/bosh'; 2 | 3 | var connection = null; 4 | var browser = null; 5 | var show_log = true; 6 | 7 | function log(msg) 8 | { 9 | $('#log').append('
').append(document.createTextNode(msg)); 10 | } 11 | 12 | 13 | function rawInput(data) 14 | { 15 | log('RECV: ' + data); 16 | } 17 | 18 | function rawOutput(data) 19 | { 20 | log('SENT: ' + data); 21 | } 22 | 23 | function onConnect(status) 24 | { 25 | if (status == Strophe.Status.CONNECTING) { 26 | log('Strophe is connecting.'); 27 | 28 | } else if (status == Strophe.Status.CONNFAIL) { 29 | log('Strophe failed to connect.'); 30 | showConnect(); 31 | } else if (status == Strophe.Status.DISCONNECTING) { 32 | log('Strophe is disconnecting.'); 33 | } else if (status == Strophe.Status.DISCONNECTED) { 34 | log('Strophe is disconnected.'); 35 | showConnect(); 36 | 37 | } else if (status == Strophe.Status.CONNECTED) { 38 | log('Strophe is connected.'); 39 | // Start up disco browser 40 | browser.showBrowser(); 41 | } 42 | } 43 | 44 | function showConnect() 45 | { 46 | var jid = $('#jid'); 47 | var pass = $('#pass'); 48 | var button = $('#connect').get(0); 49 | 50 | browser.closeBrowser(); 51 | 52 | $('label').show(); 53 | jid.show(); 54 | pass.show(); 55 | $('#anon').show(); 56 | button.value = 'connect'; 57 | return false; 58 | } 59 | 60 | function showDisconnect() 61 | { 62 | var jid = $('#jid'); 63 | var pass = $('#pass'); 64 | var button = $('#connect').get(0); 65 | 66 | button.value = 'disconnect'; 67 | pass.hide(); 68 | jid.hide(); 69 | $('label').hide(); 70 | $('#anon').hide(); 71 | return false; 72 | } 73 | 74 | $(document).ready(function () { 75 | connection = new Strophe.Connection(BOSH_SERVICE); 76 | connection.rawInput = rawInput; 77 | connection.rawOutput = rawOutput; 78 | 79 | browser = new Disco(); 80 | 81 | $("#log_container").bind('click', function () { 82 | $("#log").toggle(); 83 | } 84 | ); 85 | 86 | $('#cred').bind('submit', function () { 87 | var button = $('#connect').get(0); 88 | var jid = $('#jid'); 89 | var pass = $('#pass'); 90 | 91 | if (button.value == 'connect') { 92 | showDisconnect(); 93 | connection.connect(jid.get(0).value, 94 | pass.get(0).value, 95 | onConnect); 96 | } else { 97 | connection.disconnect(); 98 | showConnect(); 99 | } 100 | return false; 101 | }); 102 | }); -------------------------------------------------------------------------------- /contrib/discojs/scripts/disco.js: -------------------------------------------------------------------------------- 1 | 2 | var NS_DISCO_INFO = 'http://jabber.org/protocol/disco#info'; 3 | var NS_DISCO_ITEM = 'http://jabber.org/protocol/disco#items'; 4 | 5 | 6 | // Disco stuff 7 | Disco = function () { 8 | // Class that does nothing 9 | }; 10 | 11 | Disco.prototype = { 12 | showBrowser: function() { 13 | // Browser Display 14 | var disco = $('#disco'); 15 | var jid = $('#jid'); 16 | var server = connection.jid.split('@')[1]; 17 | 18 | // display input box 19 | disco.append("
Server :
"); 20 | 21 | // add handler for search form 22 | $("#browse").bind('submit', function () { 23 | this.startBrowse($("#server").get(0).value); 24 | return false; 25 | }); 26 | 27 | this.startBrowse(server); 28 | }, 29 | 30 | closeBrowser: function() { 31 | var disco = $('#disco'); 32 | 33 | disco.empty(); 34 | }, 35 | 36 | startBrowse: function(server) { 37 | // build iq request 38 | var id = 'startBrowse'; 39 | 40 | var discoiq = $iq({'from':connection.jid+"/"+connection.resource, 41 | 'to':server, 42 | 'id':id, 43 | 'type':'get'} 44 | ) 45 | .c('query', {'xmlns': NS_DISCO_INFO}); 46 | 47 | connection.addHandler(this._cbBrowse, null, 'iq', 'result', id); 48 | connection.send(discoiq.tree()); 49 | 50 | }, 51 | 52 | _cbBrowse: function(e) { 53 | var elem = $(e); // make this Element a JQuery Element 54 | alert(e); 55 | 56 | return false; // return false to remove the handler 57 | }, 58 | 59 | }; 60 | 61 | -------------------------------------------------------------------------------- /examples/attach/README: -------------------------------------------------------------------------------- 1 | This is an example of Strophe attaching to a pre-existing BOSH session 2 | that is created externally. This example requires a bit more than 3 | HTML and JavaScript. Specifically it contains a very simple Web 4 | application written in Django which creates a BOSH session before 5 | rendering the page. 6 | 7 | Requirements: 8 | 9 | * Django 1.0 (http://www.djangoproject.com) 10 | * Twisted 8.1.x (http://twistedmatrix.com) 11 | * Punjab 0.3 (http://code.stanziq.com/punjab) 12 | 13 | Note that Twisted and Punjab are only used for small functions related 14 | to JID and BOSH parsing. 15 | 16 | How It Works: 17 | 18 | The Django app contains one view which is tied to the root URL. This 19 | view uses the BOSHClient class to start a BOSH session using the 20 | settings from settings.py. 21 | 22 | Once the connection is established, Django passes the JID, SID, and 23 | RID for the BOSH session into the template engine and renders the 24 | page. 25 | 26 | The template assigns the JID, SID, and RID to global vars like so: 27 | 28 | var BOSH_JID = {{ jid }}; 29 | var BOSH_SID = {{ sid }}; 30 | var BOSH_RID = {{ rid }}; 31 | 32 | The connection is attached to Strophe by calling 33 | Strophe.Connection.attach() with this data and a connection callback 34 | handler. 35 | 36 | To show that the session is attached and works, a disco info ping is 37 | done to jabber.org. 38 | -------------------------------------------------------------------------------- /examples/attach/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metajack/strophejs/85d35a62b6bf83067116af2f5841ac56fed0d28d/examples/attach/__init__.py -------------------------------------------------------------------------------- /examples/attach/attacher/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metajack/strophejs/85d35a62b6bf83067116af2f5841ac56fed0d28d/examples/attach/attacher/__init__.py -------------------------------------------------------------------------------- /examples/attach/attacher/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.template import Context, loader 3 | 4 | from attach.settings import BOSH_SERVICE, JABBERID, PASSWORD 5 | from attach.boshclient import BOSHClient 6 | 7 | def index(request): 8 | bc = BOSHClient(JABBERID, PASSWORD, BOSH_SERVICE) 9 | bc.startSessionAndAuth() 10 | 11 | t = loader.get_template("attacher/index.html") 12 | c = Context({ 13 | 'jid': bc.jabberid.full(), 14 | 'sid': bc.sid, 15 | 'rid': bc.rid, 16 | }) 17 | 18 | return HttpResponse(t.render(c)) 19 | -------------------------------------------------------------------------------- /examples/attach/boshclient.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | import httplib, urllib 3 | import random, binascii 4 | from urlparse import urlparse 5 | 6 | from punjab.httpb import HttpbParse 7 | 8 | from twisted.words.xish import domish 9 | from twisted.words.protocols.jabber import jid 10 | 11 | TLS_XMLNS = 'urn:ietf:params:xml:ns:xmpp-tls' 12 | SASL_XMLNS = 'urn:ietf:params:xml:ns:xmpp-sasl' 13 | BIND_XMLNS = 'urn:ietf:params:xml:ns:xmpp-bind' 14 | SESSION_XMLNS = 'urn:ietf:params:xml:ns:xmpp-session' 15 | 16 | 17 | class BOSHClient: 18 | def __init__(self, jabberid, password, bosh_service): 19 | self.rid = random.randint(0, 10000000) 20 | self.jabberid = jid.internJID(jabberid) 21 | self.password = password 22 | 23 | self.authid = None 24 | self.sid = None 25 | self.logged_in = False 26 | self.headers = {"Content-type": "text/xml", 27 | "Accept": "text/xml"} 28 | 29 | self.bosh_service = urlparse(bosh_service) 30 | 31 | def buildBody(self, child=None): 32 | """Build a BOSH body. 33 | """ 34 | 35 | body = domish.Element(("http://jabber.org/protocol/httpbind", "body")) 36 | body['content'] = 'text/xml; charset=utf-8' 37 | self.rid = self.rid + 1 38 | body['rid'] = str(self.rid) 39 | body['sid'] = str(self.sid) 40 | body['xml:lang'] = 'en' 41 | 42 | if child is not None: 43 | body.addChild(child) 44 | 45 | return body 46 | 47 | def sendBody(self, body): 48 | """Send the body. 49 | """ 50 | 51 | parser = HttpbParse(True) 52 | 53 | # start new session 54 | conn = httplib.HTTPConnection(self.bosh_service.netloc) 55 | conn.request("POST", self.bosh_service.path, 56 | body.toXml(), self.headers) 57 | 58 | response = conn.getresponse() 59 | data = '' 60 | if response.status == 200: 61 | data = response.read() 62 | conn.close() 63 | 64 | return parser.parse(data) 65 | 66 | def startSessionAndAuth(self, hold='1', wait='70'): 67 | # Create a session 68 | # create body 69 | body = domish.Element(("http://jabber.org/protocol/httpbind", "body")) 70 | 71 | body['content'] = 'text/xml; charset=utf-8' 72 | body['hold'] = hold 73 | body['rid'] = str(self.rid) 74 | body['to'] = self.jabberid.host 75 | body['wait'] = wait 76 | body['window'] = '5' 77 | body['xml:lang'] = 'en' 78 | 79 | 80 | retb, elems = self.sendBody(body) 81 | if type(retb) != str and retb.hasAttribute('authid') and \ 82 | retb.hasAttribute('sid'): 83 | self.authid = retb['authid'] 84 | self.sid = retb['sid'] 85 | 86 | # go ahead and auth 87 | auth = domish.Element((SASL_XMLNS, 'auth')) 88 | auth['mechanism'] = 'PLAIN' 89 | 90 | # TODO: add authzid 91 | if auth['mechanism'] == 'PLAIN': 92 | auth_str = "" 93 | auth_str += "\000" 94 | auth_str += self.jabberid.user.encode('utf-8') 95 | auth_str += "\000" 96 | try: 97 | auth_str += self.password.encode('utf-8').strip() 98 | except UnicodeDecodeError: 99 | auth_str += self.password.decode('latin1') \ 100 | .encode('utf-8').strip() 101 | 102 | auth.addContent(binascii.b2a_base64(auth_str)) 103 | 104 | retb, elems = self.sendBody(self.buildBody(auth)) 105 | if len(elems) == 0: 106 | # poll for data 107 | retb, elems = self.sendBody(self.buildBody()) 108 | 109 | if len(elems) > 0: 110 | if elems[0].name == 'success': 111 | retb, elems = self.sendBody(self.buildBody()) 112 | 113 | if elems[0].firstChildElement().name == 'bind': 114 | iq = domish.Element(('jabber:client', 'iq')) 115 | iq['type'] = 'set' 116 | iq.addUniqueId() 117 | iq.addElement('bind') 118 | iq.bind['xmlns'] = BIND_XMLNS 119 | if self.jabberid.resource: 120 | iq.bind.addElement('resource') 121 | iq.bind.resource.addContent( 122 | self.jabberid.resource) 123 | 124 | retb, elems = self.sendBody(self.buildBody(iq)) 125 | if type(retb) != str and retb.name == 'body': 126 | # send session 127 | iq = domish.Element(('jabber:client', 'iq')) 128 | iq['type'] = 'set' 129 | iq.addUniqueId() 130 | iq.addElement('session') 131 | iq.session['xmlns'] = SESSION_XMLNS 132 | 133 | retb, elems = self.sendBody(self.buildBody(iq)) 134 | 135 | # did not bind, TODO - add a retry? 136 | if type(retb) != str and retb.name == 'body': 137 | self.logged_in = True 138 | # bump up the rid, punjab already 139 | # received self.rid 140 | self.rid += 1 141 | 142 | 143 | if __name__ == '__main__': 144 | USERNAME = sys.argv[1] 145 | PASSWORD = sys.argv[2] 146 | SERVICE = sys.argv[3] 147 | 148 | c = BOSHClient(USERNAME, PASSWORD, SERVICE) 149 | c.startSessionAndAuth() 150 | 151 | print c.logged_in 152 | 153 | -------------------------------------------------------------------------------- /examples/attach/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | try: 4 | import settings # Assumed to be in the same directory. 5 | except ImportError: 6 | import sys 7 | 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__) 8 | sys.exit(1) 9 | 10 | if __name__ == "__main__": 11 | execute_manager(settings) 12 | -------------------------------------------------------------------------------- /examples/attach/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for attach project. 2 | 3 | DEBUG = True 4 | TEMPLATE_DEBUG = DEBUG 5 | 6 | ADMINS = ( 7 | ('Some Body', 'romeo@example.com'), 8 | ) 9 | 10 | MANAGERS = ADMINS 11 | 12 | DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 13 | DATABASE_NAME = '/path/to/attach.db' # Or path to database file if using sqlite3. 14 | DATABASE_USER = '' # Not used with sqlite3. 15 | DATABASE_PASSWORD = '' # Not used with sqlite3. 16 | DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. 17 | DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. 18 | 19 | # Local time zone for this installation. Choices can be found here: 20 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 21 | # although not all choices may be available on all operating systems. 22 | # If running in a Windows environment this must be set to the same as your 23 | # system time zone. 24 | TIME_ZONE = 'America/Denver' 25 | 26 | # Language code for this installation. All choices can be found here: 27 | # http://www.i18nguy.com/unicode/language-identifiers.html 28 | LANGUAGE_CODE = 'en-us' 29 | 30 | SITE_ID = 1 31 | 32 | # If you set this to False, Django will make some optimizations so as not 33 | # to load the internationalization machinery. 34 | USE_I18N = True 35 | 36 | # Absolute path to the directory that holds media. 37 | # Example: "/home/media/media.lawrence.com/" 38 | MEDIA_ROOT = '' 39 | 40 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 41 | # trailing slash if there is a path component (optional in other cases). 42 | # Examples: "http://media.lawrence.com", "http://example.com/media/" 43 | MEDIA_URL = '' 44 | 45 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a 46 | # trailing slash. 47 | # Examples: "http://foo.com/media/", "/media/". 48 | ADMIN_MEDIA_PREFIX = '/media/' 49 | 50 | # Make this unique, and don't share it with anybody. 51 | SECRET_KEY = 'asdf' 52 | 53 | # List of callables that know how to import templates from various sources. 54 | TEMPLATE_LOADERS = ( 55 | 'django.template.loaders.filesystem.load_template_source', 56 | 'django.template.loaders.app_directories.load_template_source', 57 | # 'django.template.loaders.eggs.load_template_source', 58 | ) 59 | 60 | MIDDLEWARE_CLASSES = ( 61 | 'django.middleware.common.CommonMiddleware', 62 | 'django.contrib.sessions.middleware.SessionMiddleware', 63 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 64 | ) 65 | 66 | ROOT_URLCONF = 'attach.urls' 67 | 68 | TEMPLATE_DIRS = ( 69 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 70 | # Always use forward slashes, even on Windows. 71 | # Don't forget to use absolute paths, not relative paths. 72 | '/path/to/attach/templates', 73 | ) 74 | 75 | INSTALLED_APPS = ( 76 | 'django.contrib.auth', 77 | 'django.contrib.contenttypes', 78 | 'django.contrib.sessions', 79 | 'django.contrib.sites', 80 | 'attach.attacher', 81 | ) 82 | 83 | BOSH_SERVICE = 'http://example.com/xmpp-httpbind' 84 | JABBERID = 'romeo@example.com/bosh' 85 | PASSWORD = 'juliet.is.hawt' 86 | -------------------------------------------------------------------------------- /examples/attach/templates/attacher/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Strophe Attach Example 5 | 8 | 11 | 14 | 17 | 20 | 79 | 80 | 81 |

Strophe Attach Example

82 |

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

84 |

Log

85 |
86 |
87 | 88 | -------------------------------------------------------------------------------- /examples/attach/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | # Uncomment the next two lines to enable the admin: 4 | # from django.contrib import admin 5 | # admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | # Example: 9 | # (r'^attach/', include('attach.foo.urls')), 10 | 11 | # Uncomment the admin/doc line below and add 'django.contrib.admindocs' 12 | # to INSTALLED_APPS to enable admin documentation: 13 | # (r'^admin/doc/', include('django.contrib.admindocs.urls')), 14 | 15 | # Uncomment the next line to enable the admin: 16 | # (r'^admin/(.*)', admin.site.root), 17 | 18 | (r'^$', 'attach.attacher.views.index'), 19 | ) 20 | -------------------------------------------------------------------------------- /examples/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Strophe.js Basic Example 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/basic.js: -------------------------------------------------------------------------------- 1 | var BOSH_SERVICE = 'http://bosh.metajack.im:5280/xmpp-httpbind' 2 | var connection = null; 3 | 4 | function log(msg) 5 | { 6 | $('#log').append('
').append(document.createTextNode(msg)); 7 | } 8 | 9 | function rawInput(data) 10 | { 11 | log('RECV: ' + data); 12 | } 13 | 14 | function rawOutput(data) 15 | { 16 | log('SENT: ' + data); 17 | } 18 | 19 | function onConnect(status) 20 | { 21 | if (status == Strophe.Status.CONNECTING) { 22 | log('Strophe is connecting.'); 23 | } else if (status == Strophe.Status.CONNFAIL) { 24 | log('Strophe failed to connect.'); 25 | $('#connect').get(0).value = 'connect'; 26 | } else if (status == Strophe.Status.DISCONNECTING) { 27 | log('Strophe is disconnecting.'); 28 | } else if (status == Strophe.Status.DISCONNECTED) { 29 | log('Strophe is disconnected.'); 30 | $('#connect').get(0).value = 'connect'; 31 | } else if (status == Strophe.Status.CONNECTED) { 32 | log('Strophe is connected.'); 33 | connection.disconnect(); 34 | } 35 | } 36 | 37 | $(document).ready(function () { 38 | connection = new Strophe.Connection(BOSH_SERVICE); 39 | connection.rawInput = rawInput; 40 | connection.rawOutput = rawOutput; 41 | 42 | $('#connect').bind('click', function () { 43 | var button = $('#connect').get(0); 44 | if (button.value == 'connect') { 45 | button.value = 'disconnect'; 46 | 47 | connection.connect($('#jid').get(0).value, 48 | $('#pass').get(0).value, 49 | onConnect); 50 | } else { 51 | button.value = 'connect'; 52 | connection.disconnect(); 53 | } 54 | }); 55 | }); -------------------------------------------------------------------------------- /examples/crossdomain.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Strophe.js Basic Cross-Domain Example 5 | 6 | 8 | 10 | 11 | 13 | 15 | 16 | 18 | 19 | 20 |
21 |
22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/crossdomain.js: -------------------------------------------------------------------------------- 1 | // The BOSH_SERVICE here doesn't need to be on the same domain/port, but 2 | // it must have a /crossdomain.xml policy file that allows access from 3 | // wherever crossdomain.html lives. 4 | // 5 | // Most BOSH connection managers can serve static html files, so you should 6 | // be able to configure them to serve a /crossdomain.xml file to allow 7 | // access. 8 | var BOSH_SERVICE = 'http://bosh.metajack.im:5280/xmpp-httpbind' 9 | var connection = null; 10 | 11 | function log(msg) 12 | { 13 | $('#log').append('
').append(document.createTextNode(msg)); 14 | } 15 | 16 | function rawInput(data) 17 | { 18 | log('RECV: ' + data); 19 | } 20 | 21 | function rawOutput(data) 22 | { 23 | log('SENT: ' + data); 24 | } 25 | 26 | function onConnect(status) 27 | { 28 | if (status == Strophe.Status.CONNECTING) { 29 | log('Strophe is connecting.'); 30 | } else if (status == Strophe.Status.CONNFAIL) { 31 | log('Strophe failed to connect.'); 32 | $('#connect').get(0).value = 'connect'; 33 | } else if (status == Strophe.Status.DISCONNECTING) { 34 | log('Strophe is disconnecting.'); 35 | } else if (status == Strophe.Status.DISCONNECTED) { 36 | log('Strophe is disconnected.'); 37 | $('#connect').get(0).value = 'connect'; 38 | } else if (status == Strophe.Status.CONNECTED) { 39 | log('Strophe is connected.'); 40 | connection.disconnect(); 41 | } 42 | } 43 | 44 | $(document).ready(function () { 45 | connection = new Strophe.Connection(BOSH_SERVICE); 46 | connection.rawInput = rawInput; 47 | connection.rawOutput = rawOutput; 48 | 49 | $('#connect').bind('click', function () { 50 | var button = $('#connect').get(0); 51 | if (button.value == 'connect') { 52 | button.value = 'disconnect'; 53 | 54 | connection.connect($('#jid').get(0).value, 55 | $('#pass').get(0).value, 56 | onConnect); 57 | } else { 58 | button.value = 'connect'; 59 | connection.disconnect(); 60 | } 61 | }); 62 | }); -------------------------------------------------------------------------------- /examples/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/echobot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Strophe.js Echobot Example 5 | 7 | 9 | 11 | 12 | 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/echobot.js: -------------------------------------------------------------------------------- 1 | var BOSH_SERVICE = '/xmpp-httpbind'; 2 | var connection = null; 3 | 4 | function log(msg) 5 | { 6 | $('#log').append('
').append(document.createTextNode(msg)); 7 | } 8 | 9 | function onConnect(status) 10 | { 11 | if (status == Strophe.Status.CONNECTING) { 12 | log('Strophe is connecting.'); 13 | } else if (status == Strophe.Status.CONNFAIL) { 14 | log('Strophe failed to connect.'); 15 | $('#connect').get(0).value = 'connect'; 16 | } else if (status == Strophe.Status.DISCONNECTING) { 17 | log('Strophe is disconnecting.'); 18 | } else if (status == Strophe.Status.DISCONNECTED) { 19 | log('Strophe is disconnected.'); 20 | $('#connect').get(0).value = 'connect'; 21 | } else if (status == Strophe.Status.CONNECTED) { 22 | log('Strophe is connected.'); 23 | log('ECHOBOT: Send a message to ' + connection.jid + 24 | ' to talk to me.'); 25 | 26 | connection.addHandler(onMessage, null, 'message', null, null, null); 27 | connection.send($pres().tree()); 28 | } 29 | } 30 | 31 | function onMessage(msg) { 32 | var to = msg.getAttribute('to'); 33 | var from = msg.getAttribute('from'); 34 | var type = msg.getAttribute('type'); 35 | var elems = msg.getElementsByTagName('body'); 36 | 37 | if (type == "chat" && elems.length > 0) { 38 | var body = elems[0]; 39 | 40 | log('ECHOBOT: I got a message from ' + from + ': ' + 41 | Strophe.getText(body)); 42 | 43 | var reply = $msg({to: from, from: to, type: 'chat'}) 44 | .cnode(Strophe.copyElement(body)); 45 | connection.send(reply.tree()); 46 | 47 | log('ECHOBOT: I sent ' + from + ': ' + Strophe.getText(body)); 48 | } 49 | 50 | // we must return true to keep the handler alive. 51 | // returning false would remove it after it finishes. 52 | return true; 53 | } 54 | 55 | $(document).ready(function () { 56 | connection = new Strophe.Connection(BOSH_SERVICE); 57 | 58 | // Uncomment the following lines to spy on the wire traffic. 59 | //connection.rawInput = function (data) { log('RECV: ' + data); }; 60 | //connection.rawOutput = function (data) { log('SEND: ' + data); }; 61 | 62 | // Uncomment the following line to see all the debug output. 63 | //Strophe.log = function (level, msg) { log('LOG: ' + msg); }; 64 | 65 | 66 | $('#connect').bind('click', function () { 67 | var button = $('#connect').get(0); 68 | if (button.value == 'connect') { 69 | button.value = 'disconnect'; 70 | 71 | connection.connect($('#jid').get(0).value, 72 | $('#pass').get(0).value, 73 | onConnect); 74 | } else { 75 | button.value = 'connect'; 76 | connection.disconnect(); 77 | } 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /examples/prebind.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 | Strophe.js Pre-Bind Example 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | 35 |
36 | 37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/prebind.js: -------------------------------------------------------------------------------- 1 | // http-pre-bind example 2 | // This example works with mod_http_pre_bind found here: 3 | // http://github.com/thepug/Mod-Http-Pre-Bind 4 | // 5 | // It expects both /xmpp-httpbind to be proxied and /http-pre-bind 6 | // 7 | // If you want to test this out without setting it up, you can use Collecta's 8 | // at http://www.collecta.com/xmpp-httpbind and 9 | // http://www.collecta.com/http-pre-bind 10 | // Use a JID of 'guest.collecta.com' to test. 11 | 12 | var BOSH_SERVICE = '/xmpp-httpbind'; 13 | var PREBIND_SERVICE = '/http-pre-bind'; 14 | var connection = null; 15 | 16 | function log(msg) 17 | { 18 | $('#log').append('
').append(document.createTextNode(msg)); 19 | } 20 | 21 | function rawInput(data) 22 | { 23 | log('RECV: ' + data); 24 | } 25 | 26 | function rawOutput(data) 27 | { 28 | log('SENT: ' + data); 29 | } 30 | 31 | function onConnect(status) 32 | { 33 | if (status === Strophe.Status.CONNECTING) { 34 | log('Strophe is connecting.'); 35 | } else if (status === Strophe.Status.CONNFAIL) { 36 | log('Strophe failed to connect.'); 37 | $('#connect').get(0).value = 'connect'; 38 | } else if (status === Strophe.Status.DISCONNECTING) { 39 | log('Strophe is disconnecting.'); 40 | } else if (status === Strophe.Status.DISCONNECTED) { 41 | log('Strophe is disconnected.'); 42 | $('#connect').get(0).value = 'connect'; 43 | } else if (status === Strophe.Status.CONNECTED) { 44 | log('Strophe is connected.'); 45 | connection.disconnect(); 46 | } else if (status === Strophe.Status.ATTACHED) { 47 | log('Strophe is attached.'); 48 | connection.disconnect(); 49 | } 50 | } 51 | 52 | function normal_connect() { 53 | log('Prebind failed. Connecting normally...'); 54 | 55 | connection = new Strophe.Connection(BOSH_SERVICE); 56 | connection.rawInput = rawInput; 57 | connection.rawOutput = rawOutput; 58 | 59 | connection.connect($('#jid').val(), $('#pass').val(), onConnect); 60 | } 61 | 62 | function attach(data) { 63 | log('Prebind succeeded. Attaching...'); 64 | 65 | connection = new Strophe.Connection(BOSH_SERVICE); 66 | connection.rawInput = rawInput; 67 | connection.rawOutput = rawOutput; 68 | 69 | var $body = $(data.documentElement); 70 | connection.attach($body.find('jid').text(), 71 | $body.attr('sid'), 72 | parseInt($body.attr('rid'), 10) + 1, 73 | onConnect); 74 | } 75 | 76 | $(document).ready(function () { 77 | $('#connect').bind('click', function () { 78 | var button = $('#connect').get(0); 79 | if (button.value == 'connect') { 80 | button.value = 'disconnect'; 81 | 82 | // attempt prebind 83 | $.ajax({ 84 | type: 'POST', 85 | url: PREBIND_SERVICE, 86 | contentType: 'text/xml', 87 | processData: false, 88 | data: $build('body', { 89 | to: Strophe.getDomainFromJid($('#jid').val()), 90 | rid: '' + Math.floor(Math.random() * 4294967295), 91 | wait: '60', 92 | hold: '1'}).toString(), 93 | dataType: 'xml', 94 | error: normal_connect, 95 | success: attach}); 96 | } else { 97 | button.value = 'connect'; 98 | if (connection) { 99 | connection.disconnect(); 100 | } 101 | } 102 | }); 103 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Strophe.js", 3 | "description" : "Strophe.js is an XMPP library for JavaScript", 4 | "version" : "1.0.2", 5 | "homepage" : "http://strophe.im/strophejs", 6 | "repository" : { "type": "git", "url": "git://github.com/metajack/strophejs.git" }, 7 | "keywords" : ["xmpp", "message", "browser"], 8 | "author" : "", 9 | "contributors" : [], 10 | "licenses" : ["MIT"], 11 | "dependencies" : [], 12 | "main" : "./strophe.js", 13 | "scripts" : { "install": "make", "update": "make" }, 14 | "directories" : { "lib": "./src", "test": "./tests" }, 15 | "engines" : { "browser": "*" } 16 | } 17 | -------------------------------------------------------------------------------- /plugins/strophe.flxhr.js: -------------------------------------------------------------------------------- 1 | /* flXHR plugin 2 | ** 3 | ** This plugin implements cross-domain XmlHttpRequests via an invisible 4 | ** Flash plugin. 5 | ** 6 | ** In order for this to work, the BOSH service *must* serve a 7 | ** crossdomain.xml file that allows the client access. 8 | ** 9 | ** flXHR.js should be loaded before this plugin. 10 | */ 11 | 12 | Strophe.addConnectionPlugin('flxhr', { 13 | init: function (conn) { 14 | // replace Strophe.Request._newXHR with new flXHR version 15 | // if flXHR is detected 16 | if (flensed && flensed.flXHR) { 17 | Strophe.Request.prototype._newXHR = function () { 18 | var xhr = new flensed.flXHR({ 19 | autoUpdatePlayer: true, 20 | instancePooling: true, 21 | noCacheHeader: false, 22 | onerror: function () { 23 | conn._changeConnectStatus(Strophe.Status.CONNFAIL, 24 | "flXHR connection error"); 25 | conn._onDisconnectTimeout(); 26 | }}); 27 | xhr.onreadystatechange = this.func.bind(null, this); 28 | 29 | return xhr; 30 | }; 31 | } else { 32 | Strophe.error("flXHR plugin loaded, but flXHR not found." + 33 | " Falling back to native XHR implementation."); 34 | } 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /release_checklist.txt: -------------------------------------------------------------------------------- 1 | # -*- mode: org -*- 2 | 3 | * Release Checklist 4 | *** Make sure all tests pass 5 | *** Decide on version number 6 | *** Update CHANGELOG.txt 7 | *** Tag code with version (git tag -s release-VERSION head) 8 | *** Push repo and tags (git push && git push --tags) 9 | *** Create version.txt with version 10 | *** Run make release 11 | *** Upload zip file and tarball to GitHub 12 | *** Add documentation to strophe.im repo 13 | *** Update website 14 | *** Tell the world 15 | -------------------------------------------------------------------------------- /src/base64.js: -------------------------------------------------------------------------------- 1 | // This code was written by Tyler Akins and has been placed in the 2 | // public domain. It would be nice if you left this header intact. 3 | // Base64 code from Tyler Akins -- http://rumkin.com 4 | 5 | var Base64 = (function () { 6 | var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 7 | 8 | var obj = { 9 | /** 10 | * Encodes a string in base64 11 | * @param {String} input The string to encode in base64. 12 | */ 13 | encode: function (input) { 14 | var output = ""; 15 | var chr1, chr2, chr3; 16 | var enc1, enc2, enc3, enc4; 17 | var i = 0; 18 | 19 | do { 20 | chr1 = input.charCodeAt(i++); 21 | chr2 = input.charCodeAt(i++); 22 | chr3 = input.charCodeAt(i++); 23 | 24 | enc1 = chr1 >> 2; 25 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 26 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 27 | enc4 = chr3 & 63; 28 | 29 | if (isNaN(chr2)) { 30 | enc3 = enc4 = 64; 31 | } else if (isNaN(chr3)) { 32 | enc4 = 64; 33 | } 34 | 35 | output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + 36 | keyStr.charAt(enc3) + keyStr.charAt(enc4); 37 | } while (i < input.length); 38 | 39 | return output; 40 | }, 41 | 42 | /** 43 | * Decodes a base64 string. 44 | * @param {String} input The string to decode. 45 | */ 46 | decode: function (input) { 47 | var output = ""; 48 | var chr1, chr2, chr3; 49 | var enc1, enc2, enc3, enc4; 50 | var i = 0; 51 | 52 | // remove all characters that are not A-Z, a-z, 0-9, +, /, or = 53 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 54 | 55 | do { 56 | enc1 = keyStr.indexOf(input.charAt(i++)); 57 | enc2 = keyStr.indexOf(input.charAt(i++)); 58 | enc3 = keyStr.indexOf(input.charAt(i++)); 59 | enc4 = keyStr.indexOf(input.charAt(i++)); 60 | 61 | chr1 = (enc1 << 2) | (enc2 >> 4); 62 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 63 | chr3 = ((enc3 & 3) << 6) | enc4; 64 | 65 | output = output + String.fromCharCode(chr1); 66 | 67 | if (enc3 != 64) { 68 | output = output + String.fromCharCode(chr2); 69 | } 70 | if (enc4 != 64) { 71 | output = output + String.fromCharCode(chr3); 72 | } 73 | } while (i < input.length); 74 | 75 | return output; 76 | } 77 | }; 78 | 79 | return obj; 80 | })(); 81 | -------------------------------------------------------------------------------- /src/md5.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message 3 | * Digest Algorithm, as defined in RFC 1321. 4 | * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. 5 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 6 | * Distributed under the BSD License 7 | * See http://pajhome.org.uk/crypt/md5 for more info. 8 | */ 9 | 10 | var MD5 = (function () { 11 | /* 12 | * Configurable variables. You may need to tweak these to be compatible with 13 | * the server-side, but the defaults work in most cases. 14 | */ 15 | var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ 16 | var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ 17 | var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ 18 | 19 | /* 20 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally 21 | * to work around bugs in some JS interpreters. 22 | */ 23 | var safe_add = function (x, y) { 24 | var lsw = (x & 0xFFFF) + (y & 0xFFFF); 25 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 26 | return (msw << 16) | (lsw & 0xFFFF); 27 | }; 28 | 29 | /* 30 | * Bitwise rotate a 32-bit number to the left. 31 | */ 32 | var bit_rol = function (num, cnt) { 33 | return (num << cnt) | (num >>> (32 - cnt)); 34 | }; 35 | 36 | /* 37 | * Convert a string to an array of little-endian words 38 | * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. 39 | */ 40 | var str2binl = function (str) { 41 | var bin = []; 42 | var mask = (1 << chrsz) - 1; 43 | for(var i = 0; i < str.length * chrsz; i += chrsz) 44 | { 45 | bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32); 46 | } 47 | return bin; 48 | }; 49 | 50 | /* 51 | * Convert an array of little-endian words to a string 52 | */ 53 | var binl2str = function (bin) { 54 | var str = ""; 55 | var mask = (1 << chrsz) - 1; 56 | for(var i = 0; i < bin.length * 32; i += chrsz) 57 | { 58 | str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask); 59 | } 60 | return str; 61 | }; 62 | 63 | /* 64 | * Convert an array of little-endian words to a hex string. 65 | */ 66 | var binl2hex = function (binarray) { 67 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; 68 | var str = ""; 69 | for(var i = 0; i < binarray.length * 4; i++) 70 | { 71 | str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + 72 | hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); 73 | } 74 | return str; 75 | }; 76 | 77 | /* 78 | * Convert an array of little-endian words to a base-64 string 79 | */ 80 | var binl2b64 = function (binarray) { 81 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 82 | var str = ""; 83 | var triplet, j; 84 | for(var i = 0; i < binarray.length * 4; i += 3) 85 | { 86 | triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) | 87 | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) | 88 | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF); 89 | for(j = 0; j < 4; j++) 90 | { 91 | if(i * 8 + j * 6 > binarray.length * 32) { str += b64pad; } 92 | else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); } 93 | } 94 | } 95 | return str; 96 | }; 97 | 98 | /* 99 | * These functions implement the four basic operations the algorithm uses. 100 | */ 101 | var md5_cmn = function (q, a, b, x, s, t) { 102 | return safe_add(bit_rol(safe_add(safe_add(a, q),safe_add(x, t)), s),b); 103 | }; 104 | 105 | var md5_ff = function (a, b, c, d, x, s, t) { 106 | return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); 107 | }; 108 | 109 | var md5_gg = function (a, b, c, d, x, s, t) { 110 | return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); 111 | }; 112 | 113 | var md5_hh = function (a, b, c, d, x, s, t) { 114 | return md5_cmn(b ^ c ^ d, a, b, x, s, t); 115 | }; 116 | 117 | var md5_ii = function (a, b, c, d, x, s, t) { 118 | return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); 119 | }; 120 | 121 | /* 122 | * Calculate the MD5 of an array of little-endian words, and a bit length 123 | */ 124 | var core_md5 = function (x, len) { 125 | /* append padding */ 126 | x[len >> 5] |= 0x80 << ((len) % 32); 127 | x[(((len + 64) >>> 9) << 4) + 14] = len; 128 | 129 | var a = 1732584193; 130 | var b = -271733879; 131 | var c = -1732584194; 132 | var d = 271733878; 133 | 134 | var olda, oldb, oldc, oldd; 135 | for (var i = 0; i < x.length; i += 16) 136 | { 137 | olda = a; 138 | oldb = b; 139 | oldc = c; 140 | oldd = d; 141 | 142 | a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); 143 | d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); 144 | c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); 145 | b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); 146 | a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); 147 | d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); 148 | c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); 149 | b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); 150 | a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); 151 | d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); 152 | c = md5_ff(c, d, a, b, x[i+10], 17, -42063); 153 | b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); 154 | a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); 155 | d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); 156 | c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); 157 | b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); 158 | 159 | a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); 160 | d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); 161 | c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); 162 | b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); 163 | a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); 164 | d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); 165 | c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); 166 | b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); 167 | a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); 168 | d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); 169 | c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); 170 | b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); 171 | a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); 172 | d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); 173 | c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); 174 | b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); 175 | 176 | a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); 177 | d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); 178 | c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); 179 | b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); 180 | a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); 181 | d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); 182 | c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); 183 | b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); 184 | a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); 185 | d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); 186 | c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); 187 | b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); 188 | a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); 189 | d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); 190 | c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); 191 | b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); 192 | 193 | a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); 194 | d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); 195 | c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); 196 | b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); 197 | a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); 198 | d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); 199 | c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); 200 | b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); 201 | a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); 202 | d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); 203 | c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); 204 | b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); 205 | a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); 206 | d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); 207 | c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); 208 | b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); 209 | 210 | a = safe_add(a, olda); 211 | b = safe_add(b, oldb); 212 | c = safe_add(c, oldc); 213 | d = safe_add(d, oldd); 214 | } 215 | return [a, b, c, d]; 216 | }; 217 | 218 | 219 | /* 220 | * Calculate the HMAC-MD5, of a key and some data 221 | */ 222 | var core_hmac_md5 = function (key, data) { 223 | var bkey = str2binl(key); 224 | if(bkey.length > 16) { bkey = core_md5(bkey, key.length * chrsz); } 225 | 226 | var ipad = new Array(16), opad = new Array(16); 227 | for(var i = 0; i < 16; i++) 228 | { 229 | ipad[i] = bkey[i] ^ 0x36363636; 230 | opad[i] = bkey[i] ^ 0x5C5C5C5C; 231 | } 232 | 233 | var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz); 234 | return core_md5(opad.concat(hash), 512 + 128); 235 | }; 236 | 237 | var obj = { 238 | /* 239 | * These are the functions you'll usually want to call. 240 | * They take string arguments and return either hex or base-64 encoded 241 | * strings. 242 | */ 243 | hexdigest: function (s) { 244 | return binl2hex(core_md5(str2binl(s), s.length * chrsz)); 245 | }, 246 | 247 | b64digest: function (s) { 248 | return binl2b64(core_md5(str2binl(s), s.length * chrsz)); 249 | }, 250 | 251 | hash: function (s) { 252 | return binl2str(core_md5(str2binl(s), s.length * chrsz)); 253 | }, 254 | 255 | hmac_hexdigest: function (key, data) { 256 | return binl2hex(core_hmac_md5(key, data)); 257 | }, 258 | 259 | hmac_b64digest: function (key, data) { 260 | return binl2b64(core_hmac_md5(key, data)); 261 | }, 262 | 263 | hmac_hash: function (key, data) { 264 | return binl2str(core_hmac_md5(key, data)); 265 | }, 266 | 267 | /* 268 | * Perform a simple self-test to see if the VM is working 269 | */ 270 | test: function () { 271 | return MD5.hexdigest("abc") === "900150983cd24fb0d6963f7d28e17f72"; 272 | } 273 | }; 274 | 275 | return obj; 276 | })(); 277 | -------------------------------------------------------------------------------- /src/sha1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined 3 | * in FIPS PUB 180-1 4 | * Version 2.1a Copyright Paul Johnston 2000 - 2002. 5 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 6 | * Distributed under the BSD License 7 | * See http://pajhome.org.uk/crypt/md5 for details. 8 | */ 9 | 10 | /* 11 | * Configurable variables. You may need to tweak these to be compatible with 12 | * the server-side, but the defaults work in most cases. 13 | */ 14 | var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ 15 | var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */ 16 | var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ 17 | 18 | /* 19 | * These are the functions you'll usually want to call 20 | * They take string arguments and return either hex or base-64 encoded strings 21 | */ 22 | function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));} 23 | function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));} 24 | function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));} 25 | function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));} 26 | function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));} 27 | function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));} 28 | 29 | /* 30 | * Perform a simple self-test to see if the VM is working 31 | */ 32 | function sha1_vm_test() 33 | { 34 | return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d"; 35 | } 36 | 37 | /* 38 | * Calculate the SHA-1 of an array of big-endian words, and a bit length 39 | */ 40 | function core_sha1(x, len) 41 | { 42 | /* append padding */ 43 | x[len >> 5] |= 0x80 << (24 - len % 32); 44 | x[((len + 64 >> 9) << 4) + 15] = len; 45 | 46 | var w = new Array(80); 47 | var a = 1732584193; 48 | var b = -271733879; 49 | var c = -1732584194; 50 | var d = 271733878; 51 | var e = -1009589776; 52 | 53 | var i, j, t, olda, oldb, oldc, oldd, olde; 54 | for (i = 0; i < x.length; i += 16) 55 | { 56 | olda = a; 57 | oldb = b; 58 | oldc = c; 59 | oldd = d; 60 | olde = e; 61 | 62 | for (j = 0; j < 80; j++) 63 | { 64 | if (j < 16) { w[j] = x[i + j]; } 65 | else { w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); } 66 | t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), 67 | safe_add(safe_add(e, w[j]), sha1_kt(j))); 68 | e = d; 69 | d = c; 70 | c = rol(b, 30); 71 | b = a; 72 | a = t; 73 | } 74 | 75 | a = safe_add(a, olda); 76 | b = safe_add(b, oldb); 77 | c = safe_add(c, oldc); 78 | d = safe_add(d, oldd); 79 | e = safe_add(e, olde); 80 | } 81 | return [a, b, c, d, e]; 82 | } 83 | 84 | /* 85 | * Perform the appropriate triplet combination function for the current 86 | * iteration 87 | */ 88 | function sha1_ft(t, b, c, d) 89 | { 90 | if (t < 20) { return (b & c) | ((~b) & d); } 91 | if (t < 40) { return b ^ c ^ d; } 92 | if (t < 60) { return (b & c) | (b & d) | (c & d); } 93 | return b ^ c ^ d; 94 | } 95 | 96 | /* 97 | * Determine the appropriate additive constant for the current iteration 98 | */ 99 | function sha1_kt(t) 100 | { 101 | return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : 102 | (t < 60) ? -1894007588 : -899497514; 103 | } 104 | 105 | /* 106 | * Calculate the HMAC-SHA1 of a key and some data 107 | */ 108 | function core_hmac_sha1(key, data) 109 | { 110 | var bkey = str2binb(key); 111 | if (bkey.length > 16) { bkey = core_sha1(bkey, key.length * chrsz); } 112 | 113 | var ipad = new Array(16), opad = new Array(16); 114 | for (var i = 0; i < 16; i++) 115 | { 116 | ipad[i] = bkey[i] ^ 0x36363636; 117 | opad[i] = bkey[i] ^ 0x5C5C5C5C; 118 | } 119 | 120 | var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz); 121 | return core_sha1(opad.concat(hash), 512 + 160); 122 | } 123 | 124 | /* 125 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally 126 | * to work around bugs in some JS interpreters. 127 | */ 128 | function safe_add(x, y) 129 | { 130 | var lsw = (x & 0xFFFF) + (y & 0xFFFF); 131 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 132 | return (msw << 16) | (lsw & 0xFFFF); 133 | } 134 | 135 | /* 136 | * Bitwise rotate a 32-bit number to the left. 137 | */ 138 | function rol(num, cnt) 139 | { 140 | return (num << cnt) | (num >>> (32 - cnt)); 141 | } 142 | 143 | /* 144 | * Convert an 8-bit or 16-bit string to an array of big-endian words 145 | * In 8-bit function, characters >255 have their hi-byte silently ignored. 146 | */ 147 | function str2binb(str) 148 | { 149 | var bin = []; 150 | var mask = (1 << chrsz) - 1; 151 | for (var i = 0; i < str.length * chrsz; i += chrsz) 152 | { 153 | bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32); 154 | } 155 | return bin; 156 | } 157 | 158 | /* 159 | * Convert an array of big-endian words to a string 160 | */ 161 | function binb2str(bin) 162 | { 163 | var str = ""; 164 | var mask = (1 << chrsz) - 1; 165 | for (var i = 0; i < bin.length * 32; i += chrsz) 166 | { 167 | str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask); 168 | } 169 | return str; 170 | } 171 | 172 | /* 173 | * Convert an array of big-endian words to a hex string. 174 | */ 175 | function binb2hex(binarray) 176 | { 177 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; 178 | var str = ""; 179 | for (var i = 0; i < binarray.length * 4; i++) 180 | { 181 | str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) + 182 | hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF); 183 | } 184 | return str; 185 | } 186 | 187 | /* 188 | * Convert an array of big-endian words to a base-64 string 189 | */ 190 | function binb2b64(binarray) 191 | { 192 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 193 | var str = ""; 194 | var triplet, j; 195 | for (var i = 0; i < binarray.length * 4; i += 3) 196 | { 197 | triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) | 198 | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) | 199 | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); 200 | for (j = 0; j < 4; j++) 201 | { 202 | if (i * 8 + j * 6 > binarray.length * 32) { str += b64pad; } 203 | else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); } 204 | } 205 | } 206 | return str; 207 | } 208 | -------------------------------------------------------------------------------- /tests/muc.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 |

QUnit tests for Strophe

19 |

20 |
21 | Disconnect. 22 |
23 |
24 |
25 |
    26 |
    27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/muc.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | Strophe.Test = { 4 | BOSH_URL: "/xmpp-httpbind", 5 | XMPP_DOMAIN: 'speeqe.com', 6 | room_name: 'speeqers@chat.speeqe.com', 7 | connection: null, //connection object created in run function 8 | 9 | run: function() { 10 | $(document).ready(function(){ 11 | //Connect strophe, uses localhost to test 12 | Strophe.Test.connection = 13 | new Strophe.Connection(Strophe.Test.BOSH_URL); 14 | 15 | //connect anonymously to run most tests 16 | Strophe.Test.connection.connect(Strophe.Test.XMPP_DOMAIN, 17 | null, 18 | Strophe.Test.connectCallback); 19 | 20 | //set up the test client UI 21 | $("#disconnect").click(function() { 22 | Strophe.Test.connection.disconnect(); 23 | }); 24 | $("#run_tests").click(function() { 25 | test("Anonymous connection test.", function() { 26 | if(Strophe.Test.connection.connected) 27 | { 28 | ok( true, "all good"); 29 | } 30 | else 31 | { 32 | ok( false, "not connected anonymously"); 33 | } 34 | }); 35 | test("join a room test",function() { 36 | Strophe.Test.connection.muc.join(Strophe.Test.room_name, 37 | "testnick", 38 | function(msg) { 39 | $('#muc_item').append($(msg).text()); 40 | }, 41 | function(pres) { 42 | $('#muc_item').append($(pres).text()); 43 | }); 44 | ok(true, 45 | "joined " + Strophe.Test.room_name); 46 | 47 | }); 48 | test("send a message", function() { 49 | Strophe.Test.connection.muc.message(Strophe.Test.room_name, 50 | "testnick", 51 | "test message"); 52 | }); 53 | test("configure room", function() { 54 | Strophe.Test 55 | .connection.muc.configure(Strophe.Test.room_name); 56 | Strophe.Test 57 | .connection.muc.cancelConfigure(Strophe.Test.room_name); 58 | }); 59 | test("leave a room test", function() { 60 | var iqid = Strophe.Test 61 | .connection.muc.leave(Strophe.Test.room_name, 62 | "testnick", 63 | function() { 64 | $('#muc_item').append("left room "+ 65 | Strophe.Test.room_name); 66 | }); 67 | if(iqid) 68 | ok(true, 69 | "left room"); 70 | }); 71 | }); 72 | }); 73 | }, 74 | 75 | connectCallback: function(status,cond) { 76 | var error_message = null; 77 | if(status == Strophe.Status.CONNECTED) 78 | { 79 | $('#run_tests').show(); 80 | $('#disconnect').show(); 81 | var bare_jid = 82 | Strophe.getBareJidFromJid(Strophe.Test.connection.jid) 83 | .split("@")[0]; 84 | } 85 | else if (status == Strophe.Status.DISCONNECTED || status == Strophe.Status.DICONNECTING) 86 | { 87 | $('#run_tests').hide(); 88 | $('#disconnect').hide(); 89 | } 90 | else if ((status == 0) || (status == Strophe.Status.CONNFAIL)) 91 | { 92 | error_message = "Failed to connect to xmpp server."; 93 | } 94 | else if (status == Strophe.Status.AUTHFAIL) 95 | { 96 | error_message = "Failed to authenticate to xmpp server."; 97 | } 98 | if(error_message) 99 | { 100 | $('muc_item').text(error_message); 101 | 102 | } 103 | } 104 | }; 105 | -------------------------------------------------------------------------------- /tests/pubsub.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 |

    QUnit tests for Strophe

    19 |

    20 |
    21 | Disconnect. 22 |
    23 |
    24 |
    25 |
      26 |
      27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/pubsub.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | Strophe.Test = { 4 | BOSH_URL: "/xmpp-httpbind", 5 | XMPP_DOMAIN: 'localhost', 6 | PUBSUB_COMPONENT: "pubsub.localhost", 7 | _node_name: "", //node name created in connectCallback function 8 | connection: null, //connection object created in run function 9 | 10 | run: function() { 11 | $(document).ready(function(){ 12 | //Connect strophe, uses localhost to test 13 | Strophe.Test.connection = new Strophe.Connection(Strophe.Test.BOSH_URL); 14 | 15 | //connect anonymously to run most tests 16 | Strophe.Test.connection.connect(Strophe.Test.XMPP_DOMAIN, 17 | null, 18 | Strophe.Test.connectCallback); 19 | 20 | //set up the test client UI 21 | $("#disconnect").click(function() { 22 | Strophe.Test.connection.disconnect(); 23 | }); 24 | $("#run_tests").click(function() { 25 | test("Anonymous connection test.", function() { 26 | if(Strophe.Test.connection.connected) 27 | { 28 | ok( true, "all good"); 29 | } 30 | else 31 | { 32 | ok( false, "not connected anonymously"); 33 | } 34 | }); 35 | 36 | test("Create default node test.",function(){ 37 | var iqid = Strophe.Test.connection.pubsub 38 | .createNode(Strophe.Test.connection.jid, 39 | Strophe.Test.PUBSUB_COMPONENT, 40 | Strophe.Test._node_name, {}, 41 | function(stanza) { 42 | test("handled create node.", 43 | function() { 44 | var error = $(stanza).find("error"); 45 | 46 | if(error.length == 0) 47 | { 48 | ok(true, "returned"); 49 | } 50 | else 51 | { 52 | ok(false,"error creating node."); 53 | } 54 | }); 55 | }); 56 | ok(true,"sent create request. "+ iqid); 57 | }); 58 | 59 | test("subscribe to a node",function() { 60 | var iqid = Strophe.Test.connection.pubsub 61 | .subscribe(Strophe.Test.connection.jid, 62 | Strophe.Test.PUBSUB_COMPONENT, 63 | Strophe.Test._node_name, 64 | [], 65 | function(stanza) { 66 | test("items received", function() { 67 | console.log(stanza); 68 | if($(stanza).length > 0) 69 | { 70 | ok(true,"item received."); 71 | } 72 | else 73 | { 74 | ok(false,"no items."); 75 | } 76 | }); 77 | }, 78 | function(stanza) { 79 | var error = $(stanza).find("error"); 80 | 81 | test("handled subscribe", 82 | function() { 83 | if(error.length == 0) 84 | { 85 | ok(true,"subscribed"); 86 | } 87 | else 88 | { 89 | console.log(error.get(0)); 90 | ok(false, 91 | "not subscribed"); 92 | } 93 | 94 | }); 95 | }); 96 | 97 | if(iqid) 98 | ok(true, 99 | "subscribed to " + Strophe.Test._node_name); 100 | }); 101 | 102 | test("publish to a node",function() { 103 | var iqid = Strophe.Test.connection.pubsub 104 | .publish(Strophe.Test.connection.jid, 105 | Strophe.Test.PUBSUB_COMPONENT, 106 | Strophe.Test._node_name, 107 | {test:'test'}, 108 | function(stanza) { 109 | var error = $(stanza).find("error"); 110 | 111 | test("handled published item", 112 | function() { 113 | if(error.length == 0) 114 | { 115 | ok(true,"got item"); 116 | } 117 | else 118 | { 119 | ok(false, 120 | "no item"); 121 | } 122 | 123 | }); 124 | }); 125 | 126 | if(iqid) 127 | ok(true, 128 | "published to " + Strophe.Test._node_name); 129 | 130 | }); 131 | 132 | test("subscribe to a node with options",function() { 133 | var keyword_elem = Strophe.xmlElement("field", 134 | [["var", 135 | "http://stanziq.com/search#keyword"], 136 | ["type", 137 | 'text-single'], 138 | ["label", 139 | "keyword to match"]]); 140 | var value = Strophe.xmlElement("value",[]); 141 | var text = Strophe.xmlTextNode("crazy"); 142 | value.appendChild(text); 143 | keyword_elem.appendChild(value); 144 | 145 | var iqid = Strophe.Test.connection.pubsub 146 | .subscribe(Strophe.Test.connection.jid, 147 | Strophe.Test.PUBSUB_COMPONENT, 148 | Strophe.Test._node_name, 149 | [keyword_elem], 150 | function(stanza) {console.log(stanza);}, 151 | function(stanza) { 152 | 153 | var error = $(stanza).find("error"); 154 | 155 | test("handled subscribe with options", 156 | function() { 157 | if(error.length == 0) 158 | { 159 | ok(true,"search subscribed"); 160 | } 161 | else 162 | { 163 | console.log(error.get(0)); 164 | ok(false, 165 | "search not subscribed"); 166 | } 167 | 168 | }); 169 | }); 170 | 171 | if(iqid) 172 | ok(true, 173 | "subscribed to search"); 174 | }); 175 | 176 | test("unsubscribe to a node",function() { 177 | var iqid = Strophe.Test.connection.pubsub 178 | .unsubscribe(Strophe.Test.connection.jid, 179 | Strophe.Test.PUBSUB_COMPONENT, 180 | Strophe.Test._node_name, 181 | function(stanza) { 182 | var error = $(stanza).find("error"); 183 | 184 | test("handled unsubscribe", 185 | function() { 186 | if(error.length == 0) 187 | { 188 | ok(true,"unsubscribed"); 189 | } 190 | else 191 | { 192 | console.log(error.get(0)); 193 | ok(false, 194 | "unable to unsubscribed"); 195 | } 196 | }); 197 | }); 198 | 199 | if(iqid) 200 | ok(true, 201 | "unsubscribed from search with no options."); 202 | }); 203 | 204 | test("test items retrieval",function(){ 205 | var itemid = Strophe.Test.connection.pubsub 206 | .items(Strophe.Test.connection.jid, 207 | Strophe.Test.PUBSUB_COMPONENT, 208 | Strophe.Test._node_name, 209 | function(stanza) { 210 | ok(true,"item request successful."); 211 | }, 212 | function(stanza) { 213 | ok(false,"failed to send request."); 214 | }); 215 | 216 | if(itemid) 217 | { 218 | ok(true,"item request sent."); 219 | } 220 | }); 221 | 222 | test("test sendIQ interface.",function(){ 223 | var sendiq_good = false; 224 | //setup timeout for sendIQ for 3 seconds 225 | setTimeout(function() { 226 | test("Timeout check", function () { 227 | ok(sendiq_good, "The iq didn't timeout."); 228 | }); 229 | }, 3000); 230 | 231 | //send a pubsub subscribe stanza 232 | 233 | var sub = $iq({from:Strophe.Test.connection.jid, 234 | to:Strophe.Test.PUBSUB_COMPONENT, 235 | type:'set'}) 236 | .c('pubsub', { xmlns:Strophe.NS.PUBSUB }) 237 | .c('subscribe', 238 | {node:Strophe.Test._node_name, 239 | jid:Strophe.Test.connection.jid}); 240 | var stanza=sub.tree(); 241 | //call sendIQ with several call backs 242 | Strophe.Test.connection 243 | .sendIQ(stanza, 244 | function(stanza) { 245 | test("iq sent",function() { 246 | sendiq_good = true; 247 | ok(true,"iq sent succesfully."); 248 | }); 249 | }, 250 | function(stz) { 251 | test("iq fail",function() { 252 | if (stz) 253 | sendiq_good = true; 254 | console.log(stanza); 255 | ok(true,"failed to send iq."); 256 | }); 257 | }); 258 | }); 259 | 260 | test("test sendIQ failed.",function(){ 261 | var sub = $iq({from:Strophe.Test.connection.jid, 262 | to:Strophe.Test.PUBSUB_COMPONENT, 263 | type:'get'}); 264 | 265 | //call sendIQ with several call backs 266 | Strophe.Test.connection 267 | .sendIQ(sub.tree(), 268 | function(stanza) { 269 | console.log(stanza); 270 | test("iq sent",function() { 271 | ok(false, 272 | "iq sent succesfully when should have failed."); 273 | }); 274 | }, 275 | function(stanza) { 276 | test("iq fail",function() { 277 | ok(true, 278 | "success on failure test: failed to send iq."); 279 | }); 280 | }); 281 | }); 282 | }); 283 | }); 284 | }, 285 | 286 | connectCallback: function(status,cond) { 287 | var error_message = null; 288 | if(status == Strophe.Status.CONNECTED) 289 | { 290 | $('#run_tests').show(); 291 | $('#disconnect').show(); 292 | var bare_jid = Strophe.getBareJidFromJid(Strophe.Test.connection.jid).split("@")[0]; 293 | Strophe.Test._node_name = "/home/"+Strophe.Test.XMPP_DOMAIN+"/"+bare_jid; 294 | } 295 | else if (status == Strophe.Status.DISCONNECTED || status == Strophe.Status.DICONNECTING) 296 | { 297 | $('#run_tests').hide(); 298 | $('#disconnect').hide(); 299 | } 300 | else if ((status == 0) || (status == Strophe.Status.CONNFAIL)) 301 | { 302 | error_message = "Failed to connect to xmpp server."; 303 | } 304 | else if (status == Strophe.Status.AUTHFAIL) 305 | { 306 | error_message = "Failed to authenticate to xmpp server."; 307 | } 308 | if(error_message) 309 | { 310 | $('published_item').text(error_message); 311 | 312 | } 313 | } 314 | }; 315 | -------------------------------------------------------------------------------- /tests/strophe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Strophe.js Tests 22 | 23 | 24 |

      Strophe.js Tests

      25 |

      26 |

      27 |
        28 |
        29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/testrunner.js: -------------------------------------------------------------------------------- 1 | /* 2 | * QUnit - jQuery unit testrunner 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2008 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * and GPL (GPL-LICENSE.txt) licenses. 9 | * 10 | * $Id$ 11 | */ 12 | 13 | (function($) { 14 | 15 | // Tests for equality any JavaScript type and structure without unexpected results. 16 | // Discussions and reference: http://philrathe.com/articles/equiv 17 | // Test suites: http://philrathe.com/tests/equiv 18 | // Author: Philippe Rathé 19 | var equiv = function () { 20 | 21 | var innerEquiv; // the real equiv function 22 | var callers = []; // stack to decide between skip/abort functions 23 | 24 | // Determine what is o. 25 | function hoozit(o) { 26 | if (typeof o === "string") { 27 | return "string"; 28 | 29 | } else if (typeof o === "boolean") { 30 | return "boolean"; 31 | 32 | } else if (typeof o === "number") { 33 | 34 | if (isNaN(o)) { 35 | return "nan"; 36 | } else { 37 | return "number"; 38 | } 39 | 40 | } else if (typeof o === "undefined") { 41 | return "undefined"; 42 | 43 | // consider: typeof null === object 44 | } else if (o === null) { 45 | return "null"; 46 | 47 | // consider: typeof [] === object 48 | } else if (o instanceof Array) { 49 | return "array"; 50 | 51 | // consider: typeof new Date() === object 52 | } else if (o instanceof Date) { 53 | return "date"; 54 | 55 | // consider: /./ instanceof Object; 56 | // /./ instanceof RegExp; 57 | // typeof /./ === "function"; // => false in IE and Opera, 58 | // true in FF and Safari 59 | } else if (o instanceof RegExp) { 60 | return "regexp"; 61 | 62 | } else if (typeof o === "object") { 63 | return "object"; 64 | 65 | } else if (o instanceof Function) { 66 | return "function"; 67 | } 68 | } 69 | 70 | // Call the o related callback with the given arguments. 71 | function bindCallbacks(o, callbacks, args) { 72 | var prop = hoozit(o); 73 | if (prop) { 74 | if (hoozit(callbacks[prop]) === "function") { 75 | return callbacks[prop].apply(callbacks, args); 76 | } else { 77 | return callbacks[prop]; // or undefined 78 | } 79 | } 80 | } 81 | 82 | var callbacks = function () { 83 | 84 | // for string, boolean, number and null 85 | function useStrictEquality(b, a) { 86 | return a === b; 87 | } 88 | 89 | return { 90 | "string": useStrictEquality, 91 | "boolean": useStrictEquality, 92 | "number": useStrictEquality, 93 | "null": useStrictEquality, 94 | "undefined": useStrictEquality, 95 | 96 | "nan": function (b) { 97 | return isNaN(b); 98 | }, 99 | 100 | "date": function (b, a) { 101 | return hoozit(b) === "date" && a.valueOf() === b.valueOf(); 102 | }, 103 | 104 | "regexp": function (b, a) { 105 | return hoozit(b) === "regexp" && 106 | a.source === b.source && // the regex itself 107 | a.global === b.global && // and its modifers (gmi) ... 108 | a.ignoreCase === b.ignoreCase && 109 | a.multiline === b.multiline; 110 | }, 111 | 112 | // - skip when the property is a method of an instance (OOP) 113 | // - abort otherwise, 114 | // initial === would have catch identical references anyway 115 | "function": function () { 116 | var caller = callers[callers.length - 1]; 117 | return caller !== Object && 118 | typeof caller !== "undefined"; 119 | }, 120 | 121 | "array": function (b, a) { 122 | var i; 123 | var len; 124 | 125 | // b could be an object literal here 126 | if ( ! (hoozit(b) === "array")) { 127 | return false; 128 | } 129 | 130 | len = a.length; 131 | if (len !== b.length) { // safe and faster 132 | return false; 133 | } 134 | for (i = 0; i < len; i++) { 135 | if( ! innerEquiv(a[i], b[i])) { 136 | return false; 137 | } 138 | } 139 | return true; 140 | }, 141 | 142 | "object": function (b, a) { 143 | var i; 144 | var eq = true; // unless we can proove it 145 | var aProperties = [], bProperties = []; // collection of strings 146 | 147 | // comparing constructors is more strict than using instanceof 148 | if ( a.constructor !== b.constructor) { 149 | return false; 150 | } 151 | 152 | // stack constructor before traversing properties 153 | callers.push(a.constructor); 154 | 155 | for (i in a) { // be strict: don't ensures hasOwnProperty and go deep 156 | 157 | aProperties.push(i); // collect a's properties 158 | 159 | if ( ! innerEquiv(a[i], b[i])) { 160 | eq = false; 161 | } 162 | } 163 | 164 | callers.pop(); // unstack, we are done 165 | 166 | for (i in b) { 167 | bProperties.push(i); // collect b's properties 168 | } 169 | 170 | // Ensures identical properties name 171 | return eq && innerEquiv(aProperties.sort(), bProperties.sort()); 172 | } 173 | }; 174 | }(); 175 | 176 | innerEquiv = function () { // can take multiple arguments 177 | var args = Array.prototype.slice.apply(arguments); 178 | if (args.length < 2) { 179 | return true; // end transition 180 | } 181 | 182 | return (function (a, b) { 183 | if (a === b) { 184 | return true; // catch the most you can 185 | 186 | } else if (typeof a !== typeof b || a === null || b === null || typeof a === "undefined" || typeof b === "undefined") { 187 | return false; // don't lose time with error prone cases 188 | 189 | } else { 190 | return bindCallbacks(a, callbacks, [b, a]); 191 | } 192 | 193 | // apply transition with (1..n) arguments 194 | })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); 195 | }; 196 | 197 | return innerEquiv; 198 | }(); // equiv 199 | 200 | var config = { 201 | stats: { 202 | all: 0, 203 | bad: 0 204 | }, 205 | queue: [], 206 | // block until document ready 207 | blocking: true, 208 | //restrict modules/tests by get parameters 209 | filters: location.search.length > 1 && $.map( location.search.slice(1).split('&'), decodeURIComponent ), 210 | isLocal: !!(window.location.protocol == 'file:') 211 | }; 212 | 213 | // public API as global methods 214 | $.extend(window, { 215 | test: test, 216 | module: module, 217 | expect: expect, 218 | ok: ok, 219 | equals: equals, 220 | start: start, 221 | stop: stop, 222 | reset: reset, 223 | isLocal: config.isLocal, 224 | same: function(a, b, message) { 225 | push(equiv(a, b), a, b, message); 226 | }, 227 | QUnit: { 228 | equiv: equiv 229 | }, 230 | // legacy methods below 231 | isSet: isSet, 232 | isObj: isObj, 233 | compare: function() { 234 | throw "compare is deprecated - use same() instead"; 235 | }, 236 | compare2: function() { 237 | throw "compare2 is deprecated - use same() instead"; 238 | }, 239 | serialArray: function() { 240 | throw "serialArray is deprecated - use jsDump.parse() instead"; 241 | }, 242 | q: q, 243 | t: t, 244 | url: url, 245 | triggerEvent: triggerEvent 246 | }); 247 | 248 | $(window).load(function() { 249 | $('#userAgent').html(navigator.userAgent); 250 | var head = $('
        ').insertAfter("#userAgent"); 251 | $('').attr("disabled", true).prependTo(head).click(function() { 252 | $('li.pass')[this.checked ? 'hide' : 'show'](); 253 | }); 254 | runTest(); 255 | }); 256 | 257 | function synchronize(callback) { 258 | config.queue.push(callback); 259 | if(!config.blocking) { 260 | process(); 261 | } 262 | } 263 | 264 | function process() { 265 | while(config.queue.length && !config.blocking) { 266 | config.queue.shift()(); 267 | } 268 | } 269 | 270 | function stop(timeout) { 271 | config.blocking = true; 272 | if (timeout) 273 | config.timeout = setTimeout(function() { 274 | ok( false, "Test timed out" ); 275 | start(); 276 | }, timeout); 277 | } 278 | function start() { 279 | // A slight delay, to avoid any current callbacks 280 | setTimeout(function() { 281 | if(config.timeout) 282 | clearTimeout(config.timeout); 283 | config.blocking = false; 284 | process(); 285 | }, 13); 286 | } 287 | 288 | function validTest( name ) { 289 | var filters = config.filters; 290 | if( !filters ) 291 | return true; 292 | 293 | var i = filters.length, 294 | run = false; 295 | while( i-- ){ 296 | var filter = filters[i], 297 | not = filter.charAt(0) == '!'; 298 | if( not ) 299 | filter = filter.slice(1); 300 | if( name.indexOf(filter) != -1 ) 301 | return !not; 302 | if( not ) 303 | run = true; 304 | } 305 | return run; 306 | } 307 | 308 | function runTest() { 309 | config.blocking = false; 310 | var started = +new Date; 311 | config.fixture = document.getElementById('main').innerHTML; 312 | config.ajaxSettings = $.ajaxSettings; 313 | synchronize(function() { 314 | $('

        ').html(['Tests completed in ', 315 | +new Date - started, ' milliseconds.
        ', 316 | '', config.stats.bad, ' tests of ', config.stats.all, ' failed.

        '] 317 | .join('')) 318 | .appendTo("body"); 319 | $("#banner").addClass(config.stats.bad ? "fail" : "pass"); 320 | }); 321 | } 322 | 323 | function test(name, callback) { 324 | if(config.currentModule) 325 | name = config.currentModule + " module: " + name; 326 | var lifecycle = $.extend({ 327 | setup: function() {}, 328 | teardown: function() {} 329 | }, config.moduleLifecycle); 330 | 331 | if ( !validTest(name) ) 332 | return; 333 | 334 | synchronize(function() { 335 | config.assertions = []; 336 | config.expected = null; 337 | try { 338 | lifecycle.setup(); 339 | callback(); 340 | lifecycle.teardown(); 341 | } catch(e) { 342 | if( typeof console != "undefined" && console.error && console.warn ) { 343 | console.error("Test " + name + " died, exception and test follows"); 344 | console.error(e); 345 | console.warn(callback.toString()); 346 | } 347 | config.assertions.push( { 348 | result: false, 349 | message: "Died on test #" + (config.assertions.length + 1) + ": " + e.message 350 | }); 351 | } 352 | }); 353 | synchronize(function() { 354 | try { 355 | reset(); 356 | } catch(e) { 357 | if( typeof console != "undefined" && console.error && console.warn ) { 358 | console.error("reset() failed, following Test " + name + ", exception and reset fn follows"); 359 | console.error(e); 360 | console.warn(reset.toString()); 361 | } 362 | } 363 | 364 | if(config.expected && config.expected != config.assertions.length) { 365 | config.assertions.push({ 366 | result: false, 367 | message: "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" 368 | }); 369 | } 370 | 371 | var good = 0, bad = 0; 372 | var ol = $("
          ").hide(); 373 | config.stats.all += config.assertions.length; 374 | for ( var i = 0; i < config.assertions.length; i++ ) { 375 | var assertion = config.assertions[i]; 376 | $("
        1. ").addClass(assertion.result ? "pass" : "fail").text(assertion.message || "(no message)").appendTo(ol); 377 | assertion.result ? good++ : bad++; 378 | } 379 | config.stats.bad += bad; 380 | 381 | var b = $("").html(name + " (" + bad + ", " + good + ", " + config.assertions.length + ")") 382 | .click(function(){ 383 | $(this).next().toggle(); 384 | }) 385 | .dblclick(function(event) { 386 | var target = $(event.target).filter("strong").clone(); 387 | if ( target.length ) { 388 | target.children().remove(); 389 | location.href = location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent($.trim(target.text())); 390 | } 391 | }); 392 | 393 | $("
        2. ").addClass(bad ? "fail" : "pass").append(b).append(ol).appendTo("#tests"); 394 | 395 | if(bad) { 396 | $("#filter").attr("disabled", null); 397 | } 398 | }); 399 | } 400 | 401 | // call on start of module test to prepend name to all tests 402 | function module(name, lifecycle) { 403 | config.currentModule = name; 404 | config.moduleLifecycle = lifecycle; 405 | } 406 | 407 | /** 408 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 409 | */ 410 | function expect(asserts) { 411 | config.expected = asserts; 412 | } 413 | 414 | /** 415 | * Resets the test setup. Useful for tests that modify the DOM. 416 | */ 417 | function reset() { 418 | $("#main").html( config.fixture ); 419 | $.event.global = {}; 420 | $.ajaxSettings = $.extend({}, config.ajaxSettings); 421 | } 422 | 423 | /** 424 | * Asserts true. 425 | * @example ok( $("a").size() > 5, "There must be at least 5 anchors" ); 426 | */ 427 | function ok(a, msg) { 428 | config.assertions.push({ 429 | result: !!a, 430 | message: msg 431 | }); 432 | } 433 | 434 | /** 435 | * Asserts that two arrays are the same 436 | */ 437 | function isSet(a, b, msg) { 438 | function serialArray( a ) { 439 | var r = []; 440 | 441 | if ( a && a.length ) 442 | for ( var i = 0; i < a.length; i++ ) { 443 | var str = a[i].nodeName; 444 | if ( str ) { 445 | str = str.toLowerCase(); 446 | if ( a[i].id ) 447 | str += "#" + a[i].id; 448 | } else 449 | str = a[i]; 450 | r.push( str ); 451 | } 452 | 453 | return "[ " + r.join(", ") + " ]"; 454 | } 455 | var ret = true; 456 | if ( a && b && a.length != undefined && a.length == b.length ) { 457 | for ( var i = 0; i < a.length; i++ ) 458 | if ( a[i] != b[i] ) 459 | ret = false; 460 | } else 461 | ret = false; 462 | config.assertions.push({ 463 | result: ret, 464 | message: !ret ? (msg + " expected: " + serialArray(b) + " result: " + serialArray(a)) : msg 465 | }); 466 | } 467 | 468 | /** 469 | * Asserts that two objects are equivalent 470 | */ 471 | function isObj(a, b, msg) { 472 | var ret = true; 473 | 474 | if ( a && b ) { 475 | for ( var i in a ) 476 | if ( a[i] != b[i] ) 477 | ret = false; 478 | 479 | for ( i in b ) 480 | if ( a[i] != b[i] ) 481 | ret = false; 482 | } else 483 | ret = false; 484 | 485 | config.assertions.push({ 486 | result: ret, 487 | message: msg 488 | }); 489 | } 490 | 491 | /** 492 | * Returns an array of elements with the given IDs, eg. 493 | * @example q("main", "foo", "bar") 494 | * @result [
          , , ] 495 | */ 496 | function q() { 497 | var r = []; 498 | for ( var i = 0; i < arguments.length; i++ ) 499 | r.push( document.getElementById( arguments[i] ) ); 500 | return r; 501 | } 502 | 503 | /** 504 | * Asserts that a select matches the given IDs 505 | * @example t("Check for something", "//[a]", ["foo", "baar"]); 506 | * @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baar' 507 | */ 508 | function t(a,b,c) { 509 | var f = $(b); 510 | var s = ""; 511 | for ( var i = 0; i < f.length; i++ ) 512 | s += (s && ",") + '"' + f[i].id + '"'; 513 | isSet(f, q.apply(q,c), a + " (" + b + ")"); 514 | } 515 | 516 | /** 517 | * Add random number to url to stop IE from caching 518 | * 519 | * @example url("data/test.html") 520 | * @result "data/test.html?10538358428943" 521 | * 522 | * @example url("data/test.php?foo=bar") 523 | * @result "data/test.php?foo=bar&10538358345554" 524 | */ 525 | function url(value) { 526 | return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000); 527 | } 528 | 529 | /** 530 | * Checks that the first two arguments are equal, with an optional message. 531 | * Prints out both actual and expected values. 532 | * 533 | * Prefered to ok( actual == expected, message ) 534 | * 535 | * @example equals( $.format("Received {0} bytes.", 2), "Received 2 bytes." ); 536 | * 537 | * @param Object actual 538 | * @param Object expected 539 | * @param String message (optional) 540 | */ 541 | function equals(actual, expected, message) { 542 | push(expected == actual, actual, expected, message); 543 | } 544 | 545 | function push(result, actual, expected, message) { 546 | message = message || (result ? "okay" : "failed"); 547 | config.assertions.push({ 548 | result: result, 549 | message: result ? message + ": " + expected : message + ", expected: " + jsDump.parse(expected) + " result: " + jsDump.parse(actual) 550 | }); 551 | } 552 | 553 | /** 554 | * Trigger an event on an element. 555 | * 556 | * @example triggerEvent( document.body, "click" ); 557 | * 558 | * @param DOMElement elem 559 | * @param String type 560 | */ 561 | function triggerEvent( elem, type, event ) { 562 | if ( $.browser.mozilla || $.browser.opera ) { 563 | event = document.createEvent("MouseEvents"); 564 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 565 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 566 | elem.dispatchEvent( event ); 567 | } else if ( $.browser.msie ) { 568 | elem.fireEvent("on"+type); 569 | } 570 | } 571 | 572 | })(jQuery); 573 | 574 | /** 575 | * jsDump 576 | * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com 577 | * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) 578 | * Date: 5/15/2008 579 | * @projectDescription Advanced and extensible data dumping for Javascript. 580 | * @version 1.0.0 581 | * @author Ariel Flesler 582 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 583 | */ 584 | (function(){ 585 | function quote( str ){ 586 | return '"' + str.toString().replace(/"/g, '\\"') + '"'; 587 | }; 588 | function literal( o ){ 589 | return o + ''; 590 | }; 591 | function join( pre, arr, post ){ 592 | var s = jsDump.separator(), 593 | base = jsDump.indent(); 594 | inner = jsDump.indent(1); 595 | if( arr.join ) 596 | arr = arr.join( ',' + s + inner ); 597 | if( !arr ) 598 | return pre + post; 599 | return [ pre, inner + arr, base + post ].join(s); 600 | }; 601 | function array( arr ){ 602 | var i = arr.length, ret = Array(i); 603 | this.up(); 604 | while( i-- ) 605 | ret[i] = this.parse( arr[i] ); 606 | this.down(); 607 | return join( '[', ret, ']' ); 608 | }; 609 | 610 | var reName = /^function (\w+)/; 611 | 612 | var jsDump = window.jsDump = { 613 | parse:function( obj, type ){//type is used mostly internally, you can fix a (custom)type in advance 614 | var parser = this.parsers[ type || this.typeOf(obj) ]; 615 | type = typeof parser; 616 | 617 | return type == 'function' ? parser.call( this, obj ) : 618 | type == 'string' ? parser : 619 | this.parsers.error; 620 | }, 621 | typeOf:function( obj ){ 622 | var type = typeof obj, 623 | f = 'function';//we'll use it 3 times, save it 624 | return type != 'object' && type != f ? type : 625 | !obj ? 'null' : 626 | obj.exec ? 'regexp' :// some browsers (FF) consider regexps functions 627 | obj.getHours ? 'date' : 628 | obj.scrollBy ? 'window' : 629 | obj.nodeName == '#document' ? 'document' : 630 | obj.nodeName ? 'node' : 631 | obj.item ? 'nodelist' : // Safari reports nodelists as functions 632 | obj.callee ? 'arguments' : 633 | obj.call || obj.constructor != Array && //an array would also fall on this hack 634 | (obj+'').indexOf(f) != -1 ? f : //IE reports functions like alert, as objects 635 | 'length' in obj ? 'array' : 636 | type; 637 | }, 638 | separator:function(){ 639 | return this.multiline ? this.HTML ? '
          ' : '\n' : this.HTML ? ' ' : ' '; 640 | }, 641 | indent:function( extra ){// extra can be a number, shortcut for increasing-calling-decreasing 642 | if( !this.multiline ) 643 | return ''; 644 | var chr = this.indentChar; 645 | if( this.HTML ) 646 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 647 | return Array( this._depth_ + (extra||0) ).join(chr); 648 | }, 649 | up:function( a ){ 650 | this._depth_ += a || 1; 651 | }, 652 | down:function( a ){ 653 | this._depth_ -= a || 1; 654 | }, 655 | setParser:function( name, parser ){ 656 | this.parsers[name] = parser; 657 | }, 658 | // The next 3 are exposed so you can use them 659 | quote:quote, 660 | literal:literal, 661 | join:join, 662 | // 663 | _depth_: 1, 664 | // This is the list of parsers, to modify them, use jsDump.setParser 665 | parsers:{ 666 | window: '[Window]', 667 | document: '[Document]', 668 | error:'[ERROR]', //when no parser is found, shouldn't happen 669 | unknown: '[Unknown]', 670 | 'null':'null', 671 | undefined:'undefined', 672 | 'function':function( fn ){ 673 | var ret = 'function', 674 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 675 | if( name ) 676 | ret += ' ' + name; 677 | ret += '('; 678 | 679 | ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); 680 | return join( ret, this.parse(fn,'functionCode'), '}' ); 681 | }, 682 | array: array, 683 | nodelist: array, 684 | arguments: array, 685 | object:function( map ){ 686 | var ret = [ ]; 687 | this.up(); 688 | for( var key in map ) 689 | ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); 690 | this.down(); 691 | return join( '{', ret, '}' ); 692 | }, 693 | node:function( node ){ 694 | var open = this.HTML ? '<' : '<', 695 | close = this.HTML ? '>' : '>'; 696 | 697 | var tag = node.nodeName.toLowerCase(), 698 | ret = open + tag; 699 | 700 | for( var a in this.DOMAttrs ){ 701 | var val = node[this.DOMAttrs[a]]; 702 | if( val ) 703 | ret += ' ' + a + '=' + this.parse( val, 'attribute' ); 704 | } 705 | return ret + close + open + '/' + tag + close; 706 | }, 707 | functionArgs:function( fn ){//function calls it internally, it's the arguments part of the function 708 | var l = fn.length; 709 | if( !l ) return ''; 710 | 711 | var args = Array(l); 712 | while( l-- ) 713 | args[l] = String.fromCharCode(97+l);//97 is 'a' 714 | return ' ' + args.join(', ') + ' '; 715 | }, 716 | key:quote, //object calls it internally, the key part of an item in a map 717 | functionCode:'[code]', //function calls it internally, it's the content of the function 718 | attribute:quote, //node calls it internally, it's an html attribute value 719 | string:quote, 720 | date:quote, 721 | regexp:literal, //regex 722 | number:literal, 723 | 'boolean':literal 724 | }, 725 | DOMAttrs:{//attributes to dump from nodes, name=>realName 726 | id:'id', 727 | name:'name', 728 | 'class':'className' 729 | }, 730 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 731 | indentChar:' ',//indentation unit 732 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 733 | }; 734 | 735 | })(); 736 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | module("JIDs"); 3 | 4 | test("Normal JID", function () { 5 | var jid = "darcy@pemberley.lit/library"; 6 | equal(Strophe.getNodeFromJid(jid), "darcy", 7 | "Node should be 'darcy'"); 8 | equal(Strophe.getDomainFromJid(jid), "pemberley.lit", 9 | "Domain should be 'pemberley.lit'"); 10 | equal(Strophe.getResourceFromJid(jid), "library", 11 | "Node should be 'library'"); 12 | equal(Strophe.getBareJidFromJid(jid), 13 | "darcy@pemberley.lit", 14 | "Bare JID should be 'darcy@pemberley.lit'"); 15 | }); 16 | 17 | test("Weird node (unescaped)", function () { 18 | var jid = "darcy@netherfield.lit@pemberley.lit/library"; 19 | equal(Strophe.getNodeFromJid(jid), "darcy", 20 | "Node should be 'darcy'"); 21 | equal(Strophe.getDomainFromJid(jid), 22 | "netherfield.lit@pemberley.lit", 23 | "Domain should be 'netherfield.lit@pemberley.lit'"); 24 | equal(Strophe.getResourceFromJid(jid), "library", 25 | "Resource should be 'library'"); 26 | equal(Strophe.getBareJidFromJid(jid), 27 | "darcy@netherfield.lit@pemberley.lit", 28 | "Bare JID should be 'darcy@netherfield.lit@pemberley.lit'"); 29 | }); 30 | 31 | test("Weird node (escaped)", function () { 32 | var escapedNode = Strophe.escapeNode("darcy@netherfield.lit"); 33 | var jid = escapedNode + "@pemberley.lit/library"; 34 | equal(Strophe.getNodeFromJid(jid), "darcy\\40netherfield.lit", 35 | "Node should be 'darcy\\40netherfield.lit'"); 36 | equal(Strophe.getDomainFromJid(jid), 37 | "pemberley.lit", 38 | "Domain should be 'pemberley.lit'"); 39 | equal(Strophe.getResourceFromJid(jid), "library", 40 | "Resource should be 'library'"); 41 | equal(Strophe.getBareJidFromJid(jid), 42 | "darcy\\40netherfield.lit@pemberley.lit", 43 | "Bare JID should be 'darcy\\40netherfield.lit@pemberley.lit'"); 44 | }); 45 | 46 | test("Weird resource", function () { 47 | var jid = "books@chat.pemberley.lit/darcy@pemberley.lit/library"; 48 | equal(Strophe.getNodeFromJid(jid), "books", 49 | "Node should be 'books'"); 50 | equal(Strophe.getDomainFromJid(jid), "chat.pemberley.lit", 51 | "Domain should be 'chat.pemberley.lit'"); 52 | equal(Strophe.getResourceFromJid(jid), 53 | "darcy@pemberley.lit/library", 54 | "Resource should be 'darcy@pemberley.lit/library'"); 55 | equal(Strophe.getBareJidFromJid(jid), 56 | "books@chat.pemberley.lit", 57 | "Bare JID should be 'books@chat.pemberley.lit'"); 58 | }); 59 | 60 | module("Builder"); 61 | 62 | test("Correct namespace (#32)", function () { 63 | var stanzas = [new Strophe.Builder("message", {foo: "asdf"}).tree(), 64 | $build("iq", {}).tree(), 65 | $pres().tree()]; 66 | $.each(stanzas, function () { 67 | equal($(this).attr('xmlns'), Strophe.NS.CLIENT, 68 | "Namespace should be '" + Strophe.NS.CLIENT + "'"); 69 | }); 70 | }); 71 | 72 | test("send() accepts Builders (#27)", function () { 73 | var stanza = $pres(); 74 | var conn = new Strophe.Connection(""); 75 | // fake connection callback to avoid errors 76 | conn.connect_callback = function () {}; 77 | 78 | ok(conn._data.length === 0, "Output queue is clean"); 79 | try { 80 | conn.send(stanza); 81 | } catch (e) {} 82 | ok(conn._data.length === 1, "Output queue contains an element"); 83 | }); 84 | 85 | test("send() does not accept strings", function () { 86 | var stanza = ""; 87 | var conn = new Strophe.Connection(""); 88 | // fake connection callback to avoid errors 89 | conn.connect_callback = function () {}; 90 | expect(1); 91 | try { 92 | conn.send(stanza); 93 | } catch (e) { 94 | equal(e.name, "StropheError", "send() should throw exception"); 95 | } 96 | }); 97 | 98 | test("Builder with XML attribute escaping test", function () { 99 | var text = ""; 100 | var expected = ""; 101 | var pres = $pres({to: text}); 102 | equal(pres.toString(), expected, "< should be escaped"); 103 | 104 | text = "foo&bar"; 105 | expected = ""; 106 | pres = $pres({to: text}); 107 | equal(pres.toString(), expected, "& should be escaped"); 108 | }); 109 | 110 | test("c() accepts text and passes it to xmlElement", function () { 111 | var pres = $pres({from: "darcy@pemberley.lit", to: "books@chat.pemberley.lit"}) 112 | .c("nick", {xmlns: "http://jabber.org/protocol/nick"}, "Darcy"); 113 | var expected = "Darcy"; 114 | equal(pres.toString(), expected, "'Darcy' should be a child of "); 115 | }); 116 | 117 | module("XML"); 118 | 119 | test("XML escaping test", function () { 120 | var text = "s & p"; 121 | var textNode = Strophe.xmlTextNode(text); 122 | equal(Strophe.getText(textNode), "s & p", "should be escaped"); 123 | var text0 = "s < & > p"; 124 | var textNode0 = Strophe.xmlTextNode(text0); 125 | equal(Strophe.getText(textNode0), "s < & > p", "should be escaped"); 126 | var text1 = "s's or \"p\""; 127 | var textNode1 = Strophe.xmlTextNode(text1); 128 | equal(Strophe.getText(textNode1), "s's or "p"", "should be escaped"); 129 | var text2 = "]]>"; 130 | var textNode2 = Strophe.xmlTextNode(text2); 131 | equal(Strophe.getText(textNode2), "<![CDATA[<foo>]]>", "should be escaped"); 132 | var text3 = "]]>"; 133 | var textNode3 = Strophe.xmlTextNode(text3); 134 | equal(Strophe.getText(textNode3), "<![CDATA[]]]]><![CDATA[>]]>", "should be escaped"); 135 | var text4 = "<foo>]]>"; 136 | var textNode4 = Strophe.xmlTextNode(text4); 137 | equal(Strophe.getText(textNode4), "&lt;foo&gt;<![CDATA[<foo>]]>", "should be escaped"); 138 | }); 139 | 140 | test("XML element creation", function () { 141 | var elem = Strophe.xmlElement("message"); 142 | equal(elem.tagName, "message", "Element name should be the same"); 143 | }); 144 | 145 | test("copyElement() double escape bug", function() { 146 | var cloned = Strophe.copyElement(Strophe.xmlGenerator() 147 | .createTextNode('<><>')); 148 | equal(cloned.nodeValue, '<><>'); 149 | }); 150 | 151 | test("XML serializing", function() { 152 | var parser = new DOMParser(); 153 | // Attributes 154 | var element1 = parser.parseFromString("bar","text/xml").documentElement; 155 | equal(Strophe.serialize(element1), "bar", "should be serialized"); 156 | var element2 = parser.parseFromString("bar","text/xml").documentElement; 157 | equal(Strophe.serialize(element2), "bar", "should be serialized"); 158 | // Escaping values 159 | var element3 = parser.parseFromString("a > 'b' & "b" < c","text/xml").documentElement; 160 | equal(Strophe.serialize(element3), "a > 'b' & "b" < c", "should be serialized"); 161 | // Escaping attributes 162 | var element4 = parser.parseFromString("bar","text/xml").documentElement; 163 | equal(Strophe.serialize(element4), "bar", "should be serialized"); 164 | var element5 = parser.parseFromString(" "b"\">bar","text/xml").documentElement; 165 | equal(Strophe.serialize(element5), "bar", "should be serialized"); 166 | // Empty elements 167 | var element6 = parser.parseFromString("","text/xml").documentElement; 168 | equal(Strophe.serialize(element6), "", "should be serialized"); 169 | // Children 170 | var element7 = parser.parseFromString("ab","text/xml").documentElement; 171 | equal(Strophe.serialize(element7), "ab", "should be serialized"); 172 | var element8 = parser.parseFromString("abcd","text/xml").documentElement; 173 | equal(Strophe.serialize(element8), "abcd", "should be serialized"); 174 | // CDATA 175 | var element9 = parser.parseFromString("]]>","text/xml").documentElement; 176 | equal(Strophe.serialize(element9), "]]>", "should be serialized"); 177 | var element10 = parser.parseFromString("]]>","text/xml").documentElement; 178 | equal(Strophe.serialize(element10), "]]>", "should be serialized"); 179 | var element11 = parser.parseFromString("<foo>]]>","text/xml").documentElement; 180 | equal(Strophe.serialize(element11), "<foo>]]>", "should be serialized"); 181 | }); 182 | 183 | module("Handler"); 184 | 185 | test("Full JID matching", function () { 186 | var elem = $msg({from: 'darcy@pemberley.lit/library'}).tree(); 187 | 188 | var hand = new Strophe.Handler(null, null, null, null, null, 189 | 'darcy@pemberley.lit/library'); 190 | equal(hand.isMatch(elem), true, "Full JID should match"); 191 | 192 | hand = new Strophe.Handler(null, null, null, null, null, 193 | 'darcy@pemberley.lit') 194 | equal(hand.isMatch(elem), false, "Bare JID shouldn't match"); 195 | }); 196 | 197 | test("Bare JID matching", function () { 198 | var elem = $msg({from: 'darcy@pemberley.lit/library'}).tree(); 199 | 200 | var hand = new Strophe.Handler(null, null, null, null, null, 201 | 'darcy@pemberley.lit/library', 202 | {matchBare: true}); 203 | equal(hand.isMatch(elem), true, "Full JID should match"); 204 | 205 | hand = new Strophe.Handler(null, null, null, null, null, 206 | 'darcy@pemberley.lit', 207 | {matchBare: true}); 208 | equal(hand.isMatch(elem), true, "Bare JID should match"); 209 | }); 210 | 211 | module("Misc"); 212 | 213 | test("Quoting strings", function () { 214 | var input = '"beep \\40"'; 215 | var conn = new Strophe.Connection(); 216 | var output = conn._quote(input); 217 | equal(output, "\"\\\"beep \\\\40\\\"\"", 218 | "string should be quoted and escaped"); 219 | }); 220 | 221 | test("Function binding", function () { 222 | var spy = sinon.spy(); 223 | var obj = {}; 224 | var arg1 = "foo"; 225 | var arg2 = "bar"; 226 | var arg3 = "baz"; 227 | 228 | var f = spy.bind(obj, arg1, arg2); 229 | f(arg3); 230 | equal(spy.called, true, "bound function should be called"); 231 | equal(spy.calledOn(obj), true, 232 | "bound function should have correct context"); 233 | equal(spy.alwaysCalledWithExactly(arg1, arg2, arg3), 234 | true, 235 | "bound function should get all arguments"); 236 | }); 237 | 238 | module("XHR error handling"); 239 | 240 | // Note that these tests are pretty dependent on the actual code. 241 | 242 | test("Aborted requests do nothing", function () { 243 | Strophe.Connection.prototype._onIdle = function () {}; 244 | var conn = new Strophe.Connection("http://fake"); 245 | 246 | // simulate a finished but aborted request 247 | var req = {id: 43, 248 | sends: 1, 249 | xhr: { 250 | readyState: 4 251 | }, 252 | abort: true}; 253 | 254 | conn._requests = [req]; 255 | 256 | var spy = sinon.spy(); 257 | 258 | conn._onRequestStateChange(spy, req); 259 | 260 | equal(req.abort, false, "abort flag should be toggled"); 261 | equal(conn._requests.length, 1, "_requests should be same length"); 262 | equal(spy.called, false, "callback should not be called"); 263 | }); 264 | 265 | test("Incomplete requests do nothing", function () { 266 | Strophe.Connection.prototype._onIdle = function () {}; 267 | var conn = new Strophe.Connection("http://fake"); 268 | 269 | // simulate a finished but aborted request 270 | var req = {id: 44, 271 | sends: 1, 272 | xhr: { 273 | readyState: 3 274 | }}; 275 | 276 | conn._requests = [req]; 277 | 278 | var spy = sinon.spy(); 279 | 280 | conn._onRequestStateChange(spy, req); 281 | 282 | equal(conn._requests.length, 1, "_requests should be same length"); 283 | equal(spy.called, false, "callback should not be called"); 284 | }); 285 | }); 286 | -------------------------------------------------------------------------------- /tests/testsuite.css: -------------------------------------------------------------------------------- 1 | body, div, h1 { font-family: 'trebuchet ms', verdana, arial; margin: 0; padding: 0 } 2 | body {font-size: 10pt; } 3 | h1 { padding: 15px; font-size: large; background-color: #06b; color: white; } 4 | h1 a { color: white; } 5 | h2 { padding: 10px; background-color: #eee; color: black; margin: 0; font-size: small; font-weight: normal } 6 | 7 | .pass { color: green; } 8 | .fail { color: red; } 9 | p.result { margin-left: 1em; } 10 | 11 | #banner { height: 2em; border-bottom: 1px solid white; } 12 | h2.pass { background-color: green; } 13 | h2.fail { background-color: red; } 14 | 15 | div.testrunner-toolbar { background: #eee; border-top: 1px solid black; padding: 10px; } 16 | 17 | ol#tests > li > strong { cursor:pointer; } 18 | 19 | div#fx-tests h4 { 20 | background: red; 21 | } 22 | 23 | div#fx-tests h4.pass { 24 | background: green; 25 | } 26 | 27 | div#fx-tests div.box { 28 | background: red url(data/cow.jpg) no-repeat; 29 | overflow: hidden; 30 | border: 2px solid #000; 31 | } 32 | 33 | div#fx-tests div.overflow { 34 | overflow: visible; 35 | } 36 | 37 | div.inline { 38 | display: inline; 39 | } 40 | 41 | div.autoheight { 42 | height: auto; 43 | } 44 | 45 | div.autowidth { 46 | width: auto; 47 | } 48 | 49 | div.autoopacity { 50 | opacity: auto; 51 | } 52 | 53 | div.largewidth { 54 | width: 100px; 55 | } 56 | 57 | div.largeheight { 58 | height: 100px; 59 | } 60 | 61 | div.largeopacity { 62 | filter: progid:DXImageTransform.Microsoft.Alpha(opacity=100); 63 | } 64 | 65 | div.medwidth { 66 | width: 50px; 67 | } 68 | 69 | div.medheight { 70 | height: 50px; 71 | } 72 | 73 | div.medopacity { 74 | opacity: 0.5; 75 | filter: progid:DXImageTransform.Microsoft.Alpha(opacity=50); 76 | } 77 | 78 | div.nowidth { 79 | width: 0px; 80 | } 81 | 82 | div.noheight { 83 | height: 0px; 84 | } 85 | 86 | div.noopacity { 87 | opacity: 0; 88 | filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0); 89 | } 90 | 91 | div.hidden { 92 | display: none; 93 | } 94 | 95 | div#fx-tests div.widewidth { 96 | background-repeat: repeat-x; 97 | } 98 | 99 | div#fx-tests div.wideheight { 100 | background-repeat: repeat-y; 101 | } 102 | 103 | div#fx-tests div.widewidth.wideheight { 104 | background-repeat: repeat; 105 | } 106 | 107 | div#fx-tests div.noback { 108 | background-image: none; 109 | } 110 | 111 | div.chain, div.chain div { width: 100px; height: 20px; position: relative; float: left; } 112 | div.chain div { position: absolute; top: 0px; left: 0px; } 113 | 114 | div.chain.test { background: red; } 115 | div.chain.test div { background: green; } 116 | 117 | div.chain.out { background: green; } 118 | div.chain.out div { background: red; display: none; } 119 | 120 | div#show-tests * { display: none; } 121 | --------------------------------------------------------------------------------