├── .gitignore ├── README.rst ├── docs ├── .gitignore ├── Makefile ├── conf.py ├── index.rst └── make.bat ├── example_chat ├── __init__.py ├── chat │ ├── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── manage.py ├── settings.py ├── templates │ └── index.html └── urls.py ├── realtime ├── __init__.py ├── events.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── rungevent.py ├── signals.py ├── static │ └── js │ │ └── external │ │ └── socket.io-0.9.6.js ├── templatetags │ ├── __init__.py │ └── socketio.py ├── urls.py ├── util.py └── views.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.mo 2 | 3 | *.pyc 4 | 5 | *.swp 6 | 7 | TAGS 8 | tags 9 | 10 | *.orig 11 | 12 | .idea 13 | .project 14 | .pydevproject 15 | .settings 16 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Warning 2 | ======= 3 | **This documentation is out of date and the project is not maintained or supported, it very likely 4 | doesn't work at all and you'll probably be more lucky having a look at something else (I recommend 5 | websocket server not tied to Django).** 6 | 7 | Notice 8 | ====== 9 | 10 | Beware! This project is highly experimental. It is my personal project which may result in lack 11 | of documentation, lack of my attention due to busy time etc. 12 | 13 | Due to its early stage, it's API is subject to change (not drastically, but still). 14 | 15 | Please feel free to create an issue if you feel that something should be improved regarding code 16 | or documentation. I also encourage you to hack the source code, it is pretty short and can provide 17 | better understanding of what's going on. 18 | 19 | Installation 20 | ============ 21 | 22 | Requirements 23 | ------------ 24 | 25 | * gevent-socketio from `my gevent-socketio repository `_ 26 | (commit ``8835a91dffba4447564ffa30df95663a13e1997e``) and its dependencies 27 | * pip 28 | * django >= 1.3 29 | * git ;) 30 | 31 | In Red Hat/CentOS/Fedora they can be obtained by following commands:: 32 | 33 | sudo yum install libevent-devel python-devel python-setuptools gcc git-core 34 | sudo easy_install pip 35 | 36 | In Debian/Ubuntu:: 37 | 38 | sudo apt-get install libevent-dev python-dev python-setuptools gcc git-core 39 | sudo easy_install pip 40 | 41 | You also want to have Socket.IO client, such client in version 0.9.6 is provided by this project. 42 | 43 | To install dependencies to use chat example please execute:: 44 | 45 | pip install -r requiements.txt 46 | 47 | 48 | Installation itself 49 | ------------------- 50 | 51 | Django-realtime is distributed as setuptools package. To install it with pip execute:: 52 | 53 | pip install git+https://jstasiak@github.com/jstasiak/django-realtime.git 54 | 55 | 56 | Introduction 57 | ============ 58 | This application allow you to use Socket.IO based communication within your Django project. 59 | It uses fairly recent version of Socket.IO - currently 0.9.6. 60 | What I like in Socket.IO newer than 0.6 is that it has additional communication 61 | channels - events. Every event can be acknowledged with optional return data which is really 62 | nice feature. 63 | 64 | It also uses gevent-socketio development version patched by me. Without gevent-socketio this application wouldn't exist. 65 | 66 | Some features of django-realtime are inspired by django-socketio project 67 | (https://github.com/stephenmcd/django-socketio). I like django-socketio, but: 68 | 69 | * it provides channels (pretty much something like Socket.IO events) by modifying client 70 | code, I wanted to avoid that 71 | * supports only Socket.IO 0.6, so there is no events and no akcnowledgements (as far as I know) 72 | * has its own event system. django-realtime had such custom system in the past, but I have 73 | decided to rewrite it to use Django signals and I am happy with that, also it is interface 74 | which people are familiar with 75 | * I started this project before I discovered django-socketio ;) 76 | 77 | Django-realtime Python package name is simply ``realtime``. 78 | 79 | Configuration of your project 80 | ----------------------------- 81 | 82 | Backend 83 | +++++++ 84 | 85 | Next step is to configure Django project to use ``realtime`` app. To achieve this, you have to: 86 | 87 | * add ``realtime`` application to Django ``INSTALLED_APPS`` setting, for example:: 88 | 89 | INSTALLED_APPS += ['realtime'] 90 | 91 | * configure URL dispatcher - you have to put this code in your main ``urls.py`` file:: 92 | 93 | urlpatterns += patterns('', url(r'^socket.io/', include('realtime.urls')) ) 94 | 95 | Frontend 96 | ++++++++ 97 | 98 | If you want to use Socket.IO client provided by django-realtime, put following code in ``HEAD`` section of your HTML template file:: 99 | 100 | {% load socketio %} 101 | {% socketio_client_script %} 102 | 103 | Socket.IO client provided by this project is not modified and is provided purely for your convenience. 104 | 105 | Then you probably would write some code actually connecting to server and making use of 106 | Socket.IO client. This is beyond this projects scope, although I present here template 107 | I always take and customize:: 108 | 109 | socket = io.connect(null, { transports: ['flashsocket', 'xhr-polling'] }); 110 | socket.on('connect', function() { 111 | console.log('connected'); 112 | }); 113 | 114 | socket.on('disconnect', function() { 115 | console.log('disconnected'); 116 | }); 117 | 118 | socket.on('reconnecting', function() { 119 | console.log('reconnecting'); 120 | }); 121 | 122 | socket.on('reconnect', function() { 123 | console.log('reconnected'); 124 | }); 125 | 126 | socket.on('error', function(e) { 127 | console.log('error: ' + e); 128 | }); 129 | 130 | socket.on('message', function(message) { 131 | console.log('received:'); 132 | console.dir(message); 133 | }); 134 | 135 | **Warning!** In current development version (it is still correct at 2011-11-16) of 136 | gevent-socketio websocket transport is not working, so to avoid errors please restrict 137 | client transport list so that websocket is not there (like in the example above). 138 | 139 | 140 | Running server 141 | -------------- 142 | 143 | Due to high number of possible concurrent and long running connections you cannot use traditional 144 | server like Apache + mod_wsgi to host project using django-realtime. I use gevents pywsgi server. 145 | 146 | You can run this server by executing the following command within your project root directory:: 147 | 148 | python manage.py rungevent [interface:port] 149 | 150 | Interface and port part is optional, it defaults to localhost and 8000. 151 | 152 | If you want to be able to connect to the server from remote hosts, enter ``0`` as interface, like 153 | this:: 154 | 155 | python manage.py rungevent 0:8000 156 | 157 | API 158 | === 159 | 160 | Current connections 161 | ------------------- 162 | 163 | In the top-level of realtime package there is ``connected_sockets`` sequence which contains, 164 | what a surprise, currently connected sockets. These sockets are `gevent-socketio`_ SocketIOProtocol instances. 165 | 166 | Usage 167 | +++++ 168 | 169 | You can for example iterate over it and list connected session ids:: 170 | 171 | from realtime import connected_sockets 172 | 173 | print('Connected sockets:') 174 | for socket in connected_sockets: 175 | print('- {0}'.format(socket.session.session_id)) 176 | 177 | When you have reference to connected ``socket`` (obtained from ``realtime.connected_sockets``, 178 | from signal handler parameter ``sender`` or by other means), you can use following methods:: 179 | 180 | # sends string 'Hallelujah!' by this particular socket to this particular client 181 | # signature: socket.send(STRING) 182 | socket.send('Hallelujah!') 183 | 184 | # emits event named 'notice' with arguments 1, 2 and '!!!' 185 | # signature: socket.emit(EVENT_NAME, *args) 186 | socket.emit('notice', 1, 2, '!!!') 187 | 188 | # these are just like socket.send and socket.emit, but send message/event to all 189 | # clients but this one 190 | socket.broadcast_send('Hey! New user connected!') 191 | socket.broadcast_emit('notice', 'Server is shutting down', 'kaboom') 192 | 193 | In current implementation of ``gevent-socketio``, if message passed to ``socket.send`` is not 194 | basestring instance, it will be converted to its string representation. There is no JSON 195 | encoding here. 196 | 197 | On the other hand, arguments supplied to ``socket.emit``, ``broadcast_emit`` and ``socket.ack`` are 198 | JSON encoded. 199 | 200 | 201 | 202 | Events 203 | ------ 204 | 205 | Handling input from sockets is based on `Django signals `_. 206 | In module ``realtime.signals`` we have: 207 | 208 | * ``socket_connected`` - when client connects 209 | * ``socket_disconnected`` - when client disconnects 210 | * ``socket_client_message`` - when you do ``socket.send('some data')`` in the client 211 | * ``socket_client_event`` - fires when you do ``socket.emi('event_name', ...)`` in the client 212 | * ``socket_client_event_by_type`` - dictionary which is indexed by client event name and returns associated signal 213 | 214 | In module ``realtime.events`` there is ``Event`` class defined. Its public interface visible for listeners is as follows: 215 | 216 | * ``ack(*params)`` - functions which confirms receiving event and can be passed some data to send to client in confirmation 217 | * ``data`` - event data 218 | * ``name`` - name of the event 219 | * ``acknowledgeable()`` - true if this event can be acknowledged 220 | * ``acknowledged()`` - true if this event has been acknowledged already 221 | 222 | Usage 223 | +++++ 224 | 225 | :: 226 | 227 | from django.dispatch import receiver 228 | from realtime.signals import socket_connected, socket_disconnected, socket_client_message, socket_client_event 229 | @receiver(socket_connected) 230 | def handle_connected(sender, request, **kwargs): 231 | socket = sender 232 | print('{0} connected'.format(socket.session.session_id)) 233 | 234 | @receiver(socket_disconnected) 235 | def handle_disconnected(sender, request, **kwargs): 236 | socket = sender 237 | print('{0} disconnected'.format(socket.session.session_id)) 238 | 239 | @receiver(socket_client_message) 240 | def handle_message(sender, request, message, **kwargs): 241 | socket = sender 242 | print('{0} => message {1!r}'.format(socket.session.session_id, message)) 243 | 244 | 245 | @receiver(socket_client_event) 246 | def handle_event(sender, request, event, **kwargs): 247 | socket = sender 248 | print('{0} => event {1!r} ({2!r})'.format(socket.session.session_id, event.name, event.data)) 249 | 250 | if event.acknowledgeable: 251 | event.ack('I have received your message!') 252 | 253 | Example 254 | ======= 255 | 256 | In project root you can find ``example_chat`` directory. It contains very simple live chat 257 | implementation which uses django-realtime. 258 | 259 | I warn you, it is just proof of concept and do not expect it to work flawlessly. 260 | 261 | 262 | License 263 | ======= 264 | 265 | This project code is licensed under BSD license unless stated otherwise. Take it and use it. 266 | 267 | This repository also contains ``Socket.IO`` client which has its own license. 268 | 269 | .. _gevent-socketio: https://bitbucket.org/Jeffrey/gevent-socketio 270 | .. _socket.io: http://socket.io/ 271 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-realtime.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-realtime.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-realtime" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-realtime" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django-realtime documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Sep 22 18:42:59 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | 17 | # Hack which enables us to use autodoc without having fake project files 18 | # Source: http://www.meiocodigo.com/2009/03/18/documenting-django-pluggable-apps-with-sphinx/ 19 | from django.conf import settings 20 | settings.configure() 21 | 22 | 23 | 24 | # If extensions (or modules to document with autodoc) are in another directory, 25 | # add these directories to sys.path here. If the directory is relative to the 26 | # documentation root, use os.path.abspath to make it absolute, like shown here. 27 | #sys.path.insert(0, os.path.abspath('.')) 28 | 29 | # -- General configuration ----------------------------------------------------- 30 | 31 | # If your documentation needs a minimal Sphinx version, state it here. 32 | #needs_sphinx = '1.0' 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be extensions 35 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 36 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.jsmath'] 37 | jsmath_path = 'a' 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The suffix of source filenames. 43 | source_suffix = '.rst' 44 | 45 | # The encoding of source files. 46 | #source_encoding = 'utf-8-sig' 47 | 48 | # The master toctree document. 49 | master_doc = 'index' 50 | 51 | # General information about the project. 52 | project = u'django-realtime' 53 | copyright = u'2011, Jakub Stasiak' 54 | 55 | # The version info for the project you're documenting, acts as replacement for 56 | # |version| and |release|, also used in various other places throughout the 57 | # built documents. 58 | # 59 | # The short X.Y version. 60 | version = '0.1' 61 | # The full version, including alpha/beta/rc tags. 62 | release = '0.1.1' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | #language = None 67 | 68 | # There are two options for replacing |today|: either, you set today to some 69 | # non-false value, then it is used: 70 | #today = '' 71 | # Else, today_fmt is used as the format for a strftime call. 72 | #today_fmt = '%B %d, %Y' 73 | 74 | # List of patterns, relative to source directory, that match files and 75 | # directories to ignore when looking for source files. 76 | exclude_patterns = ['_build'] 77 | 78 | # The reST default role (used for this markup: `text`) to use for all documents. 79 | #default_role = None 80 | 81 | # If true, '()' will be appended to :func: etc. cross-reference text. 82 | #add_function_parentheses = True 83 | 84 | # If true, the current module name will be prepended to all description 85 | # unit titles (such as .. function::). 86 | #add_module_names = True 87 | 88 | # If true, sectionauthor and moduleauthor directives will be shown in the 89 | # output. They are ignored by default. 90 | #show_authors = False 91 | 92 | # The name of the Pygments (syntax highlighting) style to use. 93 | pygments_style = 'sphinx' 94 | 95 | # A list of ignored prefixes for module index sorting. 96 | #modindex_common_prefix = [] 97 | 98 | 99 | # -- Options for HTML output --------------------------------------------------- 100 | 101 | # The theme to use for HTML and HTML Help pages. See the documentation for 102 | # a list of builtin themes. 103 | html_theme = 'default' 104 | 105 | # Theme options are theme-specific and customize the look and feel of a theme 106 | # further. For a list of options available for each theme, see the 107 | # documentation. 108 | #html_theme_options = {} 109 | 110 | # Add any paths that contain custom themes here, relative to this directory. 111 | #html_theme_path = [] 112 | 113 | # The name for this set of Sphinx documents. If None, it defaults to 114 | # " v documentation". 115 | #html_title = None 116 | 117 | # A shorter title for the navigation bar. Default is the same as html_title. 118 | #html_short_title = None 119 | 120 | # The name of an image file (relative to this directory) to place at the top 121 | # of the sidebar. 122 | #html_logo = None 123 | 124 | # The name of an image file (within the static path) to use as favicon of the 125 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 126 | # pixels large. 127 | #html_favicon = None 128 | 129 | # Add any paths that contain custom static files (such as style sheets) here, 130 | # relative to this directory. They are copied after the builtin static files, 131 | # so a file named "default.css" will overwrite the builtin "default.css". 132 | html_static_path = ['_static'] 133 | 134 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 135 | # using the given strftime format. 136 | #html_last_updated_fmt = '%b %d, %Y' 137 | 138 | # If true, SmartyPants will be used to convert quotes and dashes to 139 | # typographically correct entities. 140 | #html_use_smartypants = True 141 | 142 | # Custom sidebar templates, maps document names to template names. 143 | #html_sidebars = {} 144 | 145 | # Additional templates that should be rendered to pages, maps page names to 146 | # template names. 147 | #html_additional_pages = {} 148 | 149 | # If false, no module index is generated. 150 | #html_domain_indices = True 151 | 152 | # If false, no index is generated. 153 | #html_use_index = True 154 | 155 | # If true, the index is split into individual pages for each letter. 156 | #html_split_index = False 157 | 158 | # If true, links to the reST sources are added to the pages. 159 | #html_show_sourcelink = True 160 | 161 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 162 | #html_show_sphinx = True 163 | 164 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 165 | #html_show_copyright = True 166 | 167 | # If true, an OpenSearch description file will be output, and all pages will 168 | # contain a tag referring to it. The value of this option must be the 169 | # base URL from which the finished HTML is served. 170 | #html_use_opensearch = '' 171 | 172 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 173 | #html_file_suffix = None 174 | 175 | # Output file base name for HTML help builder. 176 | htmlhelp_basename = 'django-realtimedoc' 177 | 178 | 179 | # -- Options for LaTeX output -------------------------------------------------- 180 | 181 | # The paper size ('letter' or 'a4'). 182 | #latex_paper_size = 'letter' 183 | 184 | # The font size ('10pt', '11pt' or '12pt'). 185 | #latex_font_size = '10pt' 186 | 187 | # Grouping the document tree into LaTeX files. List of tuples 188 | # (source start file, target name, title, author, documentclass [howto/manual]). 189 | latex_documents = [ 190 | ('index', 'django-realtime.tex', u'django-realtime Documentation', 191 | u'Jakub Stasiak', 'manual'), 192 | ] 193 | 194 | # The name of an image file (relative to this directory) to place at the top of 195 | # the title page. 196 | #latex_logo = None 197 | 198 | # For "manual" documents, if this is true, then toplevel headings are parts, 199 | # not chapters. 200 | #latex_use_parts = False 201 | 202 | # If true, show page references after internal links. 203 | #latex_show_pagerefs = False 204 | 205 | # If true, show URL addresses after external links. 206 | #latex_show_urls = False 207 | 208 | # Additional stuff for the LaTeX preamble. 209 | #latex_preamble = '' 210 | 211 | # Documents to append as an appendix to all manuals. 212 | #latex_appendices = [] 213 | 214 | # If false, no module index is generated. 215 | #latex_domain_indices = True 216 | 217 | 218 | # -- Options for manual page output -------------------------------------------- 219 | 220 | # One entry per manual page. List of tuples 221 | # (source start file, name, description, authors, manual section). 222 | man_pages = [ 223 | ('index', 'django-realtime', u'django-realtime Documentation', 224 | [u'Jakub Stasiak'], 1) 225 | ] 226 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. django-realtime documentation master file, created by 2 | sphinx-quickstart on Thu Sep 22 18:42:59 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to django-realtime's documentation! 7 | =========================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | Indices and tables 15 | ================== 16 | 17 | * :ref:`genindex` 18 | * :ref:`modindex` 19 | * :ref:`search` 20 | 21 | 22 | .. automodule:: realtime.events 23 | :members: 24 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | if NOT "%PAPER%" == "" ( 11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 12 | ) 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "help" ( 17 | :help 18 | echo.Please use `make ^` where ^ is one of 19 | echo. html to make standalone HTML files 20 | echo. dirhtml to make HTML files named index.html in directories 21 | echo. singlehtml to make a single large HTML file 22 | echo. pickle to make pickle files 23 | echo. json to make JSON files 24 | echo. htmlhelp to make HTML files and a HTML help project 25 | echo. qthelp to make HTML files and a qthelp project 26 | echo. devhelp to make HTML files and a Devhelp project 27 | echo. epub to make an epub 28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 29 | echo. text to make text files 30 | echo. man to make manual pages 31 | echo. changes to make an overview over all changed/added/deprecated items 32 | echo. linkcheck to check all external links for integrity 33 | echo. doctest to run all doctests embedded in the documentation if enabled 34 | goto end 35 | ) 36 | 37 | if "%1" == "clean" ( 38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 39 | del /q /s %BUILDDIR%\* 40 | goto end 41 | ) 42 | 43 | if "%1" == "html" ( 44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 45 | if errorlevel 1 exit /b 1 46 | echo. 47 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 48 | goto end 49 | ) 50 | 51 | if "%1" == "dirhtml" ( 52 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 53 | if errorlevel 1 exit /b 1 54 | echo. 55 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 56 | goto end 57 | ) 58 | 59 | if "%1" == "singlehtml" ( 60 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 61 | if errorlevel 1 exit /b 1 62 | echo. 63 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 64 | goto end 65 | ) 66 | 67 | if "%1" == "pickle" ( 68 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 69 | if errorlevel 1 exit /b 1 70 | echo. 71 | echo.Build finished; now you can process the pickle files. 72 | goto end 73 | ) 74 | 75 | if "%1" == "json" ( 76 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished; now you can process the JSON files. 80 | goto end 81 | ) 82 | 83 | if "%1" == "htmlhelp" ( 84 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished; now you can run HTML Help Workshop with the ^ 88 | .hhp project file in %BUILDDIR%/htmlhelp. 89 | goto end 90 | ) 91 | 92 | if "%1" == "qthelp" ( 93 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 94 | if errorlevel 1 exit /b 1 95 | echo. 96 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 97 | .qhcp project file in %BUILDDIR%/qthelp, like this: 98 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-realtime.qhcp 99 | echo.To view the help file: 100 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-realtime.ghc 101 | goto end 102 | ) 103 | 104 | if "%1" == "devhelp" ( 105 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 106 | if errorlevel 1 exit /b 1 107 | echo. 108 | echo.Build finished. 109 | goto end 110 | ) 111 | 112 | if "%1" == "epub" ( 113 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 117 | goto end 118 | ) 119 | 120 | if "%1" == "latex" ( 121 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 122 | if errorlevel 1 exit /b 1 123 | echo. 124 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 125 | goto end 126 | ) 127 | 128 | if "%1" == "text" ( 129 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 130 | if errorlevel 1 exit /b 1 131 | echo. 132 | echo.Build finished. The text files are in %BUILDDIR%/text. 133 | goto end 134 | ) 135 | 136 | if "%1" == "man" ( 137 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 141 | goto end 142 | ) 143 | 144 | if "%1" == "changes" ( 145 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.The overview file is in %BUILDDIR%/changes. 149 | goto end 150 | ) 151 | 152 | if "%1" == "linkcheck" ( 153 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Link check complete; look for any errors in the above output ^ 157 | or in %BUILDDIR%/linkcheck/output.txt. 158 | goto end 159 | ) 160 | 161 | if "%1" == "doctest" ( 162 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 163 | if errorlevel 1 exit /b 1 164 | echo. 165 | echo.Testing of doctests in the sources finished, look at the ^ 166 | results in %BUILDDIR%/doctest/output.txt. 167 | goto end 168 | ) 169 | 170 | :end 171 | -------------------------------------------------------------------------------- /example_chat/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstasiak/django-realtime/6c942149cb202270156878c3a59aa474cae57acf/example_chat/__init__.py -------------------------------------------------------------------------------- /example_chat/chat/__init__.py: -------------------------------------------------------------------------------- 1 | from django.dispatch import receiver 2 | from realtime import connected_sockets 3 | from realtime.signals import socket_connected, socket_disconnected, \ 4 | socket_client_event, socket_client_message, socket_client_event_by_type 5 | from realtime.util import failure, success 6 | 7 | @receiver(socket_client_event_by_type['echo']) 8 | def handle_echo(sender, request, event, **kwargs): 9 | return event.args 10 | 11 | @receiver(socket_connected) 12 | def handle_connected(sender, request, **kwargs): 13 | socket = sender 14 | session = socket.session 15 | del session['user_name'] 16 | 17 | @receiver(socket_disconnected) 18 | def handle_disconnected(sender, request, **kwargs): 19 | socket = sender 20 | session = socket.session 21 | namespace = kwargs['namespace'] 22 | if 'user_name' in session: 23 | namespace.broadcast_event_not_me('system_message', '{0} has left'.format(session['user_name'])) 24 | 25 | @receiver(socket_client_event_by_type['chat_set_name']) 26 | def handle_set_name(sender, request, event, **kwargs): 27 | socket = sender 28 | user_name = event.args[0] 29 | namespace = kwargs['namespace'] 30 | 31 | session = socket.session 32 | if 'user_name' in session: 33 | namespace.broadcast_event('system_message', '{0} changed his name to {1}'.format( 34 | session['user_name'], user_name)) 35 | else: 36 | namespace.broadcast_event('system_message', '{0} has joined'.format(user_name)) 37 | 38 | session['user_name'] = user_name 39 | return success() 40 | 41 | 42 | @receiver(socket_client_event_by_type['chat_message']) 43 | def handle_chat_message(sender, request, event, **kwargs): 44 | socket = sender 45 | session = socket.session 46 | 47 | message = event.args[0] 48 | namespace = kwargs['namespace'] 49 | 50 | if 'user_name' not in session: 51 | result = failure() 52 | else: 53 | namespace.broadcast_event('chat_message', session['user_name'], message) 54 | result = success() 55 | 56 | return result 57 | -------------------------------------------------------------------------------- /example_chat/chat/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /example_chat/chat/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /example_chat/chat/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render_to_response 2 | 3 | def index(request): 4 | return render_to_response('index.html') 5 | 6 | 7 | -------------------------------------------------------------------------------- /example_chat/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | import imp 4 | try: 5 | imp.find_module('settings') # Assumed to be in the same directory. 6 | except ImportError: 7 | import sys 8 | 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" % __file__) 9 | sys.exit(1) 10 | 11 | import settings 12 | 13 | if __name__ == "__main__": 14 | execute_manager(settings) 15 | -------------------------------------------------------------------------------- /example_chat/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for example_chat project. 2 | 3 | from os.path import abspath, dirname, join 4 | PROJECT_ROOT = abspath(dirname(__file__)) 5 | 6 | DEBUG = True 7 | TEMPLATE_DEBUG = DEBUG 8 | 9 | ADMINS = ( 10 | # ('Your Name', 'your_email@example.com'), 11 | ) 12 | 13 | MANAGERS = ADMINS 14 | 15 | DATABASES = { 16 | 'default': { 17 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 18 | 'NAME': 'db.sqlite3', # Or path to database file if using sqlite3. 19 | 'USER': '', # Not used with sqlite3. 20 | 'PASSWORD': '', # Not used with sqlite3. 21 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 22 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 23 | } 24 | } 25 | 26 | # Local time zone for this installation. Choices can be found here: 27 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 28 | # although not all choices may be available on all operating systems. 29 | # On Unix systems, a value of None will cause Django to use the same 30 | # timezone as the operating system. 31 | # If running in a Windows environment this must be set to the same as your 32 | # system time zone. 33 | TIME_ZONE = 'America/Chicago' 34 | 35 | # Language code for this installation. All choices can be found here: 36 | # http://www.i18nguy.com/unicode/language-identifiers.html 37 | LANGUAGE_CODE = 'en-us' 38 | 39 | SITE_ID = 1 40 | 41 | # If you set this to False, Django will make some optimizations so as not 42 | # to load the internationalization machinery. 43 | USE_I18N = True 44 | 45 | # If you set this to False, Django will not format dates, numbers and 46 | # calendars according to the current locale 47 | USE_L10N = True 48 | 49 | # Absolute filesystem path to the directory that will hold user-uploaded files. 50 | # Example: "/home/media/media.lawrence.com/media/" 51 | MEDIA_ROOT = '' 52 | 53 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 54 | # trailing slash. 55 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 56 | MEDIA_URL = '' 57 | 58 | # Absolute path to the directory static files should be collected to. 59 | # Don't put anything in this directory yourself; store your static files 60 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 61 | # Example: "/home/media/media.lawrence.com/static/" 62 | STATIC_ROOT = join(PROJECT_ROOT, 'static') 63 | 64 | # URL prefix for static files. 65 | # Example: "http://media.lawrence.com/static/" 66 | STATIC_URL = '/s/' 67 | 68 | # URL prefix for admin static files -- CSS, JavaScript and images. 69 | # Make sure to use a trailing slash. 70 | # Examples: "http://foo.com/static/admin/", "/static/admin/". 71 | ADMIN_MEDIA_PREFIX = STATIC_URL + 'admin/' 72 | 73 | # Additional locations of static files 74 | STATICFILES_DIRS = ( 75 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 76 | # Always use forward slashes, even on Windows. 77 | # Don't forget to use absolute paths, not relative paths. 78 | ) 79 | 80 | # List of finder classes that know how to find static files in 81 | # various locations. 82 | STATICFILES_FINDERS = ( 83 | 'django.contrib.staticfiles.finders.FileSystemFinder', 84 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 85 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 86 | ) 87 | 88 | # Make this unique, and don't share it with anybody. 89 | SECRET_KEY = 'u28f08YN)*F#)*F30823hf0h0gs8HGpw,j023.f.9-eg,23-293md-91,je-9,jf.23-f.g-9rg9ssm-f9wefwr' 90 | 91 | # List of callables that know how to import templates from various sources. 92 | TEMPLATE_LOADERS = ( 93 | 'django.template.loaders.filesystem.Loader', 94 | 'django.template.loaders.app_directories.Loader', 95 | # 'django.template.loaders.eggs.Loader', 96 | ) 97 | 98 | MIDDLEWARE_CLASSES = ( 99 | 'django.middleware.common.CommonMiddleware', 100 | 'django.contrib.sessions.middleware.SessionMiddleware', 101 | 'django.middleware.csrf.CsrfViewMiddleware', 102 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 103 | 'django.contrib.messages.middleware.MessageMiddleware', 104 | ) 105 | 106 | ROOT_URLCONF = 'urls' 107 | 108 | TEMPLATE_DIRS = ( 109 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 110 | # Always use forward slashes, even on Windows. 111 | # Don't forget to use absolute paths, not relative paths. 112 | join(PROJECT_ROOT, 'templates'), 113 | ) 114 | 115 | INSTALLED_APPS = ( 116 | 'django.contrib.auth', 117 | 'django.contrib.contenttypes', 118 | 'django.contrib.sessions', 119 | 'django.contrib.sites', 120 | 'django.contrib.messages', 121 | 'django.contrib.staticfiles', 122 | # Uncomment the next line to enable the admin: 123 | # 'django.contrib.admin', 124 | # Uncomment the next line to enable admin documentation: 125 | # 'django.contrib.admindocs', 126 | 127 | 'chat', 128 | 'realtime', 129 | ) 130 | 131 | # A sample logging configuration. The only tangible logging 132 | # performed by this configuration is to send an email to 133 | # the site admins on every HTTP 500 error. 134 | # See http://docs.djangoproject.com/en/dev/topics/logging for 135 | # more details on how to customize your logging configuration. 136 | LOGGING = { 137 | 'version': 1, 138 | 'disable_existing_loggers': False, 139 | 'handlers': { 140 | 'mail_admins': { 141 | 'level': 'ERROR', 142 | 'class': 'django.utils.log.AdminEmailHandler' 143 | } 144 | }, 145 | 'loggers': { 146 | 'django.request': { 147 | 'handlers': ['mail_admins'], 148 | 'level': 'ERROR', 149 | 'propagate': True, 150 | }, 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /example_chat/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Django, Socket.IO and gevent chat example 7 | 8 | 9 | 10 | {% load socketio %} 11 | {% socketio_client_script %} 12 | 13 | 97 | 155 | 156 | 157 | 158 | 159 |
160 |
161 |
    162 |
    163 |
    164 |
    165 |
    166 |
    167 | 168 | 169 |
    170 |
    171 |
    172 |
    173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /example_chat/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, include, url 2 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 3 | 4 | # Uncomment the next two lines to enable the admin: 5 | # from django.contrib import admin 6 | # admin.autodiscover() 7 | 8 | urlpatterns = patterns('', 9 | # Examples: 10 | # url(r'^$', 'example_chat.views.home', name='home'), 11 | # url(r'^example_chat/', include('example_chat.foo.urls')), 12 | 13 | # Uncomment the admin/doc line below to enable admin documentation: 14 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 15 | 16 | # Uncomment the next line to enable the admin: 17 | # url(r'^admin/', include(admin.site.urls)), 18 | url(r'^$', 'chat.views.index'), 19 | ) 20 | 21 | urlpatterns += staticfiles_urlpatterns() 22 | urlpatterns += patterns('', url(r'^socket.io/', include('realtime.urls'))) 23 | -------------------------------------------------------------------------------- /realtime/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.1' 2 | 3 | from django.dispatch import receiver 4 | 5 | from .signals import socket_connected, socket_disconnected 6 | 7 | connected_sockets = [] 8 | 9 | @receiver(socket_connected, weak = False) 10 | def handle_conneceted(sender, request, **kwargs): 11 | socket = sender 12 | connected_sockets.append(socket) 13 | 14 | 15 | @receiver(socket_disconnected, weak = False) 16 | def handle_disconnected(sender, request, **kwargs): 17 | socket = sender 18 | connected_sockets.remove(socket) 19 | 20 | -------------------------------------------------------------------------------- /realtime/events.py: -------------------------------------------------------------------------------- 1 | class Event(object): 2 | ''' 3 | Represents socket event received from client. 4 | ''' 5 | 6 | def __init__(self, socket, name, namespace = None, args = None, id = None): 7 | self._socket = socket 8 | self.name = name 9 | self.args = args 10 | self._namespace = namespace 11 | self._event_id = id 12 | -------------------------------------------------------------------------------- /realtime/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstasiak/django-realtime/6c942149cb202270156878c3a59aa474cae57acf/realtime/management/__init__.py -------------------------------------------------------------------------------- /realtime/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /realtime/management/commands/rungevent.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from gevent import monkey 4 | monkey.patch_all() 5 | 6 | from traceback import print_exc 7 | 8 | from django.core.management.base import BaseCommand, CommandError 9 | from django.core.handlers.wsgi import WSGIHandler 10 | from django.core.signals import got_request_exception 11 | from django.utils import autoreload 12 | 13 | from socketio.server import SocketIOServer 14 | 15 | def exception_printer(sender, **kwargs): 16 | print_exc() 17 | 18 | class Command(BaseCommand): 19 | args = '[interface:port]' 20 | help = 'runs gevent-socketio SocketIOServer' 21 | 22 | def handle(self, *args, **options): 23 | if len(args) == 1: 24 | host, port = args[0].split(':') 25 | port = int(port) 26 | else: 27 | host, port = 'localhost', 8000 28 | 29 | autoreload.main(self.main, (host,port)) 30 | 31 | def main(self, host, port): 32 | got_request_exception.connect(exception_printer) 33 | 34 | self.stdout.write('Serving at {host}:{port}\n'.format(host = host, port = port)) 35 | application = WSGIHandler() 36 | server = SocketIOServer((host, port), application, namespace = 'socket.io', policy_server = False) 37 | server.serve_forever() 38 | 39 | 40 | -------------------------------------------------------------------------------- /realtime/signals.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | from django.dispatch import Signal 4 | 5 | socket_connected = Signal(providing_args = ['request']) 6 | socket_disconnected = Signal(providing_args = ['request']) 7 | socket_client_message = Signal(providing_args = ['request', 'message']) 8 | socket_client_event = Signal(providing_args = ['request', 'event']) 9 | 10 | socket_client_event_by_type = defaultdict(lambda: Signal(providing_args = ['request', 'event'])) 11 | -------------------------------------------------------------------------------- /realtime/static/js/external/socket.io-0.9.6.js: -------------------------------------------------------------------------------- 1 | /*! Socket.IO.js build:0.9.6, development. Copyright(c) 2011 LearnBoost MIT Licensed */ 2 | 3 | /** 4 | * socket.io 5 | * Copyright(c) 2011 LearnBoost 6 | * MIT Licensed 7 | */ 8 | 9 | (function (exports, global) { 10 | 11 | /** 12 | * IO namespace. 13 | * 14 | * @namespace 15 | */ 16 | 17 | var io = exports; 18 | 19 | /** 20 | * Socket.IO version 21 | * 22 | * @api public 23 | */ 24 | 25 | io.version = '0.9.6'; 26 | 27 | /** 28 | * Protocol implemented. 29 | * 30 | * @api public 31 | */ 32 | 33 | io.protocol = 1; 34 | 35 | /** 36 | * Available transports, these will be populated with the available transports 37 | * 38 | * @api public 39 | */ 40 | 41 | io.transports = []; 42 | 43 | /** 44 | * Keep track of jsonp callbacks. 45 | * 46 | * @api private 47 | */ 48 | 49 | io.j = []; 50 | 51 | /** 52 | * Keep track of our io.Sockets 53 | * 54 | * @api private 55 | */ 56 | io.sockets = {}; 57 | 58 | 59 | /** 60 | * Manages connections to hosts. 61 | * 62 | * @param {String} uri 63 | * @Param {Boolean} force creation of new socket (defaults to false) 64 | * @api public 65 | */ 66 | 67 | io.connect = function (host, details) { 68 | var uri = io.util.parseUri(host) 69 | , uuri 70 | , socket; 71 | 72 | if (global && global.location) { 73 | uri.protocol = uri.protocol || global.location.protocol.slice(0, -1); 74 | uri.host = uri.host || (global.document 75 | ? global.document.domain : global.location.hostname); 76 | uri.port = uri.port || global.location.port; 77 | } 78 | 79 | uuri = io.util.uniqueUri(uri); 80 | 81 | var options = { 82 | host: uri.host 83 | , secure: 'https' == uri.protocol 84 | , port: uri.port || ('https' == uri.protocol ? 443 : 80) 85 | , query: uri.query || '' 86 | }; 87 | 88 | io.util.merge(options, details); 89 | 90 | if (options['force new connection'] || !io.sockets[uuri]) { 91 | socket = new io.Socket(options); 92 | } 93 | 94 | if (!options['force new connection'] && socket) { 95 | io.sockets[uuri] = socket; 96 | } 97 | 98 | socket = socket || io.sockets[uuri]; 99 | 100 | // if path is different from '' or / 101 | return socket.of(uri.path.length > 1 ? uri.path : ''); 102 | }; 103 | 104 | })('object' === typeof module ? module.exports : (this.io = {}), this); 105 | /** 106 | * socket.io 107 | * Copyright(c) 2011 LearnBoost 108 | * MIT Licensed 109 | */ 110 | 111 | (function (exports, global) { 112 | 113 | /** 114 | * Utilities namespace. 115 | * 116 | * @namespace 117 | */ 118 | 119 | var util = exports.util = {}; 120 | 121 | /** 122 | * Parses an URI 123 | * 124 | * @author Steven Levithan (MIT license) 125 | * @api public 126 | */ 127 | 128 | var re = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; 129 | 130 | var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 131 | 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 132 | 'anchor']; 133 | 134 | util.parseUri = function (str) { 135 | var m = re.exec(str || '') 136 | , uri = {} 137 | , i = 14; 138 | 139 | while (i--) { 140 | uri[parts[i]] = m[i] || ''; 141 | } 142 | 143 | return uri; 144 | }; 145 | 146 | /** 147 | * Produces a unique url that identifies a Socket.IO connection. 148 | * 149 | * @param {Object} uri 150 | * @api public 151 | */ 152 | 153 | util.uniqueUri = function (uri) { 154 | var protocol = uri.protocol 155 | , host = uri.host 156 | , port = uri.port; 157 | 158 | if ('document' in global) { 159 | host = host || document.domain; 160 | port = port || (protocol == 'https' 161 | && document.location.protocol !== 'https:' ? 443 : document.location.port); 162 | } else { 163 | host = host || 'localhost'; 164 | 165 | if (!port && protocol == 'https') { 166 | port = 443; 167 | } 168 | } 169 | 170 | return (protocol || 'http') + '://' + host + ':' + (port || 80); 171 | }; 172 | 173 | /** 174 | * Mergest 2 query strings in to once unique query string 175 | * 176 | * @param {String} base 177 | * @param {String} addition 178 | * @api public 179 | */ 180 | 181 | util.query = function (base, addition) { 182 | var query = util.chunkQuery(base || '') 183 | , components = []; 184 | 185 | util.merge(query, util.chunkQuery(addition || '')); 186 | for (var part in query) { 187 | if (query.hasOwnProperty(part)) { 188 | components.push(part + '=' + query[part]); 189 | } 190 | } 191 | 192 | return components.length ? '?' + components.join('&') : ''; 193 | }; 194 | 195 | /** 196 | * Transforms a querystring in to an object 197 | * 198 | * @param {String} qs 199 | * @api public 200 | */ 201 | 202 | util.chunkQuery = function (qs) { 203 | var query = {} 204 | , params = qs.split('&') 205 | , i = 0 206 | , l = params.length 207 | , kv; 208 | 209 | for (; i < l; ++i) { 210 | kv = params[i].split('='); 211 | if (kv[0]) { 212 | query[kv[0]] = kv[1]; 213 | } 214 | } 215 | 216 | return query; 217 | }; 218 | 219 | /** 220 | * Executes the given function when the page is loaded. 221 | * 222 | * io.util.load(function () { console.log('page loaded'); }); 223 | * 224 | * @param {Function} fn 225 | * @api public 226 | */ 227 | 228 | var pageLoaded = false; 229 | 230 | util.load = function (fn) { 231 | if ('document' in global && document.readyState === 'complete' || pageLoaded) { 232 | return fn(); 233 | } 234 | 235 | util.on(global, 'load', fn, false); 236 | }; 237 | 238 | /** 239 | * Adds an event. 240 | * 241 | * @api private 242 | */ 243 | 244 | util.on = function (element, event, fn, capture) { 245 | if (element.attachEvent) { 246 | element.attachEvent('on' + event, fn); 247 | } else if (element.addEventListener) { 248 | element.addEventListener(event, fn, capture); 249 | } 250 | }; 251 | 252 | /** 253 | * Generates the correct `XMLHttpRequest` for regular and cross domain requests. 254 | * 255 | * @param {Boolean} [xdomain] Create a request that can be used cross domain. 256 | * @returns {XMLHttpRequest|false} If we can create a XMLHttpRequest. 257 | * @api private 258 | */ 259 | 260 | util.request = function (xdomain) { 261 | 262 | if (xdomain && 'undefined' != typeof XDomainRequest) { 263 | return new XDomainRequest(); 264 | } 265 | 266 | if ('undefined' != typeof XMLHttpRequest && (!xdomain || util.ua.hasCORS)) { 267 | return new XMLHttpRequest(); 268 | } 269 | 270 | if (!xdomain) { 271 | try { 272 | return new window[(['Active'].concat('Object').join('X'))]('Microsoft.XMLHTTP'); 273 | } catch(e) { } 274 | } 275 | 276 | return null; 277 | }; 278 | 279 | /** 280 | * XHR based transport constructor. 281 | * 282 | * @constructor 283 | * @api public 284 | */ 285 | 286 | /** 287 | * Change the internal pageLoaded value. 288 | */ 289 | 290 | if ('undefined' != typeof window) { 291 | util.load(function () { 292 | pageLoaded = true; 293 | }); 294 | } 295 | 296 | /** 297 | * Defers a function to ensure a spinner is not displayed by the browser 298 | * 299 | * @param {Function} fn 300 | * @api public 301 | */ 302 | 303 | util.defer = function (fn) { 304 | if (!util.ua.webkit || 'undefined' != typeof importScripts) { 305 | return fn(); 306 | } 307 | 308 | util.load(function () { 309 | setTimeout(fn, 100); 310 | }); 311 | }; 312 | 313 | /** 314 | * Merges two objects. 315 | * 316 | * @api public 317 | */ 318 | 319 | util.merge = function merge (target, additional, deep, lastseen) { 320 | var seen = lastseen || [] 321 | , depth = typeof deep == 'undefined' ? 2 : deep 322 | , prop; 323 | 324 | for (prop in additional) { 325 | if (additional.hasOwnProperty(prop) && util.indexOf(seen, prop) < 0) { 326 | if (typeof target[prop] !== 'object' || !depth) { 327 | target[prop] = additional[prop]; 328 | seen.push(additional[prop]); 329 | } else { 330 | util.merge(target[prop], additional[prop], depth - 1, seen); 331 | } 332 | } 333 | } 334 | 335 | return target; 336 | }; 337 | 338 | /** 339 | * Merges prototypes from objects 340 | * 341 | * @api public 342 | */ 343 | 344 | util.mixin = function (ctor, ctor2) { 345 | util.merge(ctor.prototype, ctor2.prototype); 346 | }; 347 | 348 | /** 349 | * Shortcut for prototypical and static inheritance. 350 | * 351 | * @api private 352 | */ 353 | 354 | util.inherit = function (ctor, ctor2) { 355 | function f() {}; 356 | f.prototype = ctor2.prototype; 357 | ctor.prototype = new f; 358 | }; 359 | 360 | /** 361 | * Checks if the given object is an Array. 362 | * 363 | * io.util.isArray([]); // true 364 | * io.util.isArray({}); // false 365 | * 366 | * @param Object obj 367 | * @api public 368 | */ 369 | 370 | util.isArray = Array.isArray || function (obj) { 371 | return Object.prototype.toString.call(obj) === '[object Array]'; 372 | }; 373 | 374 | /** 375 | * Intersects values of two arrays into a third 376 | * 377 | * @api public 378 | */ 379 | 380 | util.intersect = function (arr, arr2) { 381 | var ret = [] 382 | , longest = arr.length > arr2.length ? arr : arr2 383 | , shortest = arr.length > arr2.length ? arr2 : arr; 384 | 385 | for (var i = 0, l = shortest.length; i < l; i++) { 386 | if (~util.indexOf(longest, shortest[i])) 387 | ret.push(shortest[i]); 388 | } 389 | 390 | return ret; 391 | } 392 | 393 | /** 394 | * Array indexOf compatibility. 395 | * 396 | * @see bit.ly/a5Dxa2 397 | * @api public 398 | */ 399 | 400 | util.indexOf = function (arr, o, i) { 401 | 402 | for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0; 403 | i < j && arr[i] !== o; i++) {} 404 | 405 | return j <= i ? -1 : i; 406 | }; 407 | 408 | /** 409 | * Converts enumerables to array. 410 | * 411 | * @api public 412 | */ 413 | 414 | util.toArray = function (enu) { 415 | var arr = []; 416 | 417 | for (var i = 0, l = enu.length; i < l; i++) 418 | arr.push(enu[i]); 419 | 420 | return arr; 421 | }; 422 | 423 | /** 424 | * UA / engines detection namespace. 425 | * 426 | * @namespace 427 | */ 428 | 429 | util.ua = {}; 430 | 431 | /** 432 | * Whether the UA supports CORS for XHR. 433 | * 434 | * @api public 435 | */ 436 | 437 | util.ua.hasCORS = 'undefined' != typeof XMLHttpRequest && (function () { 438 | try { 439 | var a = new XMLHttpRequest(); 440 | } catch (e) { 441 | return false; 442 | } 443 | 444 | return a.withCredentials != undefined; 445 | })(); 446 | 447 | /** 448 | * Detect webkit. 449 | * 450 | * @api public 451 | */ 452 | 453 | util.ua.webkit = 'undefined' != typeof navigator 454 | && /webkit/i.test(navigator.userAgent); 455 | 456 | })('undefined' != typeof io ? io : module.exports, this); 457 | 458 | /** 459 | * socket.io 460 | * Copyright(c) 2011 LearnBoost 461 | * MIT Licensed 462 | */ 463 | 464 | (function (exports, io) { 465 | 466 | /** 467 | * Expose constructor. 468 | */ 469 | 470 | exports.EventEmitter = EventEmitter; 471 | 472 | /** 473 | * Event emitter constructor. 474 | * 475 | * @api public. 476 | */ 477 | 478 | function EventEmitter () {}; 479 | 480 | /** 481 | * Adds a listener 482 | * 483 | * @api public 484 | */ 485 | 486 | EventEmitter.prototype.on = function (name, fn) { 487 | if (!this.$events) { 488 | this.$events = {}; 489 | } 490 | 491 | if (!this.$events[name]) { 492 | this.$events[name] = fn; 493 | } else if (io.util.isArray(this.$events[name])) { 494 | this.$events[name].push(fn); 495 | } else { 496 | this.$events[name] = [this.$events[name], fn]; 497 | } 498 | 499 | return this; 500 | }; 501 | 502 | EventEmitter.prototype.addListener = EventEmitter.prototype.on; 503 | 504 | /** 505 | * Adds a volatile listener. 506 | * 507 | * @api public 508 | */ 509 | 510 | EventEmitter.prototype.once = function (name, fn) { 511 | var self = this; 512 | 513 | function on () { 514 | self.removeListener(name, on); 515 | fn.apply(this, arguments); 516 | }; 517 | 518 | on.listener = fn; 519 | this.on(name, on); 520 | 521 | return this; 522 | }; 523 | 524 | /** 525 | * Removes a listener. 526 | * 527 | * @api public 528 | */ 529 | 530 | EventEmitter.prototype.removeListener = function (name, fn) { 531 | if (this.$events && this.$events[name]) { 532 | var list = this.$events[name]; 533 | 534 | if (io.util.isArray(list)) { 535 | var pos = -1; 536 | 537 | for (var i = 0, l = list.length; i < l; i++) { 538 | if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { 539 | pos = i; 540 | break; 541 | } 542 | } 543 | 544 | if (pos < 0) { 545 | return this; 546 | } 547 | 548 | list.splice(pos, 1); 549 | 550 | if (!list.length) { 551 | delete this.$events[name]; 552 | } 553 | } else if (list === fn || (list.listener && list.listener === fn)) { 554 | delete this.$events[name]; 555 | } 556 | } 557 | 558 | return this; 559 | }; 560 | 561 | /** 562 | * Removes all listeners for an event. 563 | * 564 | * @api public 565 | */ 566 | 567 | EventEmitter.prototype.removeAllListeners = function (name) { 568 | // TODO: enable this when node 0.5 is stable 569 | //if (name === undefined) { 570 | //this.$events = {}; 571 | //return this; 572 | //} 573 | 574 | if (this.$events && this.$events[name]) { 575 | this.$events[name] = null; 576 | } 577 | 578 | return this; 579 | }; 580 | 581 | /** 582 | * Gets all listeners for a certain event. 583 | * 584 | * @api publci 585 | */ 586 | 587 | EventEmitter.prototype.listeners = function (name) { 588 | if (!this.$events) { 589 | this.$events = {}; 590 | } 591 | 592 | if (!this.$events[name]) { 593 | this.$events[name] = []; 594 | } 595 | 596 | if (!io.util.isArray(this.$events[name])) { 597 | this.$events[name] = [this.$events[name]]; 598 | } 599 | 600 | return this.$events[name]; 601 | }; 602 | 603 | /** 604 | * Emits an event. 605 | * 606 | * @api public 607 | */ 608 | 609 | EventEmitter.prototype.emit = function (name) { 610 | if (!this.$events) { 611 | return false; 612 | } 613 | 614 | var handler = this.$events[name]; 615 | 616 | if (!handler) { 617 | return false; 618 | } 619 | 620 | var args = Array.prototype.slice.call(arguments, 1); 621 | 622 | if ('function' == typeof handler) { 623 | handler.apply(this, args); 624 | } else if (io.util.isArray(handler)) { 625 | var listeners = handler.slice(); 626 | 627 | for (var i = 0, l = listeners.length; i < l; i++) { 628 | listeners[i].apply(this, args); 629 | } 630 | } else { 631 | return false; 632 | } 633 | 634 | return true; 635 | }; 636 | 637 | })( 638 | 'undefined' != typeof io ? io : module.exports 639 | , 'undefined' != typeof io ? io : module.parent.exports 640 | ); 641 | 642 | /** 643 | * socket.io 644 | * Copyright(c) 2011 LearnBoost 645 | * MIT Licensed 646 | */ 647 | 648 | /** 649 | * Based on JSON2 (http://www.JSON.org/js.html). 650 | */ 651 | 652 | (function (exports, nativeJSON) { 653 | "use strict"; 654 | 655 | // use native JSON if it's available 656 | if (nativeJSON && nativeJSON.parse){ 657 | return exports.JSON = { 658 | parse: nativeJSON.parse 659 | , stringify: nativeJSON.stringify 660 | } 661 | } 662 | 663 | var JSON = exports.JSON = {}; 664 | 665 | function f(n) { 666 | // Format integers to have at least two digits. 667 | return n < 10 ? '0' + n : n; 668 | } 669 | 670 | function date(d, key) { 671 | return isFinite(d.valueOf()) ? 672 | d.getUTCFullYear() + '-' + 673 | f(d.getUTCMonth() + 1) + '-' + 674 | f(d.getUTCDate()) + 'T' + 675 | f(d.getUTCHours()) + ':' + 676 | f(d.getUTCMinutes()) + ':' + 677 | f(d.getUTCSeconds()) + 'Z' : null; 678 | }; 679 | 680 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 681 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 682 | gap, 683 | indent, 684 | meta = { // table of character substitutions 685 | '\b': '\\b', 686 | '\t': '\\t', 687 | '\n': '\\n', 688 | '\f': '\\f', 689 | '\r': '\\r', 690 | '"' : '\\"', 691 | '\\': '\\\\' 692 | }, 693 | rep; 694 | 695 | 696 | function quote(string) { 697 | 698 | // If the string contains no control characters, no quote characters, and no 699 | // backslash characters, then we can safely slap some quotes around it. 700 | // Otherwise we must also replace the offending characters with safe escape 701 | // sequences. 702 | 703 | escapable.lastIndex = 0; 704 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 705 | var c = meta[a]; 706 | return typeof c === 'string' ? c : 707 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 708 | }) + '"' : '"' + string + '"'; 709 | } 710 | 711 | 712 | function str(key, holder) { 713 | 714 | // Produce a string from holder[key]. 715 | 716 | var i, // The loop counter. 717 | k, // The member key. 718 | v, // The member value. 719 | length, 720 | mind = gap, 721 | partial, 722 | value = holder[key]; 723 | 724 | // If the value has a toJSON method, call it to obtain a replacement value. 725 | 726 | if (value instanceof Date) { 727 | value = date(key); 728 | } 729 | 730 | // If we were called with a replacer function, then call the replacer to 731 | // obtain a replacement value. 732 | 733 | if (typeof rep === 'function') { 734 | value = rep.call(holder, key, value); 735 | } 736 | 737 | // What happens next depends on the value's type. 738 | 739 | switch (typeof value) { 740 | case 'string': 741 | return quote(value); 742 | 743 | case 'number': 744 | 745 | // JSON numbers must be finite. Encode non-finite numbers as null. 746 | 747 | return isFinite(value) ? String(value) : 'null'; 748 | 749 | case 'boolean': 750 | case 'null': 751 | 752 | // If the value is a boolean or null, convert it to a string. Note: 753 | // typeof null does not produce 'null'. The case is included here in 754 | // the remote chance that this gets fixed someday. 755 | 756 | return String(value); 757 | 758 | // If the type is 'object', we might be dealing with an object or an array or 759 | // null. 760 | 761 | case 'object': 762 | 763 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 764 | // so watch out for that case. 765 | 766 | if (!value) { 767 | return 'null'; 768 | } 769 | 770 | // Make an array to hold the partial results of stringifying this object value. 771 | 772 | gap += indent; 773 | partial = []; 774 | 775 | // Is the value an array? 776 | 777 | if (Object.prototype.toString.apply(value) === '[object Array]') { 778 | 779 | // The value is an array. Stringify every element. Use null as a placeholder 780 | // for non-JSON values. 781 | 782 | length = value.length; 783 | for (i = 0; i < length; i += 1) { 784 | partial[i] = str(i, value) || 'null'; 785 | } 786 | 787 | // Join all of the elements together, separated with commas, and wrap them in 788 | // brackets. 789 | 790 | v = partial.length === 0 ? '[]' : gap ? 791 | '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : 792 | '[' + partial.join(',') + ']'; 793 | gap = mind; 794 | return v; 795 | } 796 | 797 | // If the replacer is an array, use it to select the members to be stringified. 798 | 799 | if (rep && typeof rep === 'object') { 800 | length = rep.length; 801 | for (i = 0; i < length; i += 1) { 802 | if (typeof rep[i] === 'string') { 803 | k = rep[i]; 804 | v = str(k, value); 805 | if (v) { 806 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 807 | } 808 | } 809 | } 810 | } else { 811 | 812 | // Otherwise, iterate through all of the keys in the object. 813 | 814 | for (k in value) { 815 | if (Object.prototype.hasOwnProperty.call(value, k)) { 816 | v = str(k, value); 817 | if (v) { 818 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 819 | } 820 | } 821 | } 822 | } 823 | 824 | // Join all of the member texts together, separated with commas, 825 | // and wrap them in braces. 826 | 827 | v = partial.length === 0 ? '{}' : gap ? 828 | '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : 829 | '{' + partial.join(',') + '}'; 830 | gap = mind; 831 | return v; 832 | } 833 | } 834 | 835 | // If the JSON object does not yet have a stringify method, give it one. 836 | 837 | JSON.stringify = function (value, replacer, space) { 838 | 839 | // The stringify method takes a value and an optional replacer, and an optional 840 | // space parameter, and returns a JSON text. The replacer can be a function 841 | // that can replace values, or an array of strings that will select the keys. 842 | // A default replacer method can be provided. Use of the space parameter can 843 | // produce text that is more easily readable. 844 | 845 | var i; 846 | gap = ''; 847 | indent = ''; 848 | 849 | // If the space parameter is a number, make an indent string containing that 850 | // many spaces. 851 | 852 | if (typeof space === 'number') { 853 | for (i = 0; i < space; i += 1) { 854 | indent += ' '; 855 | } 856 | 857 | // If the space parameter is a string, it will be used as the indent string. 858 | 859 | } else if (typeof space === 'string') { 860 | indent = space; 861 | } 862 | 863 | // If there is a replacer, it must be a function or an array. 864 | // Otherwise, throw an error. 865 | 866 | rep = replacer; 867 | if (replacer && typeof replacer !== 'function' && 868 | (typeof replacer !== 'object' || 869 | typeof replacer.length !== 'number')) { 870 | throw new Error('JSON.stringify'); 871 | } 872 | 873 | // Make a fake root object containing our value under the key of ''. 874 | // Return the result of stringifying the value. 875 | 876 | return str('', {'': value}); 877 | }; 878 | 879 | // If the JSON object does not yet have a parse method, give it one. 880 | 881 | JSON.parse = function (text, reviver) { 882 | // The parse method takes a text and an optional reviver function, and returns 883 | // a JavaScript value if the text is a valid JSON text. 884 | 885 | var j; 886 | 887 | function walk(holder, key) { 888 | 889 | // The walk method is used to recursively walk the resulting structure so 890 | // that modifications can be made. 891 | 892 | var k, v, value = holder[key]; 893 | if (value && typeof value === 'object') { 894 | for (k in value) { 895 | if (Object.prototype.hasOwnProperty.call(value, k)) { 896 | v = walk(value, k); 897 | if (v !== undefined) { 898 | value[k] = v; 899 | } else { 900 | delete value[k]; 901 | } 902 | } 903 | } 904 | } 905 | return reviver.call(holder, key, value); 906 | } 907 | 908 | 909 | // Parsing happens in four stages. In the first stage, we replace certain 910 | // Unicode characters with escape sequences. JavaScript handles many characters 911 | // incorrectly, either silently deleting them, or treating them as line endings. 912 | 913 | text = String(text); 914 | cx.lastIndex = 0; 915 | if (cx.test(text)) { 916 | text = text.replace(cx, function (a) { 917 | return '\\u' + 918 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 919 | }); 920 | } 921 | 922 | // In the second stage, we run the text against regular expressions that look 923 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 924 | // because they can cause invocation, and '=' because it can cause mutation. 925 | // But just to be safe, we want to reject all unexpected forms. 926 | 927 | // We split the second stage into 4 regexp operations in order to work around 928 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 929 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 930 | // replace all simple value tokens with ']' characters. Third, we delete all 931 | // open brackets that follow a colon or comma or that begin the text. Finally, 932 | // we look to see that the remaining characters are only whitespace or ']' or 933 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 934 | 935 | if (/^[\],:{}\s]*$/ 936 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 937 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 938 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 939 | 940 | // In the third stage we use the eval function to compile the text into a 941 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 942 | // in JavaScript: it can begin a block or an object literal. We wrap the text 943 | // in parens to eliminate the ambiguity. 944 | 945 | j = eval('(' + text + ')'); 946 | 947 | // In the optional fourth stage, we recursively walk the new structure, passing 948 | // each name/value pair to a reviver function for possible transformation. 949 | 950 | return typeof reviver === 'function' ? 951 | walk({'': j}, '') : j; 952 | } 953 | 954 | // If the text is not JSON parseable, then a SyntaxError is thrown. 955 | 956 | throw new SyntaxError('JSON.parse'); 957 | }; 958 | 959 | })( 960 | 'undefined' != typeof io ? io : module.exports 961 | , typeof JSON !== 'undefined' ? JSON : undefined 962 | ); 963 | 964 | /** 965 | * socket.io 966 | * Copyright(c) 2011 LearnBoost 967 | * MIT Licensed 968 | */ 969 | 970 | (function (exports, io) { 971 | 972 | /** 973 | * Parser namespace. 974 | * 975 | * @namespace 976 | */ 977 | 978 | var parser = exports.parser = {}; 979 | 980 | /** 981 | * Packet types. 982 | */ 983 | 984 | var packets = parser.packets = [ 985 | 'disconnect' 986 | , 'connect' 987 | , 'heartbeat' 988 | , 'message' 989 | , 'json' 990 | , 'event' 991 | , 'ack' 992 | , 'error' 993 | , 'noop' 994 | ]; 995 | 996 | /** 997 | * Errors reasons. 998 | */ 999 | 1000 | var reasons = parser.reasons = [ 1001 | 'transport not supported' 1002 | , 'client not handshaken' 1003 | , 'unauthorized' 1004 | ]; 1005 | 1006 | /** 1007 | * Errors advice. 1008 | */ 1009 | 1010 | var advice = parser.advice = [ 1011 | 'reconnect' 1012 | ]; 1013 | 1014 | /** 1015 | * Shortcuts. 1016 | */ 1017 | 1018 | var JSON = io.JSON 1019 | , indexOf = io.util.indexOf; 1020 | 1021 | /** 1022 | * Encodes a packet. 1023 | * 1024 | * @api private 1025 | */ 1026 | 1027 | parser.encodePacket = function (packet) { 1028 | var type = indexOf(packets, packet.type) 1029 | , id = packet.id || '' 1030 | , endpoint = packet.endpoint || '' 1031 | , ack = packet.ack 1032 | , data = null; 1033 | 1034 | switch (packet.type) { 1035 | case 'error': 1036 | var reason = packet.reason ? indexOf(reasons, packet.reason) : '' 1037 | , adv = packet.advice ? indexOf(advice, packet.advice) : ''; 1038 | 1039 | if (reason !== '' || adv !== '') 1040 | data = reason + (adv !== '' ? ('+' + adv) : ''); 1041 | 1042 | break; 1043 | 1044 | case 'message': 1045 | if (packet.data !== '') 1046 | data = packet.data; 1047 | break; 1048 | 1049 | case 'event': 1050 | var ev = { name: packet.name }; 1051 | 1052 | if (packet.args && packet.args.length) { 1053 | ev.args = packet.args; 1054 | } 1055 | 1056 | data = JSON.stringify(ev); 1057 | break; 1058 | 1059 | case 'json': 1060 | data = JSON.stringify(packet.data); 1061 | break; 1062 | 1063 | case 'connect': 1064 | if (packet.qs) 1065 | data = packet.qs; 1066 | break; 1067 | 1068 | case 'ack': 1069 | data = packet.ackId 1070 | + (packet.args && packet.args.length 1071 | ? '+' + JSON.stringify(packet.args) : ''); 1072 | break; 1073 | } 1074 | 1075 | // construct packet with required fragments 1076 | var encoded = [ 1077 | type 1078 | , id + (ack == 'data' ? '+' : '') 1079 | , endpoint 1080 | ]; 1081 | 1082 | // data fragment is optional 1083 | if (data !== null && data !== undefined) 1084 | encoded.push(data); 1085 | 1086 | return encoded.join(':'); 1087 | }; 1088 | 1089 | /** 1090 | * Encodes multiple messages (payload). 1091 | * 1092 | * @param {Array} messages 1093 | * @api private 1094 | */ 1095 | 1096 | parser.encodePayload = function (packets) { 1097 | var decoded = ''; 1098 | 1099 | if (packets.length == 1) 1100 | return packets[0]; 1101 | 1102 | for (var i = 0, l = packets.length; i < l; i++) { 1103 | var packet = packets[i]; 1104 | decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]; 1105 | } 1106 | 1107 | return decoded; 1108 | }; 1109 | 1110 | /** 1111 | * Decodes a packet 1112 | * 1113 | * @api private 1114 | */ 1115 | 1116 | var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/; 1117 | 1118 | parser.decodePacket = function (data) { 1119 | var pieces = data.match(regexp); 1120 | 1121 | if (!pieces) return {}; 1122 | 1123 | var id = pieces[2] || '' 1124 | , data = pieces[5] || '' 1125 | , packet = { 1126 | type: packets[pieces[1]] 1127 | , endpoint: pieces[4] || '' 1128 | }; 1129 | 1130 | // whether we need to acknowledge the packet 1131 | if (id) { 1132 | packet.id = id; 1133 | if (pieces[3]) 1134 | packet.ack = 'data'; 1135 | else 1136 | packet.ack = true; 1137 | } 1138 | 1139 | // handle different packet types 1140 | switch (packet.type) { 1141 | case 'error': 1142 | var pieces = data.split('+'); 1143 | packet.reason = reasons[pieces[0]] || ''; 1144 | packet.advice = advice[pieces[1]] || ''; 1145 | break; 1146 | 1147 | case 'message': 1148 | packet.data = data || ''; 1149 | break; 1150 | 1151 | case 'event': 1152 | try { 1153 | var opts = JSON.parse(data); 1154 | packet.name = opts.name; 1155 | packet.args = opts.args; 1156 | } catch (e) { } 1157 | 1158 | packet.args = packet.args || []; 1159 | break; 1160 | 1161 | case 'json': 1162 | try { 1163 | packet.data = JSON.parse(data); 1164 | } catch (e) { } 1165 | break; 1166 | 1167 | case 'connect': 1168 | packet.qs = data || ''; 1169 | break; 1170 | 1171 | case 'ack': 1172 | var pieces = data.match(/^([0-9]+)(\+)?(.*)/); 1173 | if (pieces) { 1174 | packet.ackId = pieces[1]; 1175 | packet.args = []; 1176 | 1177 | if (pieces[3]) { 1178 | try { 1179 | packet.args = pieces[3] ? JSON.parse(pieces[3]) : []; 1180 | } catch (e) { } 1181 | } 1182 | } 1183 | break; 1184 | 1185 | case 'disconnect': 1186 | case 'heartbeat': 1187 | break; 1188 | }; 1189 | 1190 | return packet; 1191 | }; 1192 | 1193 | /** 1194 | * Decodes data payload. Detects multiple messages 1195 | * 1196 | * @return {Array} messages 1197 | * @api public 1198 | */ 1199 | 1200 | parser.decodePayload = function (data) { 1201 | // IE doesn't like data[i] for unicode chars, charAt works fine 1202 | if (data.charAt(0) == '\ufffd') { 1203 | var ret = []; 1204 | 1205 | for (var i = 1, length = ''; i < data.length; i++) { 1206 | if (data.charAt(i) == '\ufffd') { 1207 | ret.push(parser.decodePacket(data.substr(i + 1).substr(0, length))); 1208 | i += Number(length) + 1; 1209 | length = ''; 1210 | } else { 1211 | length += data.charAt(i); 1212 | } 1213 | } 1214 | 1215 | return ret; 1216 | } else { 1217 | return [parser.decodePacket(data)]; 1218 | } 1219 | }; 1220 | 1221 | })( 1222 | 'undefined' != typeof io ? io : module.exports 1223 | , 'undefined' != typeof io ? io : module.parent.exports 1224 | ); 1225 | /** 1226 | * socket.io 1227 | * Copyright(c) 2011 LearnBoost 1228 | * MIT Licensed 1229 | */ 1230 | 1231 | (function (exports, io) { 1232 | 1233 | /** 1234 | * Expose constructor. 1235 | */ 1236 | 1237 | exports.Transport = Transport; 1238 | 1239 | /** 1240 | * This is the transport template for all supported transport methods. 1241 | * 1242 | * @constructor 1243 | * @api public 1244 | */ 1245 | 1246 | function Transport (socket, sessid) { 1247 | this.socket = socket; 1248 | this.sessid = sessid; 1249 | }; 1250 | 1251 | /** 1252 | * Apply EventEmitter mixin. 1253 | */ 1254 | 1255 | io.util.mixin(Transport, io.EventEmitter); 1256 | 1257 | /** 1258 | * Handles the response from the server. When a new response is received 1259 | * it will automatically update the timeout, decode the message and 1260 | * forwards the response to the onMessage function for further processing. 1261 | * 1262 | * @param {String} data Response from the server. 1263 | * @api private 1264 | */ 1265 | 1266 | Transport.prototype.onData = function (data) { 1267 | this.clearCloseTimeout(); 1268 | 1269 | // If the connection in currently open (or in a reopening state) reset the close 1270 | // timeout since we have just received data. This check is necessary so 1271 | // that we don't reset the timeout on an explicitly disconnected connection. 1272 | if (this.socket.connected || this.socket.connecting || this.socket.reconnecting) { 1273 | this.setCloseTimeout(); 1274 | } 1275 | 1276 | if (data !== '') { 1277 | // todo: we should only do decodePayload for xhr transports 1278 | var msgs = io.parser.decodePayload(data); 1279 | 1280 | if (msgs && msgs.length) { 1281 | for (var i = 0, l = msgs.length; i < l; i++) { 1282 | this.onPacket(msgs[i]); 1283 | } 1284 | } 1285 | } 1286 | 1287 | return this; 1288 | }; 1289 | 1290 | /** 1291 | * Handles packets. 1292 | * 1293 | * @api private 1294 | */ 1295 | 1296 | Transport.prototype.onPacket = function (packet) { 1297 | this.socket.setHeartbeatTimeout(); 1298 | 1299 | if (packet.type == 'heartbeat') { 1300 | return this.onHeartbeat(); 1301 | } 1302 | 1303 | if (packet.type == 'connect' && packet.endpoint == '') { 1304 | this.onConnect(); 1305 | } 1306 | 1307 | if (packet.type == 'error' && packet.advice == 'reconnect') { 1308 | this.open = false; 1309 | } 1310 | 1311 | this.socket.onPacket(packet); 1312 | 1313 | return this; 1314 | }; 1315 | 1316 | /** 1317 | * Sets close timeout 1318 | * 1319 | * @api private 1320 | */ 1321 | 1322 | Transport.prototype.setCloseTimeout = function () { 1323 | if (!this.closeTimeout) { 1324 | var self = this; 1325 | 1326 | this.closeTimeout = setTimeout(function () { 1327 | self.onDisconnect(); 1328 | }, this.socket.closeTimeout); 1329 | } 1330 | }; 1331 | 1332 | /** 1333 | * Called when transport disconnects. 1334 | * 1335 | * @api private 1336 | */ 1337 | 1338 | Transport.prototype.onDisconnect = function () { 1339 | if (this.close && this.open) this.close(); 1340 | this.clearTimeouts(); 1341 | this.socket.onDisconnect(); 1342 | return this; 1343 | }; 1344 | 1345 | /** 1346 | * Called when transport connects 1347 | * 1348 | * @api private 1349 | */ 1350 | 1351 | Transport.prototype.onConnect = function () { 1352 | this.socket.onConnect(); 1353 | return this; 1354 | } 1355 | 1356 | /** 1357 | * Clears close timeout 1358 | * 1359 | * @api private 1360 | */ 1361 | 1362 | Transport.prototype.clearCloseTimeout = function () { 1363 | if (this.closeTimeout) { 1364 | clearTimeout(this.closeTimeout); 1365 | this.closeTimeout = null; 1366 | } 1367 | }; 1368 | 1369 | /** 1370 | * Clear timeouts 1371 | * 1372 | * @api private 1373 | */ 1374 | 1375 | Transport.prototype.clearTimeouts = function () { 1376 | this.clearCloseTimeout(); 1377 | 1378 | if (this.reopenTimeout) { 1379 | clearTimeout(this.reopenTimeout); 1380 | } 1381 | }; 1382 | 1383 | /** 1384 | * Sends a packet 1385 | * 1386 | * @param {Object} packet object. 1387 | * @api private 1388 | */ 1389 | 1390 | Transport.prototype.packet = function (packet) { 1391 | this.send(io.parser.encodePacket(packet)); 1392 | }; 1393 | 1394 | /** 1395 | * Send the received heartbeat message back to server. So the server 1396 | * knows we are still connected. 1397 | * 1398 | * @param {String} heartbeat Heartbeat response from the server. 1399 | * @api private 1400 | */ 1401 | 1402 | Transport.prototype.onHeartbeat = function (heartbeat) { 1403 | this.packet({ type: 'heartbeat' }); 1404 | }; 1405 | 1406 | /** 1407 | * Called when the transport opens. 1408 | * 1409 | * @api private 1410 | */ 1411 | 1412 | Transport.prototype.onOpen = function () { 1413 | this.open = true; 1414 | this.clearCloseTimeout(); 1415 | this.socket.onOpen(); 1416 | }; 1417 | 1418 | /** 1419 | * Notifies the base when the connection with the Socket.IO server 1420 | * has been disconnected. 1421 | * 1422 | * @api private 1423 | */ 1424 | 1425 | Transport.prototype.onClose = function () { 1426 | var self = this; 1427 | 1428 | /* FIXME: reopen delay causing a infinit loop 1429 | this.reopenTimeout = setTimeout(function () { 1430 | self.open(); 1431 | }, this.socket.options['reopen delay']);*/ 1432 | 1433 | this.open = false; 1434 | this.socket.onClose(); 1435 | this.onDisconnect(); 1436 | }; 1437 | 1438 | /** 1439 | * Generates a connection url based on the Socket.IO URL Protocol. 1440 | * See for more details. 1441 | * 1442 | * @returns {String} Connection url 1443 | * @api private 1444 | */ 1445 | 1446 | Transport.prototype.prepareUrl = function () { 1447 | var options = this.socket.options; 1448 | 1449 | return this.scheme() + '://' 1450 | + options.host + ':' + options.port + '/' 1451 | + options.resource + '/' + io.protocol 1452 | + '/' + this.name + '/' + this.sessid; 1453 | }; 1454 | 1455 | /** 1456 | * Checks if the transport is ready to start a connection. 1457 | * 1458 | * @param {Socket} socket The socket instance that needs a transport 1459 | * @param {Function} fn The callback 1460 | * @api private 1461 | */ 1462 | 1463 | Transport.prototype.ready = function (socket, fn) { 1464 | fn.call(this); 1465 | }; 1466 | })( 1467 | 'undefined' != typeof io ? io : module.exports 1468 | , 'undefined' != typeof io ? io : module.parent.exports 1469 | ); 1470 | /** 1471 | * socket.io 1472 | * Copyright(c) 2011 LearnBoost 1473 | * MIT Licensed 1474 | */ 1475 | 1476 | (function (exports, io, global) { 1477 | 1478 | /** 1479 | * Expose constructor. 1480 | */ 1481 | 1482 | exports.Socket = Socket; 1483 | 1484 | /** 1485 | * Create a new `Socket.IO client` which can establish a persistent 1486 | * connection with a Socket.IO enabled server. 1487 | * 1488 | * @api public 1489 | */ 1490 | 1491 | function Socket (options) { 1492 | this.options = { 1493 | port: 80 1494 | , secure: false 1495 | , document: 'document' in global ? document : false 1496 | , resource: 'socket.io' 1497 | , transports: io.transports 1498 | , 'connect timeout': 10000 1499 | , 'try multiple transports': true 1500 | , 'reconnect': true 1501 | , 'reconnection delay': 500 1502 | , 'reconnection limit': Infinity 1503 | , 'reopen delay': 3000 1504 | , 'max reconnection attempts': 10 1505 | , 'sync disconnect on unload': true 1506 | , 'auto connect': true 1507 | , 'flash policy port': 10843 1508 | }; 1509 | 1510 | io.util.merge(this.options, options); 1511 | 1512 | this.connected = false; 1513 | this.open = false; 1514 | this.connecting = false; 1515 | this.reconnecting = false; 1516 | this.namespaces = {}; 1517 | this.buffer = []; 1518 | this.doBuffer = false; 1519 | 1520 | if (this.options['sync disconnect on unload'] && 1521 | (!this.isXDomain() || io.util.ua.hasCORS)) { 1522 | var self = this; 1523 | 1524 | io.util.on(global, 'unload', function () { 1525 | self.disconnectSync(); 1526 | }, false); 1527 | } 1528 | 1529 | if (this.options['auto connect']) { 1530 | this.connect(); 1531 | } 1532 | }; 1533 | 1534 | /** 1535 | * Apply EventEmitter mixin. 1536 | */ 1537 | 1538 | io.util.mixin(Socket, io.EventEmitter); 1539 | 1540 | /** 1541 | * Returns a namespace listener/emitter for this socket 1542 | * 1543 | * @api public 1544 | */ 1545 | 1546 | Socket.prototype.of = function (name) { 1547 | if (!this.namespaces[name]) { 1548 | this.namespaces[name] = new io.SocketNamespace(this, name); 1549 | 1550 | if (name !== '') { 1551 | this.namespaces[name].packet({ type: 'connect' }); 1552 | } 1553 | } 1554 | 1555 | return this.namespaces[name]; 1556 | }; 1557 | 1558 | /** 1559 | * Emits the given event to the Socket and all namespaces 1560 | * 1561 | * @api private 1562 | */ 1563 | 1564 | Socket.prototype.publish = function () { 1565 | this.emit.apply(this, arguments); 1566 | 1567 | var nsp; 1568 | 1569 | for (var i in this.namespaces) { 1570 | if (this.namespaces.hasOwnProperty(i)) { 1571 | nsp = this.of(i); 1572 | nsp.$emit.apply(nsp, arguments); 1573 | } 1574 | } 1575 | }; 1576 | 1577 | /** 1578 | * Performs the handshake 1579 | * 1580 | * @api private 1581 | */ 1582 | 1583 | function empty () { }; 1584 | 1585 | Socket.prototype.handshake = function (fn) { 1586 | var self = this 1587 | , options = this.options; 1588 | 1589 | function complete (data) { 1590 | if (data instanceof Error) { 1591 | self.onError(data.message); 1592 | } else { 1593 | fn.apply(null, data.split(':')); 1594 | } 1595 | }; 1596 | 1597 | var url = [ 1598 | 'http' + (options.secure ? 's' : '') + ':/' 1599 | , options.host + ':' + options.port 1600 | , options.resource 1601 | , io.protocol 1602 | , io.util.query(this.options.query, 't=' + +new Date) 1603 | ].join('/'); 1604 | 1605 | if (this.isXDomain() && !io.util.ua.hasCORS) { 1606 | var insertAt = document.getElementsByTagName('script')[0] 1607 | , script = document.createElement('script'); 1608 | 1609 | script.src = url + '&jsonp=' + io.j.length; 1610 | insertAt.parentNode.insertBefore(script, insertAt); 1611 | 1612 | io.j.push(function (data) { 1613 | complete(data); 1614 | script.parentNode.removeChild(script); 1615 | }); 1616 | } else { 1617 | var xhr = io.util.request(); 1618 | 1619 | xhr.open('GET', url, true); 1620 | xhr.withCredentials = true; 1621 | xhr.onreadystatechange = function () { 1622 | if (xhr.readyState == 4) { 1623 | xhr.onreadystatechange = empty; 1624 | 1625 | if (xhr.status == 200) { 1626 | complete(xhr.responseText); 1627 | } else { 1628 | !self.reconnecting && self.onError(xhr.responseText); 1629 | } 1630 | } 1631 | }; 1632 | xhr.send(null); 1633 | } 1634 | }; 1635 | 1636 | /** 1637 | * Find an available transport based on the options supplied in the constructor. 1638 | * 1639 | * @api private 1640 | */ 1641 | 1642 | Socket.prototype.getTransport = function (override) { 1643 | var transports = override || this.transports, match; 1644 | 1645 | for (var i = 0, transport; transport = transports[i]; i++) { 1646 | if (io.Transport[transport] 1647 | && io.Transport[transport].check(this) 1648 | && (!this.isXDomain() || io.Transport[transport].xdomainCheck())) { 1649 | return new io.Transport[transport](this, this.sessionid); 1650 | } 1651 | } 1652 | 1653 | return null; 1654 | }; 1655 | 1656 | /** 1657 | * Connects to the server. 1658 | * 1659 | * @param {Function} [fn] Callback. 1660 | * @returns {io.Socket} 1661 | * @api public 1662 | */ 1663 | 1664 | Socket.prototype.connect = function (fn) { 1665 | if (this.connecting) { 1666 | return this; 1667 | } 1668 | 1669 | var self = this; 1670 | 1671 | this.handshake(function (sid, heartbeat, close, transports) { 1672 | self.sessionid = sid; 1673 | self.closeTimeout = close * 1000; 1674 | self.heartbeatTimeout = heartbeat * 1000; 1675 | self.transports = transports ? io.util.intersect( 1676 | transports.split(',') 1677 | , self.options.transports 1678 | ) : self.options.transports; 1679 | 1680 | self.setHeartbeatTimeout(); 1681 | 1682 | function connect (transports){ 1683 | if (self.transport) self.transport.clearTimeouts(); 1684 | 1685 | self.transport = self.getTransport(transports); 1686 | if (!self.transport) return self.publish('connect_failed'); 1687 | 1688 | // once the transport is ready 1689 | self.transport.ready(self, function () { 1690 | self.connecting = true; 1691 | self.publish('connecting', self.transport.name); 1692 | self.transport.open(); 1693 | 1694 | if (self.options['connect timeout']) { 1695 | self.connectTimeoutTimer = setTimeout(function () { 1696 | if (!self.connected) { 1697 | self.connecting = false; 1698 | 1699 | if (self.options['try multiple transports']) { 1700 | if (!self.remainingTransports) { 1701 | self.remainingTransports = self.transports.slice(0); 1702 | } 1703 | 1704 | var remaining = self.remainingTransports; 1705 | 1706 | while (remaining.length > 0 && remaining.splice(0,1)[0] != 1707 | self.transport.name) {} 1708 | 1709 | if (remaining.length){ 1710 | connect(remaining); 1711 | } else { 1712 | self.publish('connect_failed'); 1713 | } 1714 | } 1715 | } 1716 | }, self.options['connect timeout']); 1717 | } 1718 | }); 1719 | } 1720 | 1721 | connect(self.transports); 1722 | 1723 | self.once('connect', function (){ 1724 | clearTimeout(self.connectTimeoutTimer); 1725 | 1726 | fn && typeof fn == 'function' && fn(); 1727 | }); 1728 | }); 1729 | 1730 | return this; 1731 | }; 1732 | 1733 | /** 1734 | * Clears and sets a new heartbeat timeout using the value given by the 1735 | * server during the handshake. 1736 | * 1737 | * @api private 1738 | */ 1739 | 1740 | Socket.prototype.setHeartbeatTimeout = function () { 1741 | clearTimeout(this.heartbeatTimeoutTimer); 1742 | 1743 | var self = this; 1744 | this.heartbeatTimeoutTimer = setTimeout(function () { 1745 | self.transport.onClose(); 1746 | }, this.heartbeatTimeout); 1747 | }; 1748 | 1749 | /** 1750 | * Sends a message. 1751 | * 1752 | * @param {Object} data packet. 1753 | * @returns {io.Socket} 1754 | * @api public 1755 | */ 1756 | 1757 | Socket.prototype.packet = function (data) { 1758 | if (this.connected && !this.doBuffer) { 1759 | this.transport.packet(data); 1760 | } else { 1761 | this.buffer.push(data); 1762 | } 1763 | 1764 | return this; 1765 | }; 1766 | 1767 | /** 1768 | * Sets buffer state 1769 | * 1770 | * @api private 1771 | */ 1772 | 1773 | Socket.prototype.setBuffer = function (v) { 1774 | this.doBuffer = v; 1775 | 1776 | if (!v && this.connected && this.buffer.length) { 1777 | this.transport.payload(this.buffer); 1778 | this.buffer = []; 1779 | } 1780 | }; 1781 | 1782 | /** 1783 | * Disconnect the established connect. 1784 | * 1785 | * @returns {io.Socket} 1786 | * @api public 1787 | */ 1788 | 1789 | Socket.prototype.disconnect = function () { 1790 | if (this.connected || this.connecting) { 1791 | if (this.open) { 1792 | this.of('').packet({ type: 'disconnect' }); 1793 | } 1794 | 1795 | // handle disconnection immediately 1796 | this.onDisconnect('booted'); 1797 | } 1798 | 1799 | return this; 1800 | }; 1801 | 1802 | /** 1803 | * Disconnects the socket with a sync XHR. 1804 | * 1805 | * @api private 1806 | */ 1807 | 1808 | Socket.prototype.disconnectSync = function () { 1809 | // ensure disconnection 1810 | var xhr = io.util.request() 1811 | , uri = this.resource + '/' + io.protocol + '/' + this.sessionid; 1812 | 1813 | xhr.open('GET', uri, true); 1814 | 1815 | // handle disconnection immediately 1816 | this.onDisconnect('booted'); 1817 | }; 1818 | 1819 | /** 1820 | * Check if we need to use cross domain enabled transports. Cross domain would 1821 | * be a different port or different domain name. 1822 | * 1823 | * @returns {Boolean} 1824 | * @api private 1825 | */ 1826 | 1827 | Socket.prototype.isXDomain = function () { 1828 | 1829 | var port = global.location.port || 1830 | ('https:' == global.location.protocol ? 443 : 80); 1831 | 1832 | return this.options.host !== global.location.hostname 1833 | || this.options.port != port; 1834 | }; 1835 | 1836 | /** 1837 | * Called upon handshake. 1838 | * 1839 | * @api private 1840 | */ 1841 | 1842 | Socket.prototype.onConnect = function () { 1843 | if (!this.connected) { 1844 | this.connected = true; 1845 | this.connecting = false; 1846 | if (!this.doBuffer) { 1847 | // make sure to flush the buffer 1848 | this.setBuffer(false); 1849 | } 1850 | this.emit('connect'); 1851 | } 1852 | }; 1853 | 1854 | /** 1855 | * Called when the transport opens 1856 | * 1857 | * @api private 1858 | */ 1859 | 1860 | Socket.prototype.onOpen = function () { 1861 | this.open = true; 1862 | }; 1863 | 1864 | /** 1865 | * Called when the transport closes. 1866 | * 1867 | * @api private 1868 | */ 1869 | 1870 | Socket.prototype.onClose = function () { 1871 | this.open = false; 1872 | clearTimeout(this.heartbeatTimeoutTimer); 1873 | }; 1874 | 1875 | /** 1876 | * Called when the transport first opens a connection 1877 | * 1878 | * @param text 1879 | */ 1880 | 1881 | Socket.prototype.onPacket = function (packet) { 1882 | this.of(packet.endpoint).onPacket(packet); 1883 | }; 1884 | 1885 | /** 1886 | * Handles an error. 1887 | * 1888 | * @api private 1889 | */ 1890 | 1891 | Socket.prototype.onError = function (err) { 1892 | if (err && err.advice) { 1893 | if (err.advice === 'reconnect' && (this.connected || this.connecting)) { 1894 | this.disconnect(); 1895 | if (this.options.reconnect) { 1896 | this.reconnect(); 1897 | } 1898 | } 1899 | } 1900 | 1901 | this.publish('error', err && err.reason ? err.reason : err); 1902 | }; 1903 | 1904 | /** 1905 | * Called when the transport disconnects. 1906 | * 1907 | * @api private 1908 | */ 1909 | 1910 | Socket.prototype.onDisconnect = function (reason) { 1911 | var wasConnected = this.connected 1912 | , wasConnecting = this.connecting; 1913 | 1914 | this.connected = false; 1915 | this.connecting = false; 1916 | this.open = false; 1917 | 1918 | if (wasConnected || wasConnecting) { 1919 | this.transport.close(); 1920 | this.transport.clearTimeouts(); 1921 | if (wasConnected) { 1922 | this.publish('disconnect', reason); 1923 | 1924 | if ('booted' != reason && this.options.reconnect && !this.reconnecting) { 1925 | this.reconnect(); 1926 | } 1927 | } 1928 | } 1929 | }; 1930 | 1931 | /** 1932 | * Called upon reconnection. 1933 | * 1934 | * @api private 1935 | */ 1936 | 1937 | Socket.prototype.reconnect = function () { 1938 | this.reconnecting = true; 1939 | this.reconnectionAttempts = 0; 1940 | this.reconnectionDelay = this.options['reconnection delay']; 1941 | 1942 | var self = this 1943 | , maxAttempts = this.options['max reconnection attempts'] 1944 | , tryMultiple = this.options['try multiple transports'] 1945 | , limit = this.options['reconnection limit']; 1946 | 1947 | function reset () { 1948 | if (self.connected) { 1949 | for (var i in self.namespaces) { 1950 | if (self.namespaces.hasOwnProperty(i) && '' !== i) { 1951 | self.namespaces[i].packet({ type: 'connect' }); 1952 | } 1953 | } 1954 | self.publish('reconnect', self.transport.name, self.reconnectionAttempts); 1955 | } 1956 | 1957 | clearTimeout(self.reconnectionTimer); 1958 | 1959 | self.removeListener('connect_failed', maybeReconnect); 1960 | self.removeListener('connect', maybeReconnect); 1961 | 1962 | self.reconnecting = false; 1963 | 1964 | delete self.reconnectionAttempts; 1965 | delete self.reconnectionDelay; 1966 | delete self.reconnectionTimer; 1967 | delete self.redoTransports; 1968 | 1969 | self.options['try multiple transports'] = tryMultiple; 1970 | }; 1971 | 1972 | function maybeReconnect () { 1973 | if (!self.reconnecting) { 1974 | return; 1975 | } 1976 | 1977 | if (self.connected) { 1978 | return reset(); 1979 | }; 1980 | 1981 | if (self.connecting && self.reconnecting) { 1982 | return self.reconnectionTimer = setTimeout(maybeReconnect, 1000); 1983 | } 1984 | 1985 | if (self.reconnectionAttempts++ >= maxAttempts) { 1986 | if (!self.redoTransports) { 1987 | self.on('connect_failed', maybeReconnect); 1988 | self.options['try multiple transports'] = true; 1989 | self.transport = self.getTransport(); 1990 | self.redoTransports = true; 1991 | self.connect(); 1992 | } else { 1993 | self.publish('reconnect_failed'); 1994 | reset(); 1995 | } 1996 | } else { 1997 | if (self.reconnectionDelay < limit) { 1998 | self.reconnectionDelay *= 2; // exponential back off 1999 | } 2000 | 2001 | self.connect(); 2002 | self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts); 2003 | self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay); 2004 | } 2005 | }; 2006 | 2007 | this.options['try multiple transports'] = false; 2008 | this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay); 2009 | 2010 | this.on('connect', maybeReconnect); 2011 | }; 2012 | 2013 | })( 2014 | 'undefined' != typeof io ? io : module.exports 2015 | , 'undefined' != typeof io ? io : module.parent.exports 2016 | , this 2017 | ); 2018 | /** 2019 | * socket.io 2020 | * Copyright(c) 2011 LearnBoost 2021 | * MIT Licensed 2022 | */ 2023 | 2024 | (function (exports, io) { 2025 | 2026 | /** 2027 | * Expose constructor. 2028 | */ 2029 | 2030 | exports.SocketNamespace = SocketNamespace; 2031 | 2032 | /** 2033 | * Socket namespace constructor. 2034 | * 2035 | * @constructor 2036 | * @api public 2037 | */ 2038 | 2039 | function SocketNamespace (socket, name) { 2040 | this.socket = socket; 2041 | this.name = name || ''; 2042 | this.flags = {}; 2043 | this.json = new Flag(this, 'json'); 2044 | this.ackPackets = 0; 2045 | this.acks = {}; 2046 | }; 2047 | 2048 | /** 2049 | * Apply EventEmitter mixin. 2050 | */ 2051 | 2052 | io.util.mixin(SocketNamespace, io.EventEmitter); 2053 | 2054 | /** 2055 | * Copies emit since we override it 2056 | * 2057 | * @api private 2058 | */ 2059 | 2060 | SocketNamespace.prototype.$emit = io.EventEmitter.prototype.emit; 2061 | 2062 | /** 2063 | * Creates a new namespace, by proxying the request to the socket. This 2064 | * allows us to use the synax as we do on the server. 2065 | * 2066 | * @api public 2067 | */ 2068 | 2069 | SocketNamespace.prototype.of = function () { 2070 | return this.socket.of.apply(this.socket, arguments); 2071 | }; 2072 | 2073 | /** 2074 | * Sends a packet. 2075 | * 2076 | * @api private 2077 | */ 2078 | 2079 | SocketNamespace.prototype.packet = function (packet) { 2080 | packet.endpoint = this.name; 2081 | this.socket.packet(packet); 2082 | this.flags = {}; 2083 | return this; 2084 | }; 2085 | 2086 | /** 2087 | * Sends a message 2088 | * 2089 | * @api public 2090 | */ 2091 | 2092 | SocketNamespace.prototype.send = function (data, fn) { 2093 | var packet = { 2094 | type: this.flags.json ? 'json' : 'message' 2095 | , data: data 2096 | }; 2097 | 2098 | if ('function' == typeof fn) { 2099 | packet.id = ++this.ackPackets; 2100 | packet.ack = true; 2101 | this.acks[packet.id] = fn; 2102 | } 2103 | 2104 | return this.packet(packet); 2105 | }; 2106 | 2107 | /** 2108 | * Emits an event 2109 | * 2110 | * @api public 2111 | */ 2112 | 2113 | SocketNamespace.prototype.emit = function (name) { 2114 | var args = Array.prototype.slice.call(arguments, 1) 2115 | , lastArg = args[args.length - 1] 2116 | , packet = { 2117 | type: 'event' 2118 | , name: name 2119 | }; 2120 | 2121 | if ('function' == typeof lastArg) { 2122 | packet.id = ++this.ackPackets; 2123 | packet.ack = 'data'; 2124 | this.acks[packet.id] = lastArg; 2125 | args = args.slice(0, args.length - 1); 2126 | } 2127 | 2128 | packet.args = args; 2129 | 2130 | return this.packet(packet); 2131 | }; 2132 | 2133 | /** 2134 | * Disconnects the namespace 2135 | * 2136 | * @api private 2137 | */ 2138 | 2139 | SocketNamespace.prototype.disconnect = function () { 2140 | if (this.name === '') { 2141 | this.socket.disconnect(); 2142 | } else { 2143 | this.packet({ type: 'disconnect' }); 2144 | this.$emit('disconnect'); 2145 | } 2146 | 2147 | return this; 2148 | }; 2149 | 2150 | /** 2151 | * Handles a packet 2152 | * 2153 | * @api private 2154 | */ 2155 | 2156 | SocketNamespace.prototype.onPacket = function (packet) { 2157 | var self = this; 2158 | 2159 | function ack () { 2160 | self.packet({ 2161 | type: 'ack' 2162 | , args: io.util.toArray(arguments) 2163 | , ackId: packet.id 2164 | }); 2165 | }; 2166 | 2167 | switch (packet.type) { 2168 | case 'connect': 2169 | this.$emit('connect'); 2170 | break; 2171 | 2172 | case 'disconnect': 2173 | if (this.name === '') { 2174 | this.socket.onDisconnect(packet.reason || 'booted'); 2175 | } else { 2176 | this.$emit('disconnect', packet.reason); 2177 | } 2178 | break; 2179 | 2180 | case 'message': 2181 | case 'json': 2182 | var params = ['message', packet.data]; 2183 | 2184 | if (packet.ack == 'data') { 2185 | params.push(ack); 2186 | } else if (packet.ack) { 2187 | this.packet({ type: 'ack', ackId: packet.id }); 2188 | } 2189 | 2190 | this.$emit.apply(this, params); 2191 | break; 2192 | 2193 | case 'event': 2194 | var params = [packet.name].concat(packet.args); 2195 | 2196 | if (packet.ack == 'data') 2197 | params.push(ack); 2198 | 2199 | this.$emit.apply(this, params); 2200 | break; 2201 | 2202 | case 'ack': 2203 | if (this.acks[packet.ackId]) { 2204 | this.acks[packet.ackId].apply(this, packet.args); 2205 | delete this.acks[packet.ackId]; 2206 | } 2207 | break; 2208 | 2209 | case 'error': 2210 | if (packet.advice){ 2211 | this.socket.onError(packet); 2212 | } else { 2213 | if (packet.reason == 'unauthorized') { 2214 | this.$emit('connect_failed', packet.reason); 2215 | } else { 2216 | this.$emit('error', packet.reason); 2217 | } 2218 | } 2219 | break; 2220 | } 2221 | }; 2222 | 2223 | /** 2224 | * Flag interface. 2225 | * 2226 | * @api private 2227 | */ 2228 | 2229 | function Flag (nsp, name) { 2230 | this.namespace = nsp; 2231 | this.name = name; 2232 | }; 2233 | 2234 | /** 2235 | * Send a message 2236 | * 2237 | * @api public 2238 | */ 2239 | 2240 | Flag.prototype.send = function () { 2241 | this.namespace.flags[this.name] = true; 2242 | this.namespace.send.apply(this.namespace, arguments); 2243 | }; 2244 | 2245 | /** 2246 | * Emit an event 2247 | * 2248 | * @api public 2249 | */ 2250 | 2251 | Flag.prototype.emit = function () { 2252 | this.namespace.flags[this.name] = true; 2253 | this.namespace.emit.apply(this.namespace, arguments); 2254 | }; 2255 | 2256 | })( 2257 | 'undefined' != typeof io ? io : module.exports 2258 | , 'undefined' != typeof io ? io : module.parent.exports 2259 | ); 2260 | 2261 | /** 2262 | * socket.io 2263 | * Copyright(c) 2011 LearnBoost 2264 | * MIT Licensed 2265 | */ 2266 | 2267 | (function (exports, io, global) { 2268 | 2269 | /** 2270 | * Expose constructor. 2271 | */ 2272 | 2273 | exports.websocket = WS; 2274 | 2275 | /** 2276 | * The WebSocket transport uses the HTML5 WebSocket API to establish an 2277 | * persistent connection with the Socket.IO server. This transport will also 2278 | * be inherited by the FlashSocket fallback as it provides a API compatible 2279 | * polyfill for the WebSockets. 2280 | * 2281 | * @constructor 2282 | * @extends {io.Transport} 2283 | * @api public 2284 | */ 2285 | 2286 | function WS (socket) { 2287 | io.Transport.apply(this, arguments); 2288 | }; 2289 | 2290 | /** 2291 | * Inherits from Transport. 2292 | */ 2293 | 2294 | io.util.inherit(WS, io.Transport); 2295 | 2296 | /** 2297 | * Transport name 2298 | * 2299 | * @api public 2300 | */ 2301 | 2302 | WS.prototype.name = 'websocket'; 2303 | 2304 | /** 2305 | * Initializes a new `WebSocket` connection with the Socket.IO server. We attach 2306 | * all the appropriate listeners to handle the responses from the server. 2307 | * 2308 | * @returns {Transport} 2309 | * @api public 2310 | */ 2311 | 2312 | WS.prototype.open = function () { 2313 | var query = io.util.query(this.socket.options.query) 2314 | , self = this 2315 | , Socket 2316 | 2317 | 2318 | if (!Socket) { 2319 | Socket = global.MozWebSocket || global.WebSocket; 2320 | } 2321 | 2322 | this.websocket = new Socket(this.prepareUrl() + query); 2323 | 2324 | this.websocket.onopen = function () { 2325 | self.onOpen(); 2326 | self.socket.setBuffer(false); 2327 | }; 2328 | this.websocket.onmessage = function (ev) { 2329 | self.onData(ev.data); 2330 | }; 2331 | this.websocket.onclose = function () { 2332 | self.onClose(); 2333 | self.socket.setBuffer(true); 2334 | }; 2335 | this.websocket.onerror = function (e) { 2336 | self.onError(e); 2337 | }; 2338 | 2339 | return this; 2340 | }; 2341 | 2342 | /** 2343 | * Send a message to the Socket.IO server. The message will automatically be 2344 | * encoded in the correct message format. 2345 | * 2346 | * @returns {Transport} 2347 | * @api public 2348 | */ 2349 | 2350 | WS.prototype.send = function (data) { 2351 | this.websocket.send(data); 2352 | return this; 2353 | }; 2354 | 2355 | /** 2356 | * Payload 2357 | * 2358 | * @api private 2359 | */ 2360 | 2361 | WS.prototype.payload = function (arr) { 2362 | for (var i = 0, l = arr.length; i < l; i++) { 2363 | this.packet(arr[i]); 2364 | } 2365 | return this; 2366 | }; 2367 | 2368 | /** 2369 | * Disconnect the established `WebSocket` connection. 2370 | * 2371 | * @returns {Transport} 2372 | * @api public 2373 | */ 2374 | 2375 | WS.prototype.close = function () { 2376 | this.websocket.close(); 2377 | return this; 2378 | }; 2379 | 2380 | /** 2381 | * Handle the errors that `WebSocket` might be giving when we 2382 | * are attempting to connect or send messages. 2383 | * 2384 | * @param {Error} e The error. 2385 | * @api private 2386 | */ 2387 | 2388 | WS.prototype.onError = function (e) { 2389 | this.socket.onError(e); 2390 | }; 2391 | 2392 | /** 2393 | * Returns the appropriate scheme for the URI generation. 2394 | * 2395 | * @api private 2396 | */ 2397 | WS.prototype.scheme = function () { 2398 | return this.socket.options.secure ? 'wss' : 'ws'; 2399 | }; 2400 | 2401 | /** 2402 | * Checks if the browser has support for native `WebSockets` and that 2403 | * it's not the polyfill created for the FlashSocket transport. 2404 | * 2405 | * @return {Boolean} 2406 | * @api public 2407 | */ 2408 | 2409 | WS.check = function () { 2410 | return ('WebSocket' in global && !('__addTask' in WebSocket)) 2411 | || 'MozWebSocket' in global; 2412 | }; 2413 | 2414 | /** 2415 | * Check if the `WebSocket` transport support cross domain communications. 2416 | * 2417 | * @returns {Boolean} 2418 | * @api public 2419 | */ 2420 | 2421 | WS.xdomainCheck = function () { 2422 | return true; 2423 | }; 2424 | 2425 | /** 2426 | * Add the transport to your public io.transports array. 2427 | * 2428 | * @api private 2429 | */ 2430 | 2431 | io.transports.push('websocket'); 2432 | 2433 | })( 2434 | 'undefined' != typeof io ? io.Transport : module.exports 2435 | , 'undefined' != typeof io ? io : module.parent.exports 2436 | , this 2437 | ); 2438 | 2439 | /** 2440 | * socket.io 2441 | * Copyright(c) 2011 LearnBoost 2442 | * MIT Licensed 2443 | */ 2444 | 2445 | (function (exports, io) { 2446 | 2447 | /** 2448 | * Expose constructor. 2449 | */ 2450 | 2451 | exports.flashsocket = Flashsocket; 2452 | 2453 | /** 2454 | * The FlashSocket transport. This is a API wrapper for the HTML5 WebSocket 2455 | * specification. It uses a .swf file to communicate with the server. If you want 2456 | * to serve the .swf file from a other server than where the Socket.IO script is 2457 | * coming from you need to use the insecure version of the .swf. More information 2458 | * about this can be found on the github page. 2459 | * 2460 | * @constructor 2461 | * @extends {io.Transport.websocket} 2462 | * @api public 2463 | */ 2464 | 2465 | function Flashsocket () { 2466 | io.Transport.websocket.apply(this, arguments); 2467 | }; 2468 | 2469 | /** 2470 | * Inherits from Transport. 2471 | */ 2472 | 2473 | io.util.inherit(Flashsocket, io.Transport.websocket); 2474 | 2475 | /** 2476 | * Transport name 2477 | * 2478 | * @api public 2479 | */ 2480 | 2481 | Flashsocket.prototype.name = 'flashsocket'; 2482 | 2483 | /** 2484 | * Disconnect the established `FlashSocket` connection. This is done by adding a 2485 | * new task to the FlashSocket. The rest will be handled off by the `WebSocket` 2486 | * transport. 2487 | * 2488 | * @returns {Transport} 2489 | * @api public 2490 | */ 2491 | 2492 | Flashsocket.prototype.open = function () { 2493 | var self = this 2494 | , args = arguments; 2495 | 2496 | WebSocket.__addTask(function () { 2497 | io.Transport.websocket.prototype.open.apply(self, args); 2498 | }); 2499 | return this; 2500 | }; 2501 | 2502 | /** 2503 | * Sends a message to the Socket.IO server. This is done by adding a new 2504 | * task to the FlashSocket. The rest will be handled off by the `WebSocket` 2505 | * transport. 2506 | * 2507 | * @returns {Transport} 2508 | * @api public 2509 | */ 2510 | 2511 | Flashsocket.prototype.send = function () { 2512 | var self = this, args = arguments; 2513 | WebSocket.__addTask(function () { 2514 | io.Transport.websocket.prototype.send.apply(self, args); 2515 | }); 2516 | return this; 2517 | }; 2518 | 2519 | /** 2520 | * Disconnects the established `FlashSocket` connection. 2521 | * 2522 | * @returns {Transport} 2523 | * @api public 2524 | */ 2525 | 2526 | Flashsocket.prototype.close = function () { 2527 | WebSocket.__tasks.length = 0; 2528 | io.Transport.websocket.prototype.close.call(this); 2529 | return this; 2530 | }; 2531 | 2532 | /** 2533 | * The WebSocket fall back needs to append the flash container to the body 2534 | * element, so we need to make sure we have access to it. Or defer the call 2535 | * until we are sure there is a body element. 2536 | * 2537 | * @param {Socket} socket The socket instance that needs a transport 2538 | * @param {Function} fn The callback 2539 | * @api private 2540 | */ 2541 | 2542 | Flashsocket.prototype.ready = function (socket, fn) { 2543 | function init () { 2544 | var options = socket.options 2545 | , port = options['flash policy port'] 2546 | , path = [ 2547 | 'http' + (options.secure ? 's' : '') + ':/' 2548 | , options.host + ':' + options.port 2549 | , options.resource 2550 | , 'static/flashsocket' 2551 | , 'WebSocketMain' + (socket.isXDomain() ? 'Insecure' : '') + '.swf' 2552 | ]; 2553 | 2554 | // Only start downloading the swf file when the checked that this browser 2555 | // actually supports it 2556 | if (!Flashsocket.loaded) { 2557 | if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined') { 2558 | // Set the correct file based on the XDomain settings 2559 | WEB_SOCKET_SWF_LOCATION = path.join('/'); 2560 | } 2561 | 2562 | if (port !== 843) { 2563 | WebSocket.loadFlashPolicyFile('xmlsocket://' + options.host + ':' + port); 2564 | } 2565 | 2566 | WebSocket.__initialize(); 2567 | Flashsocket.loaded = true; 2568 | } 2569 | 2570 | fn.call(self); 2571 | } 2572 | 2573 | var self = this; 2574 | if (document.body) return init(); 2575 | 2576 | io.util.load(init); 2577 | }; 2578 | 2579 | /** 2580 | * Check if the FlashSocket transport is supported as it requires that the Adobe 2581 | * Flash Player plug-in version `10.0.0` or greater is installed. And also check if 2582 | * the polyfill is correctly loaded. 2583 | * 2584 | * @returns {Boolean} 2585 | * @api public 2586 | */ 2587 | 2588 | Flashsocket.check = function () { 2589 | if ( 2590 | typeof WebSocket == 'undefined' 2591 | || !('__initialize' in WebSocket) || !swfobject 2592 | ) return false; 2593 | 2594 | return swfobject.getFlashPlayerVersion().major >= 10; 2595 | }; 2596 | 2597 | /** 2598 | * Check if the FlashSocket transport can be used as cross domain / cross origin 2599 | * transport. Because we can't see which type (secure or insecure) of .swf is used 2600 | * we will just return true. 2601 | * 2602 | * @returns {Boolean} 2603 | * @api public 2604 | */ 2605 | 2606 | Flashsocket.xdomainCheck = function () { 2607 | return true; 2608 | }; 2609 | 2610 | /** 2611 | * Disable AUTO_INITIALIZATION 2612 | */ 2613 | 2614 | if (typeof window != 'undefined') { 2615 | WEB_SOCKET_DISABLE_AUTO_INITIALIZATION = true; 2616 | } 2617 | 2618 | /** 2619 | * Add the transport to your public io.transports array. 2620 | * 2621 | * @api private 2622 | */ 2623 | 2624 | io.transports.push('flashsocket'); 2625 | })( 2626 | 'undefined' != typeof io ? io.Transport : module.exports 2627 | , 'undefined' != typeof io ? io : module.parent.exports 2628 | ); 2629 | /* SWFObject v2.2 2630 | is released under the MIT License 2631 | */ 2632 | if ('undefined' != typeof window) { 2633 | var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O[(['Active'].concat('Object').join('X'))]!=D){try{var ad=new window[(['Active'].concat('Object').join('X'))](W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab 2636 | // License: New BSD License 2637 | // Reference: http://dev.w3.org/html5/websockets/ 2638 | // Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol 2639 | 2640 | (function() { 2641 | 2642 | if ('undefined' == typeof window || window.WebSocket) return; 2643 | 2644 | var console = window.console; 2645 | if (!console || !console.log || !console.error) { 2646 | console = {log: function(){ }, error: function(){ }}; 2647 | } 2648 | 2649 | if (!swfobject.hasFlashPlayerVersion("10.0.0")) { 2650 | console.error("Flash Player >= 10.0.0 is required."); 2651 | return; 2652 | } 2653 | if (location.protocol == "file:") { 2654 | console.error( 2655 | "WARNING: web-socket-js doesn't work in file:///... URL " + 2656 | "unless you set Flash Security Settings properly. " + 2657 | "Open the page via Web server i.e. http://..."); 2658 | } 2659 | 2660 | /** 2661 | * This class represents a faux web socket. 2662 | * @param {string} url 2663 | * @param {array or string} protocols 2664 | * @param {string} proxyHost 2665 | * @param {int} proxyPort 2666 | * @param {string} headers 2667 | */ 2668 | WebSocket = function(url, protocols, proxyHost, proxyPort, headers) { 2669 | var self = this; 2670 | self.__id = WebSocket.__nextId++; 2671 | WebSocket.__instances[self.__id] = self; 2672 | self.readyState = WebSocket.CONNECTING; 2673 | self.bufferedAmount = 0; 2674 | self.__events = {}; 2675 | if (!protocols) { 2676 | protocols = []; 2677 | } else if (typeof protocols == "string") { 2678 | protocols = [protocols]; 2679 | } 2680 | // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. 2681 | // Otherwise, when onopen fires immediately, onopen is called before it is set. 2682 | setTimeout(function() { 2683 | WebSocket.__addTask(function() { 2684 | WebSocket.__flash.create( 2685 | self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null); 2686 | }); 2687 | }, 0); 2688 | }; 2689 | 2690 | /** 2691 | * Send data to the web socket. 2692 | * @param {string} data The data to send to the socket. 2693 | * @return {boolean} True for success, false for failure. 2694 | */ 2695 | WebSocket.prototype.send = function(data) { 2696 | if (this.readyState == WebSocket.CONNECTING) { 2697 | throw "INVALID_STATE_ERR: Web Socket connection has not been established"; 2698 | } 2699 | // We use encodeURIComponent() here, because FABridge doesn't work if 2700 | // the argument includes some characters. We don't use escape() here 2701 | // because of this: 2702 | // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions 2703 | // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't 2704 | // preserve all Unicode characters either e.g. "\uffff" in Firefox. 2705 | // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require 2706 | // additional testing. 2707 | var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data)); 2708 | if (result < 0) { // success 2709 | return true; 2710 | } else { 2711 | this.bufferedAmount += result; 2712 | return false; 2713 | } 2714 | }; 2715 | 2716 | /** 2717 | * Close this web socket gracefully. 2718 | */ 2719 | WebSocket.prototype.close = function() { 2720 | if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { 2721 | return; 2722 | } 2723 | this.readyState = WebSocket.CLOSING; 2724 | WebSocket.__flash.close(this.__id); 2725 | }; 2726 | 2727 | /** 2728 | * Implementation of {@link DOM 2 EventTarget Interface} 2729 | * 2730 | * @param {string} type 2731 | * @param {function} listener 2732 | * @param {boolean} useCapture 2733 | * @return void 2734 | */ 2735 | WebSocket.prototype.addEventListener = function(type, listener, useCapture) { 2736 | if (!(type in this.__events)) { 2737 | this.__events[type] = []; 2738 | } 2739 | this.__events[type].push(listener); 2740 | }; 2741 | 2742 | /** 2743 | * Implementation of {@link DOM 2 EventTarget Interface} 2744 | * 2745 | * @param {string} type 2746 | * @param {function} listener 2747 | * @param {boolean} useCapture 2748 | * @return void 2749 | */ 2750 | WebSocket.prototype.removeEventListener = function(type, listener, useCapture) { 2751 | if (!(type in this.__events)) return; 2752 | var events = this.__events[type]; 2753 | for (var i = events.length - 1; i >= 0; --i) { 2754 | if (events[i] === listener) { 2755 | events.splice(i, 1); 2756 | break; 2757 | } 2758 | } 2759 | }; 2760 | 2761 | /** 2762 | * Implementation of {@link DOM 2 EventTarget Interface} 2763 | * 2764 | * @param {Event} event 2765 | * @return void 2766 | */ 2767 | WebSocket.prototype.dispatchEvent = function(event) { 2768 | var events = this.__events[event.type] || []; 2769 | for (var i = 0; i < events.length; ++i) { 2770 | events[i](event); 2771 | } 2772 | var handler = this["on" + event.type]; 2773 | if (handler) handler(event); 2774 | }; 2775 | 2776 | /** 2777 | * Handles an event from Flash. 2778 | * @param {Object} flashEvent 2779 | */ 2780 | WebSocket.prototype.__handleEvent = function(flashEvent) { 2781 | if ("readyState" in flashEvent) { 2782 | this.readyState = flashEvent.readyState; 2783 | } 2784 | if ("protocol" in flashEvent) { 2785 | this.protocol = flashEvent.protocol; 2786 | } 2787 | 2788 | var jsEvent; 2789 | if (flashEvent.type == "open" || flashEvent.type == "error") { 2790 | jsEvent = this.__createSimpleEvent(flashEvent.type); 2791 | } else if (flashEvent.type == "close") { 2792 | // TODO implement jsEvent.wasClean 2793 | jsEvent = this.__createSimpleEvent("close"); 2794 | } else if (flashEvent.type == "message") { 2795 | var data = decodeURIComponent(flashEvent.message); 2796 | jsEvent = this.__createMessageEvent("message", data); 2797 | } else { 2798 | throw "unknown event type: " + flashEvent.type; 2799 | } 2800 | 2801 | this.dispatchEvent(jsEvent); 2802 | }; 2803 | 2804 | WebSocket.prototype.__createSimpleEvent = function(type) { 2805 | if (document.createEvent && window.Event) { 2806 | var event = document.createEvent("Event"); 2807 | event.initEvent(type, false, false); 2808 | return event; 2809 | } else { 2810 | return {type: type, bubbles: false, cancelable: false}; 2811 | } 2812 | }; 2813 | 2814 | WebSocket.prototype.__createMessageEvent = function(type, data) { 2815 | if (document.createEvent && window.MessageEvent && !window.opera) { 2816 | var event = document.createEvent("MessageEvent"); 2817 | event.initMessageEvent("message", false, false, data, null, null, window, null); 2818 | return event; 2819 | } else { 2820 | // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. 2821 | return {type: type, data: data, bubbles: false, cancelable: false}; 2822 | } 2823 | }; 2824 | 2825 | /** 2826 | * Define the WebSocket readyState enumeration. 2827 | */ 2828 | WebSocket.CONNECTING = 0; 2829 | WebSocket.OPEN = 1; 2830 | WebSocket.CLOSING = 2; 2831 | WebSocket.CLOSED = 3; 2832 | 2833 | WebSocket.__flash = null; 2834 | WebSocket.__instances = {}; 2835 | WebSocket.__tasks = []; 2836 | WebSocket.__nextId = 0; 2837 | 2838 | /** 2839 | * Load a new flash security policy file. 2840 | * @param {string} url 2841 | */ 2842 | WebSocket.loadFlashPolicyFile = function(url){ 2843 | WebSocket.__addTask(function() { 2844 | WebSocket.__flash.loadManualPolicyFile(url); 2845 | }); 2846 | }; 2847 | 2848 | /** 2849 | * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. 2850 | */ 2851 | WebSocket.__initialize = function() { 2852 | if (WebSocket.__flash) return; 2853 | 2854 | if (WebSocket.__swfLocation) { 2855 | // For backword compatibility. 2856 | window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; 2857 | } 2858 | if (!window.WEB_SOCKET_SWF_LOCATION) { 2859 | console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); 2860 | return; 2861 | } 2862 | var container = document.createElement("div"); 2863 | container.id = "webSocketContainer"; 2864 | // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents 2865 | // Flash from loading at least in IE. So we move it out of the screen at (-100, -100). 2866 | // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash 2867 | // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is 2868 | // the best we can do as far as we know now. 2869 | container.style.position = "absolute"; 2870 | if (WebSocket.__isFlashLite()) { 2871 | container.style.left = "0px"; 2872 | container.style.top = "0px"; 2873 | } else { 2874 | container.style.left = "-100px"; 2875 | container.style.top = "-100px"; 2876 | } 2877 | var holder = document.createElement("div"); 2878 | holder.id = "webSocketFlash"; 2879 | container.appendChild(holder); 2880 | document.body.appendChild(container); 2881 | // See this article for hasPriority: 2882 | // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html 2883 | swfobject.embedSWF( 2884 | WEB_SOCKET_SWF_LOCATION, 2885 | "webSocketFlash", 2886 | "1" /* width */, 2887 | "1" /* height */, 2888 | "10.0.0" /* SWF version */, 2889 | null, 2890 | null, 2891 | {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"}, 2892 | null, 2893 | function(e) { 2894 | if (!e.success) { 2895 | console.error("[WebSocket] swfobject.embedSWF failed"); 2896 | } 2897 | }); 2898 | }; 2899 | 2900 | /** 2901 | * Called by Flash to notify JS that it's fully loaded and ready 2902 | * for communication. 2903 | */ 2904 | WebSocket.__onFlashInitialized = function() { 2905 | // We need to set a timeout here to avoid round-trip calls 2906 | // to flash during the initialization process. 2907 | setTimeout(function() { 2908 | WebSocket.__flash = document.getElementById("webSocketFlash"); 2909 | WebSocket.__flash.setCallerUrl(location.href); 2910 | WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG); 2911 | for (var i = 0; i < WebSocket.__tasks.length; ++i) { 2912 | WebSocket.__tasks[i](); 2913 | } 2914 | WebSocket.__tasks = []; 2915 | }, 0); 2916 | }; 2917 | 2918 | /** 2919 | * Called by Flash to notify WebSockets events are fired. 2920 | */ 2921 | WebSocket.__onFlashEvent = function() { 2922 | setTimeout(function() { 2923 | try { 2924 | // Gets events using receiveEvents() instead of getting it from event object 2925 | // of Flash event. This is to make sure to keep message order. 2926 | // It seems sometimes Flash events don't arrive in the same order as they are sent. 2927 | var events = WebSocket.__flash.receiveEvents(); 2928 | for (var i = 0; i < events.length; ++i) { 2929 | WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); 2930 | } 2931 | } catch (e) { 2932 | console.error(e); 2933 | } 2934 | }, 0); 2935 | return true; 2936 | }; 2937 | 2938 | // Called by Flash. 2939 | WebSocket.__log = function(message) { 2940 | console.log(decodeURIComponent(message)); 2941 | }; 2942 | 2943 | // Called by Flash. 2944 | WebSocket.__error = function(message) { 2945 | console.error(decodeURIComponent(message)); 2946 | }; 2947 | 2948 | WebSocket.__addTask = function(task) { 2949 | if (WebSocket.__flash) { 2950 | task(); 2951 | } else { 2952 | WebSocket.__tasks.push(task); 2953 | } 2954 | }; 2955 | 2956 | /** 2957 | * Test if the browser is running flash lite. 2958 | * @return {boolean} True if flash lite is running, false otherwise. 2959 | */ 2960 | WebSocket.__isFlashLite = function() { 2961 | if (!window.navigator || !window.navigator.mimeTypes) { 2962 | return false; 2963 | } 2964 | var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"]; 2965 | if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) { 2966 | return false; 2967 | } 2968 | return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false; 2969 | }; 2970 | 2971 | if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { 2972 | if (window.addEventListener) { 2973 | window.addEventListener("load", function(){ 2974 | WebSocket.__initialize(); 2975 | }, false); 2976 | } else { 2977 | window.attachEvent("onload", function(){ 2978 | WebSocket.__initialize(); 2979 | }); 2980 | } 2981 | } 2982 | 2983 | })(); 2984 | 2985 | /** 2986 | * socket.io 2987 | * Copyright(c) 2011 LearnBoost 2988 | * MIT Licensed 2989 | */ 2990 | 2991 | (function (exports, io, global) { 2992 | 2993 | /** 2994 | * Expose constructor. 2995 | * 2996 | * @api public 2997 | */ 2998 | 2999 | exports.XHR = XHR; 3000 | 3001 | /** 3002 | * XHR constructor 3003 | * 3004 | * @costructor 3005 | * @api public 3006 | */ 3007 | 3008 | function XHR (socket) { 3009 | if (!socket) return; 3010 | 3011 | io.Transport.apply(this, arguments); 3012 | this.sendBuffer = []; 3013 | }; 3014 | 3015 | /** 3016 | * Inherits from Transport. 3017 | */ 3018 | 3019 | io.util.inherit(XHR, io.Transport); 3020 | 3021 | /** 3022 | * Establish a connection 3023 | * 3024 | * @returns {Transport} 3025 | * @api public 3026 | */ 3027 | 3028 | XHR.prototype.open = function () { 3029 | this.socket.setBuffer(false); 3030 | this.onOpen(); 3031 | this.get(); 3032 | 3033 | // we need to make sure the request succeeds since we have no indication 3034 | // whether the request opened or not until it succeeded. 3035 | this.setCloseTimeout(); 3036 | 3037 | return this; 3038 | }; 3039 | 3040 | /** 3041 | * Check if we need to send data to the Socket.IO server, if we have data in our 3042 | * buffer we encode it and forward it to the `post` method. 3043 | * 3044 | * @api private 3045 | */ 3046 | 3047 | XHR.prototype.payload = function (payload) { 3048 | var msgs = []; 3049 | 3050 | for (var i = 0, l = payload.length; i < l; i++) { 3051 | msgs.push(io.parser.encodePacket(payload[i])); 3052 | } 3053 | 3054 | this.send(io.parser.encodePayload(msgs)); 3055 | }; 3056 | 3057 | /** 3058 | * Send data to the Socket.IO server. 3059 | * 3060 | * @param data The message 3061 | * @returns {Transport} 3062 | * @api public 3063 | */ 3064 | 3065 | XHR.prototype.send = function (data) { 3066 | this.post(data); 3067 | return this; 3068 | }; 3069 | 3070 | /** 3071 | * Posts a encoded message to the Socket.IO server. 3072 | * 3073 | * @param {String} data A encoded message. 3074 | * @api private 3075 | */ 3076 | 3077 | function empty () { }; 3078 | 3079 | XHR.prototype.post = function (data) { 3080 | var self = this; 3081 | this.socket.setBuffer(true); 3082 | 3083 | function stateChange () { 3084 | if (this.readyState == 4) { 3085 | this.onreadystatechange = empty; 3086 | self.posting = false; 3087 | 3088 | if (this.status == 200){ 3089 | self.socket.setBuffer(false); 3090 | } else { 3091 | self.onClose(); 3092 | } 3093 | } 3094 | } 3095 | 3096 | function onload () { 3097 | this.onload = empty; 3098 | self.socket.setBuffer(false); 3099 | }; 3100 | 3101 | this.sendXHR = this.request('POST'); 3102 | 3103 | if (global.XDomainRequest && this.sendXHR instanceof XDomainRequest) { 3104 | this.sendXHR.onload = this.sendXHR.onerror = onload; 3105 | } else { 3106 | this.sendXHR.onreadystatechange = stateChange; 3107 | } 3108 | 3109 | this.sendXHR.send(data); 3110 | }; 3111 | 3112 | /** 3113 | * Disconnects the established `XHR` connection. 3114 | * 3115 | * @returns {Transport} 3116 | * @api public 3117 | */ 3118 | 3119 | XHR.prototype.close = function () { 3120 | this.onClose(); 3121 | return this; 3122 | }; 3123 | 3124 | /** 3125 | * Generates a configured XHR request 3126 | * 3127 | * @param {String} url The url that needs to be requested. 3128 | * @param {String} method The method the request should use. 3129 | * @returns {XMLHttpRequest} 3130 | * @api private 3131 | */ 3132 | 3133 | XHR.prototype.request = function (method) { 3134 | var req = io.util.request(this.socket.isXDomain()) 3135 | , query = io.util.query(this.socket.options.query, 't=' + +new Date); 3136 | 3137 | req.open(method || 'GET', this.prepareUrl() + query, true); 3138 | 3139 | if (method == 'POST') { 3140 | try { 3141 | if (req.setRequestHeader) { 3142 | req.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); 3143 | } else { 3144 | // XDomainRequest 3145 | req.contentType = 'text/plain'; 3146 | } 3147 | } catch (e) {} 3148 | } 3149 | 3150 | return req; 3151 | }; 3152 | 3153 | /** 3154 | * Returns the scheme to use for the transport URLs. 3155 | * 3156 | * @api private 3157 | */ 3158 | 3159 | XHR.prototype.scheme = function () { 3160 | return this.socket.options.secure ? 'https' : 'http'; 3161 | }; 3162 | 3163 | /** 3164 | * Check if the XHR transports are supported 3165 | * 3166 | * @param {Boolean} xdomain Check if we support cross domain requests. 3167 | * @returns {Boolean} 3168 | * @api public 3169 | */ 3170 | 3171 | XHR.check = function (socket, xdomain) { 3172 | try { 3173 | var request = io.util.request(xdomain), 3174 | usesXDomReq = (global.XDomainRequest && request instanceof XDomainRequest), 3175 | socketProtocol = (socket && socket.options && socket.options.secure ? 'https:' : 'http:'), 3176 | isXProtocol = (socketProtocol != global.location.protocol); 3177 | if (request && !(usesXDomReq && isXProtocol)) { 3178 | return true; 3179 | } 3180 | } catch(e) {} 3181 | 3182 | return false; 3183 | }; 3184 | 3185 | /** 3186 | * Check if the XHR transport supports cross domain requests. 3187 | * 3188 | * @returns {Boolean} 3189 | * @api public 3190 | */ 3191 | 3192 | XHR.xdomainCheck = function () { 3193 | return XHR.check(null, true); 3194 | }; 3195 | 3196 | })( 3197 | 'undefined' != typeof io ? io.Transport : module.exports 3198 | , 'undefined' != typeof io ? io : module.parent.exports 3199 | , this 3200 | ); 3201 | /** 3202 | * socket.io 3203 | * Copyright(c) 2011 LearnBoost 3204 | * MIT Licensed 3205 | */ 3206 | 3207 | (function (exports, io) { 3208 | 3209 | /** 3210 | * Expose constructor. 3211 | */ 3212 | 3213 | exports.htmlfile = HTMLFile; 3214 | 3215 | /** 3216 | * The HTMLFile transport creates a `forever iframe` based transport 3217 | * for Internet Explorer. Regular forever iframe implementations will 3218 | * continuously trigger the browsers buzy indicators. If the forever iframe 3219 | * is created inside a `htmlfile` these indicators will not be trigged. 3220 | * 3221 | * @constructor 3222 | * @extends {io.Transport.XHR} 3223 | * @api public 3224 | */ 3225 | 3226 | function HTMLFile (socket) { 3227 | io.Transport.XHR.apply(this, arguments); 3228 | }; 3229 | 3230 | /** 3231 | * Inherits from XHR transport. 3232 | */ 3233 | 3234 | io.util.inherit(HTMLFile, io.Transport.XHR); 3235 | 3236 | /** 3237 | * Transport name 3238 | * 3239 | * @api public 3240 | */ 3241 | 3242 | HTMLFile.prototype.name = 'htmlfile'; 3243 | 3244 | /** 3245 | * Creates a new Ac...eX `htmlfile` with a forever loading iframe 3246 | * that can be used to listen to messages. Inside the generated 3247 | * `htmlfile` a reference will be made to the HTMLFile transport. 3248 | * 3249 | * @api private 3250 | */ 3251 | 3252 | HTMLFile.prototype.get = function () { 3253 | this.doc = new window[(['Active'].concat('Object').join('X'))]('htmlfile'); 3254 | this.doc.open(); 3255 | this.doc.write(''); 3256 | this.doc.close(); 3257 | this.doc.parentWindow.s = this; 3258 | 3259 | var iframeC = this.doc.createElement('div'); 3260 | iframeC.className = 'socketio'; 3261 | 3262 | this.doc.body.appendChild(iframeC); 3263 | this.iframe = this.doc.createElement('iframe'); 3264 | 3265 | iframeC.appendChild(this.iframe); 3266 | 3267 | var self = this 3268 | , query = io.util.query(this.socket.options.query, 't='+ +new Date); 3269 | 3270 | this.iframe.src = this.prepareUrl() + query; 3271 | 3272 | io.util.on(window, 'unload', function () { 3273 | self.destroy(); 3274 | }); 3275 | }; 3276 | 3277 | /** 3278 | * The Socket.IO server will write script tags inside the forever 3279 | * iframe, this function will be used as callback for the incoming 3280 | * information. 3281 | * 3282 | * @param {String} data The message 3283 | * @param {document} doc Reference to the context 3284 | * @api private 3285 | */ 3286 | 3287 | HTMLFile.prototype._ = function (data, doc) { 3288 | this.onData(data); 3289 | try { 3290 | var script = doc.getElementsByTagName('script')[0]; 3291 | script.parentNode.removeChild(script); 3292 | } catch (e) { } 3293 | }; 3294 | 3295 | /** 3296 | * Destroy the established connection, iframe and `htmlfile`. 3297 | * And calls the `CollectGarbage` function of Internet Explorer 3298 | * to release the memory. 3299 | * 3300 | * @api private 3301 | */ 3302 | 3303 | HTMLFile.prototype.destroy = function () { 3304 | if (this.iframe){ 3305 | try { 3306 | this.iframe.src = 'about:blank'; 3307 | } catch(e){} 3308 | 3309 | this.doc = null; 3310 | this.iframe.parentNode.removeChild(this.iframe); 3311 | this.iframe = null; 3312 | 3313 | CollectGarbage(); 3314 | } 3315 | }; 3316 | 3317 | /** 3318 | * Disconnects the established connection. 3319 | * 3320 | * @returns {Transport} Chaining. 3321 | * @api public 3322 | */ 3323 | 3324 | HTMLFile.prototype.close = function () { 3325 | this.destroy(); 3326 | return io.Transport.XHR.prototype.close.call(this); 3327 | }; 3328 | 3329 | /** 3330 | * Checks if the browser supports this transport. The browser 3331 | * must have an `Ac...eXObject` implementation. 3332 | * 3333 | * @return {Boolean} 3334 | * @api public 3335 | */ 3336 | 3337 | HTMLFile.check = function () { 3338 | if (typeof window != "undefined" && (['Active'].concat('Object').join('X')) in window){ 3339 | try { 3340 | var a = new window[(['Active'].concat('Object').join('X'))]('htmlfile'); 3341 | return a && io.Transport.XHR.check(); 3342 | } catch(e){} 3343 | } 3344 | return false; 3345 | }; 3346 | 3347 | /** 3348 | * Check if cross domain requests are supported. 3349 | * 3350 | * @returns {Boolean} 3351 | * @api public 3352 | */ 3353 | 3354 | HTMLFile.xdomainCheck = function () { 3355 | // we can probably do handling for sub-domains, we should 3356 | // test that it's cross domain but a subdomain here 3357 | return false; 3358 | }; 3359 | 3360 | /** 3361 | * Add the transport to your public io.transports array. 3362 | * 3363 | * @api private 3364 | */ 3365 | 3366 | io.transports.push('htmlfile'); 3367 | 3368 | })( 3369 | 'undefined' != typeof io ? io.Transport : module.exports 3370 | , 'undefined' != typeof io ? io : module.parent.exports 3371 | ); 3372 | 3373 | /** 3374 | * socket.io 3375 | * Copyright(c) 2011 LearnBoost 3376 | * MIT Licensed 3377 | */ 3378 | 3379 | (function (exports, io, global) { 3380 | 3381 | /** 3382 | * Expose constructor. 3383 | */ 3384 | 3385 | exports['xhr-polling'] = XHRPolling; 3386 | 3387 | /** 3388 | * The XHR-polling transport uses long polling XHR requests to create a 3389 | * "persistent" connection with the server. 3390 | * 3391 | * @constructor 3392 | * @api public 3393 | */ 3394 | 3395 | function XHRPolling () { 3396 | io.Transport.XHR.apply(this, arguments); 3397 | }; 3398 | 3399 | /** 3400 | * Inherits from XHR transport. 3401 | */ 3402 | 3403 | io.util.inherit(XHRPolling, io.Transport.XHR); 3404 | 3405 | /** 3406 | * Merge the properties from XHR transport 3407 | */ 3408 | 3409 | io.util.merge(XHRPolling, io.Transport.XHR); 3410 | 3411 | /** 3412 | * Transport name 3413 | * 3414 | * @api public 3415 | */ 3416 | 3417 | XHRPolling.prototype.name = 'xhr-polling'; 3418 | 3419 | /** 3420 | * Establish a connection, for iPhone and Android this will be done once the page 3421 | * is loaded. 3422 | * 3423 | * @returns {Transport} Chaining. 3424 | * @api public 3425 | */ 3426 | 3427 | XHRPolling.prototype.open = function () { 3428 | var self = this; 3429 | 3430 | io.Transport.XHR.prototype.open.call(self); 3431 | return false; 3432 | }; 3433 | 3434 | /** 3435 | * Starts a XHR request to wait for incoming messages. 3436 | * 3437 | * @api private 3438 | */ 3439 | 3440 | function empty () {}; 3441 | 3442 | XHRPolling.prototype.get = function () { 3443 | if (!this.open) return; 3444 | 3445 | var self = this; 3446 | 3447 | function stateChange () { 3448 | if (this.readyState == 4) { 3449 | this.onreadystatechange = empty; 3450 | 3451 | if (this.status == 200) { 3452 | self.onData(this.responseText); 3453 | self.get(); 3454 | } else { 3455 | self.onClose(); 3456 | } 3457 | } 3458 | }; 3459 | 3460 | function onload () { 3461 | this.onload = empty; 3462 | this.onerror = empty; 3463 | self.onData(this.responseText); 3464 | self.get(); 3465 | }; 3466 | 3467 | function onerror () { 3468 | self.onClose(); 3469 | }; 3470 | 3471 | this.xhr = this.request(); 3472 | 3473 | if (global.XDomainRequest && this.xhr instanceof XDomainRequest) { 3474 | this.xhr.onload = onload; 3475 | this.xhr.onerror = onerror; 3476 | } else { 3477 | this.xhr.onreadystatechange = stateChange; 3478 | } 3479 | 3480 | this.xhr.send(null); 3481 | }; 3482 | 3483 | /** 3484 | * Handle the unclean close behavior. 3485 | * 3486 | * @api private 3487 | */ 3488 | 3489 | XHRPolling.prototype.onClose = function () { 3490 | io.Transport.XHR.prototype.onClose.call(this); 3491 | 3492 | if (this.xhr) { 3493 | this.xhr.onreadystatechange = this.xhr.onload = this.xhr.onerror = empty; 3494 | try { 3495 | this.xhr.abort(); 3496 | } catch(e){} 3497 | this.xhr = null; 3498 | } 3499 | }; 3500 | 3501 | /** 3502 | * Webkit based browsers show a infinit spinner when you start a XHR request 3503 | * before the browsers onload event is called so we need to defer opening of 3504 | * the transport until the onload event is called. Wrapping the cb in our 3505 | * defer method solve this. 3506 | * 3507 | * @param {Socket} socket The socket instance that needs a transport 3508 | * @param {Function} fn The callback 3509 | * @api private 3510 | */ 3511 | 3512 | XHRPolling.prototype.ready = function (socket, fn) { 3513 | var self = this; 3514 | 3515 | io.util.defer(function () { 3516 | fn.call(self); 3517 | }); 3518 | }; 3519 | 3520 | /** 3521 | * Add the transport to your public io.transports array. 3522 | * 3523 | * @api private 3524 | */ 3525 | 3526 | io.transports.push('xhr-polling'); 3527 | 3528 | })( 3529 | 'undefined' != typeof io ? io.Transport : module.exports 3530 | , 'undefined' != typeof io ? io : module.parent.exports 3531 | , this 3532 | ); 3533 | 3534 | /** 3535 | * socket.io 3536 | * Copyright(c) 2011 LearnBoost 3537 | * MIT Licensed 3538 | */ 3539 | 3540 | (function (exports, io, global) { 3541 | /** 3542 | * There is a way to hide the loading indicator in Firefox. If you create and 3543 | * remove a iframe it will stop showing the current loading indicator. 3544 | * Unfortunately we can't feature detect that and UA sniffing is evil. 3545 | * 3546 | * @api private 3547 | */ 3548 | 3549 | var indicator = global.document && "MozAppearance" in 3550 | global.document.documentElement.style; 3551 | 3552 | /** 3553 | * Expose constructor. 3554 | */ 3555 | 3556 | exports['jsonp-polling'] = JSONPPolling; 3557 | 3558 | /** 3559 | * The JSONP transport creates an persistent connection by dynamically 3560 | * inserting a script tag in the page. This script tag will receive the 3561 | * information of the Socket.IO server. When new information is received 3562 | * it creates a new script tag for the new data stream. 3563 | * 3564 | * @constructor 3565 | * @extends {io.Transport.xhr-polling} 3566 | * @api public 3567 | */ 3568 | 3569 | function JSONPPolling (socket) { 3570 | io.Transport['xhr-polling'].apply(this, arguments); 3571 | 3572 | this.index = io.j.length; 3573 | 3574 | var self = this; 3575 | 3576 | io.j.push(function (msg) { 3577 | self._(msg); 3578 | }); 3579 | }; 3580 | 3581 | /** 3582 | * Inherits from XHR polling transport. 3583 | */ 3584 | 3585 | io.util.inherit(JSONPPolling, io.Transport['xhr-polling']); 3586 | 3587 | /** 3588 | * Transport name 3589 | * 3590 | * @api public 3591 | */ 3592 | 3593 | JSONPPolling.prototype.name = 'jsonp-polling'; 3594 | 3595 | /** 3596 | * Posts a encoded message to the Socket.IO server using an iframe. 3597 | * The iframe is used because script tags can create POST based requests. 3598 | * The iframe is positioned outside of the view so the user does not 3599 | * notice it's existence. 3600 | * 3601 | * @param {String} data A encoded message. 3602 | * @api private 3603 | */ 3604 | 3605 | JSONPPolling.prototype.post = function (data) { 3606 | var self = this 3607 | , query = io.util.query( 3608 | this.socket.options.query 3609 | , 't='+ (+new Date) + '&i=' + this.index 3610 | ); 3611 | 3612 | if (!this.form) { 3613 | var form = document.createElement('form') 3614 | , area = document.createElement('textarea') 3615 | , id = this.iframeId = 'socketio_iframe_' + this.index 3616 | , iframe; 3617 | 3618 | form.className = 'socketio'; 3619 | form.style.position = 'absolute'; 3620 | form.style.top = '0px'; 3621 | form.style.left = '0px'; 3622 | form.style.display = 'none'; 3623 | form.target = id; 3624 | form.method = 'POST'; 3625 | form.setAttribute('accept-charset', 'utf-8'); 3626 | area.name = 'd'; 3627 | form.appendChild(area); 3628 | document.body.appendChild(form); 3629 | 3630 | this.form = form; 3631 | this.area = area; 3632 | } 3633 | 3634 | this.form.action = this.prepareUrl() + query; 3635 | 3636 | function complete () { 3637 | initIframe(); 3638 | self.socket.setBuffer(false); 3639 | }; 3640 | 3641 | function initIframe () { 3642 | if (self.iframe) { 3643 | self.form.removeChild(self.iframe); 3644 | } 3645 | 3646 | try { 3647 | // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) 3648 | iframe = document.createElement('